Commit | Line | Data |
---|---|---|
3b3009ea CL |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Generic netlink handshake service | |
4 | * | |
5 | * Author: Chuck Lever <chuck.lever@oracle.com> | |
6 | * | |
7 | * Copyright (c) 2023, Oracle and/or its affiliates. | |
8 | */ | |
9 | ||
10 | #include <linux/types.h> | |
11 | #include <linux/socket.h> | |
12 | #include <linux/kernel.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/skbuff.h> | |
15 | #include <linux/mm.h> | |
16 | ||
17 | #include <net/sock.h> | |
18 | #include <net/genetlink.h> | |
19 | #include <net/netns/generic.h> | |
20 | ||
88232ec1 CL |
21 | #include <kunit/visibility.h> |
22 | ||
3b3009ea CL |
23 | #include <uapi/linux/handshake.h> |
24 | #include "handshake.h" | |
25 | #include "genl.h" | |
26 | ||
27 | #include <trace/events/handshake.h> | |
28 | ||
29 | /** | |
30 | * handshake_genl_notify - Notify handlers that a request is waiting | |
31 | * @net: target network namespace | |
32 | * @proto: handshake protocol | |
33 | * @flags: memory allocation control flags | |
34 | * | |
35 | * Returns zero on success or a negative errno if notification failed. | |
36 | */ | |
37 | int handshake_genl_notify(struct net *net, const struct handshake_proto *proto, | |
38 | gfp_t flags) | |
39 | { | |
40 | struct sk_buff *msg; | |
41 | void *hdr; | |
42 | ||
88232ec1 CL |
43 | /* Disable notifications during unit testing */ |
44 | if (!test_bit(HANDSHAKE_F_PROTO_NOTIFY, &proto->hp_flags)) | |
45 | return 0; | |
46 | ||
3b3009ea CL |
47 | if (!genl_has_listeners(&handshake_nl_family, net, |
48 | proto->hp_handler_class)) | |
49 | return -ESRCH; | |
50 | ||
51 | msg = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL); | |
52 | if (!msg) | |
53 | return -ENOMEM; | |
54 | ||
55 | hdr = genlmsg_put(msg, 0, 0, &handshake_nl_family, 0, | |
56 | HANDSHAKE_CMD_READY); | |
57 | if (!hdr) | |
58 | goto out_free; | |
59 | ||
60 | if (nla_put_u32(msg, HANDSHAKE_A_ACCEPT_HANDLER_CLASS, | |
61 | proto->hp_handler_class) < 0) { | |
62 | genlmsg_cancel(msg, hdr); | |
63 | goto out_free; | |
64 | } | |
65 | ||
66 | genlmsg_end(msg, hdr); | |
67 | return genlmsg_multicast_netns(&handshake_nl_family, net, msg, | |
68 | 0, proto->hp_handler_class, flags); | |
69 | ||
70 | out_free: | |
71 | nlmsg_free(msg); | |
72 | return -EMSGSIZE; | |
73 | } | |
74 | ||
75 | /** | |
76 | * handshake_genl_put - Create a generic netlink message header | |
77 | * @msg: buffer in which to create the header | |
78 | * @info: generic netlink message context | |
79 | * | |
80 | * Returns a ready-to-use header, or NULL. | |
81 | */ | |
82 | struct nlmsghdr *handshake_genl_put(struct sk_buff *msg, | |
83 | struct genl_info *info) | |
84 | { | |
85 | return genlmsg_put(msg, info->snd_portid, info->snd_seq, | |
86 | &handshake_nl_family, 0, info->genlhdr->cmd); | |
87 | } | |
88 | EXPORT_SYMBOL(handshake_genl_put); | |
89 | ||
90 | /* | |
91 | * dup() a kernel socket for use as a user space file descriptor | |
92 | * in the current process. The kernel socket must have an | |
93 | * instatiated struct file. | |
94 | * | |
95 | * Implicit argument: "current()" | |
96 | */ | |
97 | static int handshake_dup(struct socket *sock) | |
98 | { | |
99 | struct file *file; | |
100 | int newfd; | |
101 | ||
102 | if (!sock->file) | |
103 | return -EBADF; | |
104 | ||
105 | file = get_file(sock->file); | |
106 | newfd = get_unused_fd_flags(O_CLOEXEC); | |
107 | if (newfd < 0) { | |
108 | fput(file); | |
109 | return newfd; | |
110 | } | |
111 | ||
112 | fd_install(newfd, file); | |
113 | return newfd; | |
114 | } | |
115 | ||
116 | int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info) | |
117 | { | |
118 | struct net *net = sock_net(skb->sk); | |
119 | struct handshake_net *hn = handshake_pernet(net); | |
120 | struct handshake_req *req = NULL; | |
121 | struct socket *sock; | |
122 | int class, fd, err; | |
123 | ||
124 | err = -EOPNOTSUPP; | |
125 | if (!hn) | |
126 | goto out_status; | |
127 | ||
128 | err = -EINVAL; | |
129 | if (GENL_REQ_ATTR_CHECK(info, HANDSHAKE_A_ACCEPT_HANDLER_CLASS)) | |
130 | goto out_status; | |
131 | class = nla_get_u32(info->attrs[HANDSHAKE_A_ACCEPT_HANDLER_CLASS]); | |
132 | ||
133 | err = -EAGAIN; | |
134 | req = handshake_req_next(hn, class); | |
135 | if (!req) | |
136 | goto out_status; | |
137 | ||
138 | sock = req->hr_sk->sk_socket; | |
139 | fd = handshake_dup(sock); | |
140 | if (fd < 0) { | |
141 | err = fd; | |
142 | goto out_complete; | |
143 | } | |
144 | err = req->hr_proto->hp_accept(req, info, fd); | |
145 | if (err) | |
146 | goto out_complete; | |
147 | ||
148 | trace_handshake_cmd_accept(net, req, req->hr_sk, fd); | |
149 | return 0; | |
150 | ||
151 | out_complete: | |
152 | handshake_complete(req, -EIO, NULL); | |
153 | fput(sock->file); | |
154 | out_status: | |
155 | trace_handshake_cmd_accept_err(net, req, NULL, err); | |
156 | return err; | |
157 | } | |
158 | ||
159 | int handshake_nl_done_doit(struct sk_buff *skb, struct genl_info *info) | |
160 | { | |
161 | struct net *net = sock_net(skb->sk); | |
162 | struct socket *sock = NULL; | |
163 | struct handshake_req *req; | |
164 | int fd, status, err; | |
165 | ||
166 | if (GENL_REQ_ATTR_CHECK(info, HANDSHAKE_A_DONE_SOCKFD)) | |
167 | return -EINVAL; | |
168 | fd = nla_get_u32(info->attrs[HANDSHAKE_A_DONE_SOCKFD]); | |
169 | ||
170 | err = 0; | |
171 | sock = sockfd_lookup(fd, &err); | |
172 | if (err) { | |
173 | err = -EBADF; | |
174 | goto out_status; | |
175 | } | |
176 | ||
177 | req = handshake_req_hash_lookup(sock->sk); | |
178 | if (!req) { | |
179 | err = -EBUSY; | |
180 | fput(sock->file); | |
181 | goto out_status; | |
182 | } | |
183 | ||
184 | trace_handshake_cmd_done(net, req, sock->sk, fd); | |
185 | ||
186 | status = -EIO; | |
187 | if (info->attrs[HANDSHAKE_A_DONE_STATUS]) | |
188 | status = nla_get_u32(info->attrs[HANDSHAKE_A_DONE_STATUS]); | |
189 | ||
190 | handshake_complete(req, status, info); | |
191 | fput(sock->file); | |
192 | return 0; | |
193 | ||
194 | out_status: | |
195 | trace_handshake_cmd_done_err(net, req, sock->sk, err); | |
196 | return err; | |
197 | } | |
198 | ||
199 | static unsigned int handshake_net_id; | |
200 | ||
201 | static int __net_init handshake_net_init(struct net *net) | |
202 | { | |
203 | struct handshake_net *hn = net_generic(net, handshake_net_id); | |
204 | unsigned long tmp; | |
205 | struct sysinfo si; | |
206 | ||
207 | /* | |
208 | * Arbitrary limit to prevent handshakes that do not make | |
209 | * progress from clogging up the system. The cap scales up | |
210 | * with the amount of physical memory on the system. | |
211 | */ | |
212 | si_meminfo(&si); | |
213 | tmp = si.totalram / (25 * si.mem_unit); | |
214 | hn->hn_pending_max = clamp(tmp, 3UL, 50UL); | |
215 | ||
216 | spin_lock_init(&hn->hn_lock); | |
217 | hn->hn_pending = 0; | |
218 | hn->hn_flags = 0; | |
219 | INIT_LIST_HEAD(&hn->hn_requests); | |
220 | return 0; | |
221 | } | |
222 | ||
223 | static void __net_exit handshake_net_exit(struct net *net) | |
224 | { | |
225 | struct handshake_net *hn = net_generic(net, handshake_net_id); | |
226 | struct handshake_req *req; | |
227 | LIST_HEAD(requests); | |
228 | ||
229 | /* | |
230 | * Drain the net's pending list. Requests that have been | |
231 | * accepted and are in progress will be destroyed when | |
232 | * the socket is closed. | |
233 | */ | |
234 | spin_lock(&hn->hn_lock); | |
235 | set_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags); | |
236 | list_splice_init(&requests, &hn->hn_requests); | |
237 | spin_unlock(&hn->hn_lock); | |
238 | ||
239 | while (!list_empty(&requests)) { | |
240 | req = list_first_entry(&requests, struct handshake_req, hr_list); | |
241 | list_del(&req->hr_list); | |
242 | ||
243 | /* | |
244 | * Requests on this list have not yet been | |
245 | * accepted, so they do not have an fd to put. | |
246 | */ | |
247 | ||
248 | handshake_complete(req, -ETIMEDOUT, NULL); | |
249 | } | |
250 | } | |
251 | ||
6aa445e3 | 252 | static struct pernet_operations handshake_genl_net_ops = { |
3b3009ea CL |
253 | .init = handshake_net_init, |
254 | .exit = handshake_net_exit, | |
255 | .id = &handshake_net_id, | |
256 | .size = sizeof(struct handshake_net), | |
257 | }; | |
258 | ||
259 | /** | |
260 | * handshake_pernet - Get the handshake private per-net structure | |
261 | * @net: network namespace | |
262 | * | |
263 | * Returns a pointer to the net's private per-net structure for the | |
264 | * handshake module, or NULL if handshake_init() failed. | |
265 | */ | |
266 | struct handshake_net *handshake_pernet(struct net *net) | |
267 | { | |
268 | return handshake_net_id ? | |
269 | net_generic(net, handshake_net_id) : NULL; | |
270 | } | |
88232ec1 | 271 | EXPORT_SYMBOL_IF_KUNIT(handshake_pernet); |
3b3009ea CL |
272 | |
273 | static int __init handshake_init(void) | |
274 | { | |
275 | int ret; | |
276 | ||
277 | ret = handshake_req_hash_init(); | |
278 | if (ret) { | |
279 | pr_warn("handshake: hash initialization failed (%d)\n", ret); | |
280 | return ret; | |
281 | } | |
282 | ||
283 | ret = genl_register_family(&handshake_nl_family); | |
284 | if (ret) { | |
285 | pr_warn("handshake: netlink registration failed (%d)\n", ret); | |
286 | handshake_req_hash_destroy(); | |
287 | return ret; | |
288 | } | |
289 | ||
290 | /* | |
291 | * ORDER: register_pernet_subsys must be done last. | |
292 | * | |
293 | * If initialization does not make it past pernet_subsys | |
294 | * registration, then handshake_net_id will remain 0. That | |
295 | * shunts the handshake consumer API to return ENOTSUPP | |
296 | * to prevent it from dereferencing something that hasn't | |
297 | * been allocated. | |
298 | */ | |
299 | ret = register_pernet_subsys(&handshake_genl_net_ops); | |
300 | if (ret) { | |
301 | pr_warn("handshake: pernet registration failed (%d)\n", ret); | |
302 | genl_unregister_family(&handshake_nl_family); | |
303 | handshake_req_hash_destroy(); | |
304 | } | |
305 | ||
306 | return ret; | |
307 | } | |
308 | ||
309 | static void __exit handshake_exit(void) | |
310 | { | |
311 | unregister_pernet_subsys(&handshake_genl_net_ops); | |
312 | handshake_net_id = 0; | |
313 | ||
314 | handshake_req_hash_destroy(); | |
315 | genl_unregister_family(&handshake_nl_family); | |
316 | } | |
317 | ||
318 | module_init(handshake_init); | |
319 | module_exit(handshake_exit); |