11a59d1b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
27672d0b5SEvgeniy Polyakov /*
37672d0b5SEvgeniy Polyakov * connector.c
47672d0b5SEvgeniy Polyakov *
5acb9c1b2SEvgeniy Polyakov * 2004+ Copyright (c) Evgeniy Polyakov <zbr@ioremap.net>
67672d0b5SEvgeniy Polyakov * All rights reserved.
77672d0b5SEvgeniy Polyakov */
87672d0b5SEvgeniy Polyakov
9d2af686cSRandy Dunlap #include <linux/compiler.h>
107672d0b5SEvgeniy Polyakov #include <linux/kernel.h>
117672d0b5SEvgeniy Polyakov #include <linux/module.h>
127672d0b5SEvgeniy Polyakov #include <linux/list.h>
137672d0b5SEvgeniy Polyakov #include <linux/skbuff.h>
149631d79eSHong zhi guo #include <net/netlink.h>
157672d0b5SEvgeniy Polyakov #include <linux/moduleparam.h>
167672d0b5SEvgeniy Polyakov #include <linux/connector.h>
175a0e3ad6STejun Heo #include <linux/slab.h>
188ed965d6SArjan van de Ven #include <linux/mutex.h>
19a0a61a60SLi Zefan #include <linux/proc_fs.h>
20a0a61a60SLi Zefan #include <linux/spinlock.h>
217672d0b5SEvgeniy Polyakov
227672d0b5SEvgeniy Polyakov #include <net/sock.h>
237672d0b5SEvgeniy Polyakov
247672d0b5SEvgeniy Polyakov MODULE_LICENSE("GPL");
25acb9c1b2SEvgeniy Polyakov MODULE_AUTHOR("Evgeniy Polyakov <zbr@ioremap.net>");
267672d0b5SEvgeniy Polyakov MODULE_DESCRIPTION("Generic userspace <-> kernelspace connector.");
273700c3c2SStephen Hemminger MODULE_ALIAS_NET_PF_PROTO(PF_NETLINK, NETLINK_CONNECTOR);
287672d0b5SEvgeniy Polyakov
297672d0b5SEvgeniy Polyakov static struct cn_dev cdev;
307672d0b5SEvgeniy Polyakov
3178374676SLi Zefan static int cn_already_initialized;
327672d0b5SEvgeniy Polyakov
337672d0b5SEvgeniy Polyakov /*
3434470e0bSDavid Fries * Sends mult (multiple) cn_msg at a time.
3534470e0bSDavid Fries *
367672d0b5SEvgeniy Polyakov * msg->seq and msg->ack are used to determine message genealogy.
377672d0b5SEvgeniy Polyakov * When someone sends message it puts there locally unique sequence
387672d0b5SEvgeniy Polyakov * and random acknowledge numbers. Sequence number may be copied into
397672d0b5SEvgeniy Polyakov * nlmsghdr->nlmsg_seq too.
407672d0b5SEvgeniy Polyakov *
417672d0b5SEvgeniy Polyakov * Sequence number is incremented with each message to be sent.
427672d0b5SEvgeniy Polyakov *
43ac8f7330SDavid Fries * If we expect a reply to our message then the sequence number in
447672d0b5SEvgeniy Polyakov * received message MUST be the same as in original message, and
457672d0b5SEvgeniy Polyakov * acknowledge number MUST be the same + 1.
467672d0b5SEvgeniy Polyakov *
477672d0b5SEvgeniy Polyakov * If we receive a message and its sequence number is not equal to the
487672d0b5SEvgeniy Polyakov * one we are expecting then it is a new message.
497672d0b5SEvgeniy Polyakov *
507672d0b5SEvgeniy Polyakov * If we receive a message and its sequence number is the same as one
517672d0b5SEvgeniy Polyakov * we are expecting but it's acknowledgement number is not equal to
527672d0b5SEvgeniy Polyakov * the acknowledgement number in the original message + 1, then it is
537672d0b5SEvgeniy Polyakov * a new message.
547672d0b5SEvgeniy Polyakov *
5534470e0bSDavid Fries * If msg->len != len, then additional cn_msg messages are expected following
5634470e0bSDavid Fries * the first msg.
5734470e0bSDavid Fries *
58ac8f7330SDavid Fries * The message is sent to, the portid if given, the group if given, both if
59ac8f7330SDavid Fries * both, or if both are zero then the group is looked up and sent there.
607672d0b5SEvgeniy Polyakov */
cn_netlink_send_mult(struct cn_msg * msg,u16 len,u32 portid,u32 __group,gfp_t gfp_mask,int (* filter)(struct sock * dsk,struct sk_buff * skb,void * data),void * filter_data)6134470e0bSDavid Fries int cn_netlink_send_mult(struct cn_msg *msg, u16 len, u32 portid, u32 __group,
622aa1f7a1SAnjali Kulkarni gfp_t gfp_mask,
632aa1f7a1SAnjali Kulkarni int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data),
642aa1f7a1SAnjali Kulkarni void *filter_data)
657672d0b5SEvgeniy Polyakov {
667672d0b5SEvgeniy Polyakov struct cn_callback_entry *__cbq;
677672d0b5SEvgeniy Polyakov unsigned int size;
687672d0b5SEvgeniy Polyakov struct sk_buff *skb;
697672d0b5SEvgeniy Polyakov struct nlmsghdr *nlh;
707672d0b5SEvgeniy Polyakov struct cn_msg *data;
717672d0b5SEvgeniy Polyakov struct cn_dev *dev = &cdev;
727672d0b5SEvgeniy Polyakov u32 group = 0;
737672d0b5SEvgeniy Polyakov int found = 0;
747672d0b5SEvgeniy Polyakov
75ac8f7330SDavid Fries if (portid || __group) {
76ac8f7330SDavid Fries group = __group;
77ac8f7330SDavid Fries } else {
787672d0b5SEvgeniy Polyakov spin_lock_bh(&dev->cbdev->queue_lock);
797672d0b5SEvgeniy Polyakov list_for_each_entry(__cbq, &dev->cbdev->queue_list,
807672d0b5SEvgeniy Polyakov callback_entry) {
81acd042bbSEvgeniy Polyakov if (cn_cb_equal(&__cbq->id.id, &msg->id)) {
827672d0b5SEvgeniy Polyakov found = 1;
837672d0b5SEvgeniy Polyakov group = __cbq->group;
84fd00eeccSLi Zefan break;
857672d0b5SEvgeniy Polyakov }
867672d0b5SEvgeniy Polyakov }
877672d0b5SEvgeniy Polyakov spin_unlock_bh(&dev->cbdev->queue_lock);
887672d0b5SEvgeniy Polyakov
897672d0b5SEvgeniy Polyakov if (!found)
907672d0b5SEvgeniy Polyakov return -ENODEV;
917672d0b5SEvgeniy Polyakov }
927672d0b5SEvgeniy Polyakov
93ac8f7330SDavid Fries if (!portid && !netlink_has_listeners(dev->nls, group))
94b191ba0dSEvgeniy Polyakov return -ESRCH;
95b191ba0dSEvgeniy Polyakov
9634470e0bSDavid Fries size = sizeof(*msg) + len;
977672d0b5SEvgeniy Polyakov
989631d79eSHong zhi guo skb = nlmsg_new(size, gfp_mask);
997672d0b5SEvgeniy Polyakov if (!skb)
1007672d0b5SEvgeniy Polyakov return -ENOMEM;
1017672d0b5SEvgeniy Polyakov
1029631d79eSHong zhi guo nlh = nlmsg_put(skb, 0, msg->seq, NLMSG_DONE, size, 0);
10385c93166SJavier Martinez Canillas if (!nlh) {
10485c93166SJavier Martinez Canillas kfree_skb(skb);
10585c93166SJavier Martinez Canillas return -EMSGSIZE;
10685c93166SJavier Martinez Canillas }
1077672d0b5SEvgeniy Polyakov
10885c93166SJavier Martinez Canillas data = nlmsg_data(nlh);
1097672d0b5SEvgeniy Polyakov
110ac73bf50SMathias Krause memcpy(data, msg, size);
1117672d0b5SEvgeniy Polyakov
1127672d0b5SEvgeniy Polyakov NETLINK_CB(skb).dst_group = group;
1137672d0b5SEvgeniy Polyakov
114ac8f7330SDavid Fries if (group)
1152aa1f7a1SAnjali Kulkarni return netlink_broadcast_filtered(dev->nls, skb, portid, group,
1162aa1f7a1SAnjali Kulkarni gfp_mask, filter,
1172aa1f7a1SAnjali Kulkarni (void *)filter_data);
118d0164adcSMel Gorman return netlink_unicast(dev->nls, skb, portid,
119d0164adcSMel Gorman !gfpflags_allow_blocking(gfp_mask));
1207672d0b5SEvgeniy Polyakov }
12134470e0bSDavid Fries EXPORT_SYMBOL_GPL(cn_netlink_send_mult);
12234470e0bSDavid Fries
12334470e0bSDavid Fries /* same as cn_netlink_send_mult except msg->len is used for len */
cn_netlink_send(struct cn_msg * msg,u32 portid,u32 __group,gfp_t gfp_mask)12434470e0bSDavid Fries int cn_netlink_send(struct cn_msg *msg, u32 portid, u32 __group,
12534470e0bSDavid Fries gfp_t gfp_mask)
12634470e0bSDavid Fries {
1272aa1f7a1SAnjali Kulkarni return cn_netlink_send_mult(msg, msg->len, portid, __group, gfp_mask,
1282aa1f7a1SAnjali Kulkarni NULL, NULL);
12934470e0bSDavid Fries }
130deb0e9b2SAndrew Morton EXPORT_SYMBOL_GPL(cn_netlink_send);
1317672d0b5SEvgeniy Polyakov
1327672d0b5SEvgeniy Polyakov /*
1337672d0b5SEvgeniy Polyakov * Callback helper - queues work and setup destructor for given data.
1347672d0b5SEvgeniy Polyakov */
cn_call_callback(struct sk_buff * skb)135f1489cfbSPhilipp Reisner static int cn_call_callback(struct sk_buff *skb)
1367672d0b5SEvgeniy Polyakov {
137a30cfa47SDavid Fries struct nlmsghdr *nlh;
13804f482faSPatrick McHardy struct cn_callback_entry *i, *cbq = NULL;
1397672d0b5SEvgeniy Polyakov struct cn_dev *dev = &cdev;
1409631d79eSHong zhi guo struct cn_msg *msg = nlmsg_data(nlmsg_hdr(skb));
14104f482faSPatrick McHardy struct netlink_skb_parms *nsp = &NETLINK_CB(skb);
142acd042bbSEvgeniy Polyakov int err = -ENODEV;
1437672d0b5SEvgeniy Polyakov
144a30cfa47SDavid Fries /* verify msg->len is within skb */
145a30cfa47SDavid Fries nlh = nlmsg_hdr(skb);
146a30cfa47SDavid Fries if (nlh->nlmsg_len < NLMSG_HDRLEN + sizeof(struct cn_msg) + msg->len)
147a30cfa47SDavid Fries return -EINVAL;
148a30cfa47SDavid Fries
1497672d0b5SEvgeniy Polyakov spin_lock_bh(&dev->cbdev->queue_lock);
15004f482faSPatrick McHardy list_for_each_entry(i, &dev->cbdev->queue_list, callback_entry) {
15104f482faSPatrick McHardy if (cn_cb_equal(&i->id.id, &msg->id)) {
152e65f7ee3SElena Reshetova refcount_inc(&i->refcnt);
15304f482faSPatrick McHardy cbq = i;
1547672d0b5SEvgeniy Polyakov break;
1557672d0b5SEvgeniy Polyakov }
1567672d0b5SEvgeniy Polyakov }
1577672d0b5SEvgeniy Polyakov spin_unlock_bh(&dev->cbdev->queue_lock);
1587672d0b5SEvgeniy Polyakov
15904f482faSPatrick McHardy if (cbq != NULL) {
16004f482faSPatrick McHardy cbq->callback(msg, nsp);
16104f482faSPatrick McHardy kfree_skb(skb);
16204f482faSPatrick McHardy cn_queue_release_callback(cbq);
1630e087858SPatrick McHardy err = 0;
16404f482faSPatrick McHardy }
16504f482faSPatrick McHardy
166acd042bbSEvgeniy Polyakov return err;
1677672d0b5SEvgeniy Polyakov }
1687672d0b5SEvgeniy Polyakov
169*bfdfdc2fSAnjali Kulkarni /*
170*bfdfdc2fSAnjali Kulkarni * Allow non-root access for NETLINK_CONNECTOR family having CN_IDX_PROC
171*bfdfdc2fSAnjali Kulkarni * multicast group.
172*bfdfdc2fSAnjali Kulkarni */
cn_bind(struct net * net,int group)173*bfdfdc2fSAnjali Kulkarni static int cn_bind(struct net *net, int group)
174*bfdfdc2fSAnjali Kulkarni {
175*bfdfdc2fSAnjali Kulkarni unsigned long groups = (unsigned long) group;
176*bfdfdc2fSAnjali Kulkarni
177*bfdfdc2fSAnjali Kulkarni if (ns_capable(net->user_ns, CAP_NET_ADMIN))
178*bfdfdc2fSAnjali Kulkarni return 0;
179*bfdfdc2fSAnjali Kulkarni
180*bfdfdc2fSAnjali Kulkarni if (test_bit(CN_IDX_PROC - 1, &groups))
181*bfdfdc2fSAnjali Kulkarni return 0;
182*bfdfdc2fSAnjali Kulkarni
183*bfdfdc2fSAnjali Kulkarni return -EPERM;
184*bfdfdc2fSAnjali Kulkarni }
185*bfdfdc2fSAnjali Kulkarni
cn_release(struct sock * sk,unsigned long * groups)1862aa1f7a1SAnjali Kulkarni static void cn_release(struct sock *sk, unsigned long *groups)
1872aa1f7a1SAnjali Kulkarni {
1882aa1f7a1SAnjali Kulkarni if (groups && test_bit(CN_IDX_PROC - 1, groups)) {
1892aa1f7a1SAnjali Kulkarni kfree(sk->sk_user_data);
1902aa1f7a1SAnjali Kulkarni sk->sk_user_data = NULL;
1912aa1f7a1SAnjali Kulkarni }
1922aa1f7a1SAnjali Kulkarni }
1932aa1f7a1SAnjali Kulkarni
1947672d0b5SEvgeniy Polyakov /*
1957672d0b5SEvgeniy Polyakov * Main netlink receiving function.
1967672d0b5SEvgeniy Polyakov *
19700f5e06cSLi Zefan * It checks skb, netlink header and msg sizes, and calls callback helper.
1987672d0b5SEvgeniy Polyakov */
cn_rx_skb(struct sk_buff * skb)19955285bf0SFlorian Westphal static void cn_rx_skb(struct sk_buff *skb)
2007672d0b5SEvgeniy Polyakov {
2017672d0b5SEvgeniy Polyakov struct nlmsghdr *nlh;
202162b2bedSMathias Krause int len, err;
2037672d0b5SEvgeniy Polyakov
2049631d79eSHong zhi guo if (skb->len >= NLMSG_HDRLEN) {
205b529ccf2SArnaldo Carvalho de Melo nlh = nlmsg_hdr(skb);
206162b2bedSMathias Krause len = nlmsg_len(nlh);
2077672d0b5SEvgeniy Polyakov
208162b2bedSMathias Krause if (len < (int)sizeof(struct cn_msg) ||
2097672d0b5SEvgeniy Polyakov skb->len < nlh->nlmsg_len ||
21055285bf0SFlorian Westphal len > CONNECTOR_MAX_MSG_SIZE)
2116cf92e98SMichal Januszewski return;
2127672d0b5SEvgeniy Polyakov
21355285bf0SFlorian Westphal err = cn_call_callback(skb_get(skb));
2147672d0b5SEvgeniy Polyakov if (err < 0)
2157672d0b5SEvgeniy Polyakov kfree_skb(skb);
2167672d0b5SEvgeniy Polyakov }
2177672d0b5SEvgeniy Polyakov }
2187672d0b5SEvgeniy Polyakov
2197672d0b5SEvgeniy Polyakov /*
2207672d0b5SEvgeniy Polyakov * Callback add routing - adds callback with given ID and name.
2217672d0b5SEvgeniy Polyakov * If there is registered callback with the same ID it will not be added.
2227672d0b5SEvgeniy Polyakov *
2237672d0b5SEvgeniy Polyakov * May sleep.
2247672d0b5SEvgeniy Polyakov */
cn_add_callback(const struct cb_id * id,const char * name,void (* callback)(struct cn_msg *,struct netlink_skb_parms *))225c18e6869SGeoff Levand int cn_add_callback(const struct cb_id *id, const char *name,
226f3c48eccSValentin Ilie void (*callback)(struct cn_msg *,
227f3c48eccSValentin Ilie struct netlink_skb_parms *))
2287672d0b5SEvgeniy Polyakov {
2297672d0b5SEvgeniy Polyakov struct cn_dev *dev = &cdev;
2307672d0b5SEvgeniy Polyakov
231d6cc7f1aSEvgeniy Polyakov if (!cn_already_initialized)
232d6cc7f1aSEvgeniy Polyakov return -EAGAIN;
233d6cc7f1aSEvgeniy Polyakov
234fe6bc89aSQinglang Miao return cn_queue_add_callback(dev->cbdev, name, id, callback);
2357672d0b5SEvgeniy Polyakov }
236deb0e9b2SAndrew Morton EXPORT_SYMBOL_GPL(cn_add_callback);
2377672d0b5SEvgeniy Polyakov
2387672d0b5SEvgeniy Polyakov /*
2397672d0b5SEvgeniy Polyakov * Callback remove routing - removes callback
2407672d0b5SEvgeniy Polyakov * with given ID.
2417672d0b5SEvgeniy Polyakov * If there is no registered callback with given
2427672d0b5SEvgeniy Polyakov * ID nothing happens.
2437672d0b5SEvgeniy Polyakov *
2447672d0b5SEvgeniy Polyakov * May sleep while waiting for reference counter to become zero.
2457672d0b5SEvgeniy Polyakov */
cn_del_callback(const struct cb_id * id)246c18e6869SGeoff Levand void cn_del_callback(const struct cb_id *id)
2477672d0b5SEvgeniy Polyakov {
2487672d0b5SEvgeniy Polyakov struct cn_dev *dev = &cdev;
2497672d0b5SEvgeniy Polyakov
2507672d0b5SEvgeniy Polyakov cn_queue_del_callback(dev->cbdev, id);
2517672d0b5SEvgeniy Polyakov }
252deb0e9b2SAndrew Morton EXPORT_SYMBOL_GPL(cn_del_callback);
2537672d0b5SEvgeniy Polyakov
cn_proc_show(struct seq_file * m,void * v)254d2af686cSRandy Dunlap static int __maybe_unused cn_proc_show(struct seq_file *m, void *v)
255a0a61a60SLi Zefan {
256a0a61a60SLi Zefan struct cn_queue_dev *dev = cdev.cbdev;
257a0a61a60SLi Zefan struct cn_callback_entry *cbq;
258a0a61a60SLi Zefan
259a0a61a60SLi Zefan seq_printf(m, "Name ID\n");
260a0a61a60SLi Zefan
261a0a61a60SLi Zefan spin_lock_bh(&dev->queue_lock);
262a0a61a60SLi Zefan
263a0a61a60SLi Zefan list_for_each_entry(cbq, &dev->queue_list, callback_entry) {
264a0a61a60SLi Zefan seq_printf(m, "%-15s %u:%u\n",
265a0a61a60SLi Zefan cbq->id.name,
266a0a61a60SLi Zefan cbq->id.id.idx,
267a0a61a60SLi Zefan cbq->id.id.val);
268a0a61a60SLi Zefan }
269a0a61a60SLi Zefan
270a0a61a60SLi Zefan spin_unlock_bh(&dev->queue_lock);
271a0a61a60SLi Zefan
272a0a61a60SLi Zefan return 0;
273a0a61a60SLi Zefan }
274a0a61a60SLi Zefan
cn_init(void)2750fe763c5SGreg Kroah-Hartman static int cn_init(void)
2767672d0b5SEvgeniy Polyakov {
2777672d0b5SEvgeniy Polyakov struct cn_dev *dev = &cdev;
278a31f2d17SPablo Neira Ayuso struct netlink_kernel_cfg cfg = {
279a31f2d17SPablo Neira Ayuso .groups = CN_NETLINK_USERS + 0xf,
280903e9d1bSVasily Averin .input = cn_rx_skb,
281*bfdfdc2fSAnjali Kulkarni .flags = NL_CFG_F_NONROOT_RECV,
282*bfdfdc2fSAnjali Kulkarni .bind = cn_bind,
2832aa1f7a1SAnjali Kulkarni .release = cn_release,
284a31f2d17SPablo Neira Ayuso };
2857672d0b5SEvgeniy Polyakov
2869f00d977SPablo Neira Ayuso dev->nls = netlink_kernel_create(&init_net, NETLINK_CONNECTOR, &cfg);
2877672d0b5SEvgeniy Polyakov if (!dev->nls)
2887672d0b5SEvgeniy Polyakov return -EIO;
2897672d0b5SEvgeniy Polyakov
2907672d0b5SEvgeniy Polyakov dev->cbdev = cn_queue_alloc_dev("cqueue", dev->nls);
2917672d0b5SEvgeniy Polyakov if (!dev->cbdev) {
292b7c6ba6eSDenis V. Lunev netlink_kernel_release(dev->nls);
2937672d0b5SEvgeniy Polyakov return -EINVAL;
2947672d0b5SEvgeniy Polyakov }
2957672d0b5SEvgeniy Polyakov
296d6cc7f1aSEvgeniy Polyakov cn_already_initialized = 1;
297d6cc7f1aSEvgeniy Polyakov
2983f3942acSChristoph Hellwig proc_create_single("connector", S_IRUGO, init_net.proc_net, cn_proc_show);
299a0a61a60SLi Zefan
3007672d0b5SEvgeniy Polyakov return 0;
3017672d0b5SEvgeniy Polyakov }
3027672d0b5SEvgeniy Polyakov
cn_fini(void)3030fe763c5SGreg Kroah-Hartman static void cn_fini(void)
3047672d0b5SEvgeniy Polyakov {
3057672d0b5SEvgeniy Polyakov struct cn_dev *dev = &cdev;
3067672d0b5SEvgeniy Polyakov
3077672d0b5SEvgeniy Polyakov cn_already_initialized = 0;
3087672d0b5SEvgeniy Polyakov
309ece31ffdSGao feng remove_proc_entry("connector", init_net.proc_net);
310a0a61a60SLi Zefan
3117672d0b5SEvgeniy Polyakov cn_queue_free_dev(dev->cbdev);
312b7c6ba6eSDenis V. Lunev netlink_kernel_release(dev->nls);
3137672d0b5SEvgeniy Polyakov }
3147672d0b5SEvgeniy Polyakov
315d6cc7f1aSEvgeniy Polyakov subsys_initcall(cn_init);
3167672d0b5SEvgeniy Polyakov module_exit(cn_fini);
317