Commit | Line | Data |
---|---|---|
1a59d1b8 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
7672d0b5 | 2 | /* |
f3c48ecc | 3 | * connector.c |
1a5645bc | 4 | * |
acb9c1b2 | 5 | * 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net> |
7672d0b5 | 6 | * All rights reserved. |
7672d0b5 EP |
7 | */ |
8 | ||
d2af686c | 9 | #include <linux/compiler.h> |
7672d0b5 EP |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> | |
12 | #include <linux/list.h> | |
13 | #include <linux/skbuff.h> | |
9631d79e | 14 | #include <net/netlink.h> |
7672d0b5 EP |
15 | #include <linux/moduleparam.h> |
16 | #include <linux/connector.h> | |
5a0e3ad6 | 17 | #include <linux/slab.h> |
8ed965d6 | 18 | #include <linux/mutex.h> |
a0a61a60 LZ |
19 | #include <linux/proc_fs.h> |
20 | #include <linux/spinlock.h> | |
7672d0b5 EP |
21 | |
22 | #include <net/sock.h> | |
23 | ||
24 | MODULE_LICENSE("GPL"); | |
acb9c1b2 | 25 | MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>"); |
7672d0b5 | 26 | MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector."); |
3700c3c2 | 27 | MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_CONNECTOR); |
7672d0b5 | 28 | |
7672d0b5 EP |
29 | static struct cn_dev cdev; |
30 | ||
78374676 | 31 | static int cn_already_initialized; |
7672d0b5 EP |
32 | |
33 | /* | |
34470e0b DF |
34 | * Sends mult (multiple) cn_msg at a time. |
35 | * | |
7672d0b5 EP |
36 | * msg->seq and msg->ack are used to determine message genealogy. |
37 | * When someone sends message it puts there locally unique sequence | |
38 | * and random acknowledge numbers. Sequence number may be copied into | |
39 | * nlmsghdr->nlmsg_seq too. | |
40 | * | |
41 | * Sequence number is incremented with each message to be sent. | |
42 | * | |
ac8f7330 | 43 | * If we expect a reply to our message then the sequence number in |
7672d0b5 EP |
44 | * received message MUST be the same as in original message, and |
45 | * acknowledge number MUST be the same + 1. | |
46 | * | |
47 | * If we receive a message and its sequence number is not equal to the | |
48 | * one we are expecting then it is a new message. | |
49 | * | |
50 | * If we receive a message and its sequence number is the same as one | |
51 | * we are expecting but it's acknowledgement number is not equal to | |
52 | * the acknowledgement number in the original message + 1, then it is | |
53 | * a new message. | |
54 | * | |
34470e0b DF |
55 | * If msg->len != len, then additional cn_msg messages are expected following |
56 | * the first msg. | |
57 | * | |
ac8f7330 DF |
58 | * The message is sent to, the portid if given, the group if given, both if |
59 | * both, or if both are zero then the group is looked up and sent there. | |
7672d0b5 | 60 | */ |
34470e0b | 61 | int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group, |
403863e9 JP |
62 | gfp_t gfp_mask, netlink_filter_fn filter, |
63 | void *filter_data) | |
7672d0b5 EP |
64 | { |
65 | struct cn_callback_entry *__cbq; | |
66 | unsigned int size; | |
67 | struct sk_buff *skb; | |
68 | struct nlmsghdr *nlh; | |
69 | struct cn_msg *data; | |
70 | struct cn_dev *dev = &cdev; | |
71 | u32 group = 0; | |
72 | int found = 0; | |
73 | ||
ac8f7330 DF |
74 | if (portid || __group) { |
75 | group = __group; | |
76 | } else { | |
7672d0b5 EP |
77 | spin_lock_bh(&dev->cbdev->queue_lock); |
78 | list_for_each_entry(__cbq, &dev->cbdev->queue_list, | |
79 | callback_entry) { | |
acd042bb | 80 | if (cn_cb_equal(&__cbq->id.id, &msg->id)) { |
7672d0b5 EP |
81 | found = 1; |
82 | group = __cbq->group; | |
fd00eecc | 83 | break; |
7672d0b5 EP |
84 | } |
85 | } | |
86 | spin_unlock_bh(&dev->cbdev->queue_lock); | |
87 | ||
88 | if (!found) | |
89 | return -ENODEV; | |
7672d0b5 EP |
90 | } |
91 | ||
ac8f7330 | 92 | if (!portid && !netlink_has_listeners(dev->nls, group)) |
b191ba0d EP |
93 | return -ESRCH; |
94 | ||
34470e0b | 95 | size = sizeof(*msg) + len; |
7672d0b5 | 96 | |
9631d79e | 97 | skb = nlmsg_new(size, gfp_mask); |
7672d0b5 EP |
98 | if (!skb) |
99 | return -ENOMEM; | |
100 | ||
9631d79e | 101 | nlh = nlmsg_put(skb, 0, msg->seq, NLMSG_DONE, size, 0); |
85c93166 JMC |
102 | if (!nlh) { |
103 | kfree_skb(skb); | |
104 | return -EMSGSIZE; | |
105 | } | |
7672d0b5 | 106 | |
85c93166 | 107 | data = nlmsg_data(nlh); |
7672d0b5 | 108 | |
ac73bf50 | 109 | memcpy(data, msg, size); |
7672d0b5 EP |
110 | |
111 | NETLINK_CB(skb).dst_group = group; | |
112 | ||
ac8f7330 | 113 | if (group) |
2aa1f7a1 AK |
114 | return netlink_broadcast_filtered(dev->nls, skb, portid, group, |
115 | gfp_mask, filter, | |
116 | (void *)filter_data); | |
d0164adc MG |
117 | return netlink_unicast(dev->nls, skb, portid, |
118 | !gfpflags_allow_blocking(gfp_mask)); | |
7672d0b5 | 119 | } |
34470e0b DF |
120 | EXPORT_SYMBOL_GPL(cn_netlink_send_mult); |
121 | ||
122 | /* same as cn_netlink_send_mult except msg->len is used for len */ | |
123 | int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group, | |
124 | gfp_t gfp_mask) | |
125 | { | |
2aa1f7a1 AK |
126 | return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask, |
127 | NULL, NULL); | |
34470e0b | 128 | } |
deb0e9b2 | 129 | EXPORT_SYMBOL_GPL(cn_netlink_send); |
7672d0b5 EP |
130 | |
131 | /* | |
132 | * Callback helper - queues work and setup destructor for given data. | |
133 | */ | |
f1489cfb | 134 | static int cn_call_callback(struct sk_buff *skb) |
7672d0b5 | 135 | { |
a30cfa47 | 136 | struct nlmsghdr *nlh; |
04f482fa | 137 | struct cn_callback_entry *i, *cbq = NULL; |
7672d0b5 | 138 | struct cn_dev *dev = &cdev; |
9631d79e | 139 | struct cn_msg *msg = nlmsg_data(nlmsg_hdr(skb)); |
04f482fa | 140 | struct netlink_skb_parms *nsp = &NETLINK_CB(skb); |
acd042bb | 141 | int err = -ENODEV; |
7672d0b5 | 142 | |
a30cfa47 DF |
143 | /* verify msg->len is within skb */ |
144 | nlh = nlmsg_hdr(skb); | |
145 | if (nlh->nlmsg_len < NLMSG_HDRLEN + sizeof(struct cn_msg) + msg->len) | |
146 | return -EINVAL; | |
147 | ||
7672d0b5 | 148 | spin_lock_bh(&dev->cbdev->queue_lock); |
04f482fa PM |
149 | list_for_each_entry(i, &dev->cbdev->queue_list, callback_entry) { |
150 | if (cn_cb_equal(&i->id.id, &msg->id)) { | |
e65f7ee3 | 151 | refcount_inc(&i->refcnt); |
04f482fa | 152 | cbq = i; |
7672d0b5 EP |
153 | break; |
154 | } | |
155 | } | |
156 | spin_unlock_bh(&dev->cbdev->queue_lock); | |
157 | ||
04f482fa PM |
158 | if (cbq != NULL) { |
159 | cbq->callback(msg, nsp); | |
160 | kfree_skb(skb); | |
161 | cn_queue_release_callback(cbq); | |
0e087858 | 162 | err = 0; |
04f482fa PM |
163 | } |
164 | ||
acd042bb | 165 | return err; |
7672d0b5 EP |
166 | } |
167 | ||
bfdfdc2f AK |
168 | /* |
169 | * Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC | |
170 | * multicast group. | |
171 | */ | |
172 | static int cn_bind(struct net *net, int group) | |
173 | { | |
174 | unsigned long groups = (unsigned long) group; | |
175 | ||
176 | if (ns_capable(net->user_ns, CAP_NET_ADMIN)) | |
177 | return 0; | |
178 | ||
179 | if (test_bit(CN_IDX_PROC - 1, &groups)) | |
180 | return 0; | |
181 | ||
182 | return -EPERM; | |
183 | } | |
184 | ||
2aa1f7a1 AK |
185 | static void cn_release(struct sock *sk, unsigned long *groups) |
186 | { | |
187 | if (groups && test_bit(CN_IDX_PROC - 1, groups)) { | |
188 | kfree(sk->sk_user_data); | |
189 | sk->sk_user_data = NULL; | |
190 | } | |
191 | } | |
192 | ||
7672d0b5 EP |
193 | /* |
194 | * Main netlink receiving function. | |
195 | * | |
00f5e06c | 196 | * It checks skb, netlink header and msg sizes, and calls callback helper. |
7672d0b5 | 197 | */ |
55285bf0 | 198 | static void cn_rx_skb(struct sk_buff *skb) |
7672d0b5 EP |
199 | { |
200 | struct nlmsghdr *nlh; | |
162b2bed | 201 | int len, err; |
7672d0b5 | 202 | |
9631d79e | 203 | if (skb->len >= NLMSG_HDRLEN) { |
b529ccf2 | 204 | nlh = nlmsg_hdr(skb); |
162b2bed | 205 | len = nlmsg_len(nlh); |
7672d0b5 | 206 | |
162b2bed | 207 | if (len < (int)sizeof(struct cn_msg) || |
7672d0b5 | 208 | skb->len < nlh->nlmsg_len || |
55285bf0 | 209 | len > CONNECTOR_MAX_MSG_SIZE) |
6cf92e98 | 210 | return; |
7672d0b5 | 211 | |
55285bf0 | 212 | err = cn_call_callback(skb_get(skb)); |
7672d0b5 EP |
213 | if (err < 0) |
214 | kfree_skb(skb); | |
215 | } | |
7672d0b5 EP |
216 | } |
217 | ||
7672d0b5 EP |
218 | /* |
219 | * Callback add routing - adds callback with given ID and name. | |
220 | * If there is registered callback with the same ID it will not be added. | |
221 | * | |
222 | * May sleep. | |
223 | */ | |
c18e6869 | 224 | int cn_add_callback(const struct cb_id *id, const char *name, |
f3c48ecc VI |
225 | void (*callback)(struct cn_msg *, |
226 | struct netlink_skb_parms *)) | |
7672d0b5 | 227 | { |
7672d0b5 | 228 | struct cn_dev *dev = &cdev; |
7672d0b5 | 229 | |
d6cc7f1a EP |
230 | if (!cn_already_initialized) |
231 | return -EAGAIN; | |
232 | ||
fe6bc89a | 233 | return cn_queue_add_callback(dev->cbdev, name, id, callback); |
7672d0b5 | 234 | } |
deb0e9b2 | 235 | EXPORT_SYMBOL_GPL(cn_add_callback); |
7672d0b5 EP |
236 | |
237 | /* | |
238 | * Callback remove routing - removes callback | |
239 | * with given ID. | |
240 | * If there is no registered callback with given | |
241 | * ID nothing happens. | |
242 | * | |
243 | * May sleep while waiting for reference counter to become zero. | |
244 | */ | |
c18e6869 | 245 | void cn_del_callback(const struct cb_id *id) |
7672d0b5 EP |
246 | { |
247 | struct cn_dev *dev = &cdev; | |
248 | ||
249 | cn_queue_del_callback(dev->cbdev, id); | |
7672d0b5 | 250 | } |
deb0e9b2 | 251 | EXPORT_SYMBOL_GPL(cn_del_callback); |
7672d0b5 | 252 | |
d2af686c | 253 | static int __maybe_unused cn_proc_show(struct seq_file *m, void *v) |
a0a61a60 LZ |
254 | { |
255 | struct cn_queue_dev *dev = cdev.cbdev; | |
256 | struct cn_callback_entry *cbq; | |
257 | ||
258 | seq_printf(m, "Name ID\n"); | |
259 | ||
260 | spin_lock_bh(&dev->queue_lock); | |
261 | ||
262 | list_for_each_entry(cbq, &dev->queue_list, callback_entry) { | |
263 | seq_printf(m, "%-15s %u:%u\n", | |
264 | cbq->id.name, | |
265 | cbq->id.id.idx, | |
266 | cbq->id.id.val); | |
267 | } | |
268 | ||
269 | spin_unlock_bh(&dev->queue_lock); | |
270 | ||
271 | return 0; | |
272 | } | |
273 | ||
0fe763c5 | 274 | static int cn_init(void) |
7672d0b5 EP |
275 | { |
276 | struct cn_dev *dev = &cdev; | |
a31f2d17 PNA |
277 | struct netlink_kernel_cfg cfg = { |
278 | .groups = CN_NETLINK_USERS + 0xf, | |
903e9d1b | 279 | .input = cn_rx_skb, |
bfdfdc2f AK |
280 | .flags = NL_CFG_F_NONROOT_RECV, |
281 | .bind = cn_bind, | |
2aa1f7a1 | 282 | .release = cn_release, |
a31f2d17 | 283 | }; |
7672d0b5 | 284 | |
9f00d977 | 285 | dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, &cfg); |
7672d0b5 EP |
286 | if (!dev->nls) |
287 | return -EIO; | |
288 | ||
289 | dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls); | |
290 | if (!dev->cbdev) { | |
b7c6ba6e | 291 | netlink_kernel_release(dev->nls); |
7672d0b5 EP |
292 | return -EINVAL; |
293 | } | |
1a5645bc | 294 | |
d6cc7f1a | 295 | cn_already_initialized = 1; |
7672d0b5 | 296 | |
3f3942ac | 297 | proc_create_single("connector", S_IRUGO, init_net.proc_net, cn_proc_show); |
a0a61a60 | 298 | |
7672d0b5 EP |
299 | return 0; |
300 | } | |
301 | ||
0fe763c5 | 302 | static void cn_fini(void) |
7672d0b5 EP |
303 | { |
304 | struct cn_dev *dev = &cdev; | |
305 | ||
306 | cn_already_initialized = 0; | |
307 | ||
ece31ffd | 308 | remove_proc_entry("connector", init_net.proc_net); |
a0a61a60 | 309 | |
7672d0b5 | 310 | cn_queue_free_dev(dev->cbdev); |
b7c6ba6e | 311 | netlink_kernel_release(dev->nls); |
7672d0b5 EP |
312 | } |
313 | ||
d6cc7f1a | 314 | subsys_initcall(cn_init); |
7672d0b5 | 315 | module_exit(cn_fini); |