diff options
author | Pravin B Shelar <pshelar@nicira.com> | 2013-04-23 03:48:30 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-04-25 01:43:15 -0400 |
commit | def3117493eafd9dfa1f809d861e0031b2cc8a07 (patch) | |
tree | 93edfbb60f9d57d37d5643062cf3d41befc18579 /net/netlink | |
parent | 133b94245c54bb2d8832bfb15975b931bc00d914 (diff) |
genl: Allow concurrent genl callbacks.
All genl callbacks are serialized by genl-mutex. This can become
bottleneck in multi threaded case.
Following patch adds an parameter to genl_family so that a
particular family can get concurrent netlink callback without
genl_lock held.
New rw-sem is used to protect genl callback from genl family unregister.
in case of parallel_ops genl-family read-lock is taken for callbacks and
write lock is taken for register or unregistration for any family.
In case of locked genl family semaphore and gel-mutex is locked for
any openration.
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/netlink')
-rw-r--r-- | net/netlink/genetlink.c | 114 |
1 files changed, 76 insertions, 38 deletions
diff --git a/net/netlink/genetlink.c b/net/netlink/genetlink.c index 5a55be3f17a5..2f72598dd8fe 100644 --- a/net/netlink/genetlink.c +++ b/net/netlink/genetlink.c | |||
@@ -16,10 +16,12 @@ | |||
16 | #include <linux/skbuff.h> | 16 | #include <linux/skbuff.h> |
17 | #include <linux/mutex.h> | 17 | #include <linux/mutex.h> |
18 | #include <linux/bitmap.h> | 18 | #include <linux/bitmap.h> |
19 | #include <linux/rwsem.h> | ||
19 | #include <net/sock.h> | 20 | #include <net/sock.h> |
20 | #include <net/genetlink.h> | 21 | #include <net/genetlink.h> |
21 | 22 | ||
22 | static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */ | 23 | static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */ |
24 | static DECLARE_RWSEM(cb_lock); | ||
23 | 25 | ||
24 | void genl_lock(void) | 26 | void genl_lock(void) |
25 | { | 27 | { |
@@ -41,6 +43,18 @@ int lockdep_genl_is_held(void) | |||
41 | EXPORT_SYMBOL(lockdep_genl_is_held); | 43 | EXPORT_SYMBOL(lockdep_genl_is_held); |
42 | #endif | 44 | #endif |
43 | 45 | ||
46 | static void genl_lock_all(void) | ||
47 | { | ||
48 | down_write(&cb_lock); | ||
49 | genl_lock(); | ||
50 | } | ||
51 | |||
52 | static void genl_unlock_all(void) | ||
53 | { | ||
54 | genl_unlock(); | ||
55 | up_write(&cb_lock); | ||
56 | } | ||
57 | |||
44 | #define GENL_FAM_TAB_SIZE 16 | 58 | #define GENL_FAM_TAB_SIZE 16 |
45 | #define GENL_FAM_TAB_MASK (GENL_FAM_TAB_SIZE - 1) | 59 | #define GENL_FAM_TAB_MASK (GENL_FAM_TAB_SIZE - 1) |
46 | 60 | ||
@@ -144,7 +158,7 @@ int genl_register_mc_group(struct genl_family *family, | |||
144 | BUG_ON(grp->name[0] == '\0'); | 158 | BUG_ON(grp->name[0] == '\0'); |
145 | BUG_ON(memchr(grp->name, '\0', GENL_NAMSIZ) == NULL); | 159 | BUG_ON(memchr(grp->name, '\0', GENL_NAMSIZ) == NULL); |
146 | 160 | ||
147 | genl_lock(); | 161 | genl_lock_all(); |
148 | 162 | ||
149 | /* special-case our own group */ | 163 | /* special-case our own group */ |
150 | if (grp == ¬ify_grp) | 164 | if (grp == ¬ify_grp) |
@@ -213,7 +227,7 @@ int genl_register_mc_group(struct genl_family *family, | |||
213 | 227 | ||
214 | genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, grp); | 228 | genl_ctrl_event(CTRL_CMD_NEWMCAST_GRP, grp); |
215 | out: | 229 | out: |
216 | genl_unlock(); | 230 | genl_unlock_all(); |
217 | return err; | 231 | return err; |
218 | } | 232 | } |
219 | EXPORT_SYMBOL(genl_register_mc_group); | 233 | EXPORT_SYMBOL(genl_register_mc_group); |
@@ -255,9 +269,9 @@ static void __genl_unregister_mc_group(struct genl_family *family, | |||
255 | void genl_unregister_mc_group(struct genl_family *family, | 269 | void genl_unregister_mc_group(struct genl_family *family, |
256 | struct genl_multicast_group *grp) | 270 | struct genl_multicast_group *grp) |
257 | { | 271 | { |
258 | genl_lock(); | 272 | genl_lock_all(); |
259 | __genl_unregister_mc_group(family, grp); | 273 | __genl_unregister_mc_group(family, grp); |
260 | genl_unlock(); | 274 | genl_unlock_all(); |
261 | } | 275 | } |
262 | EXPORT_SYMBOL(genl_unregister_mc_group); | 276 | EXPORT_SYMBOL(genl_unregister_mc_group); |
263 | 277 | ||
@@ -303,9 +317,9 @@ int genl_register_ops(struct genl_family *family, struct genl_ops *ops) | |||
303 | if (ops->policy) | 317 | if (ops->policy) |
304 | ops->flags |= GENL_CMD_CAP_HASPOL; | 318 | ops->flags |= GENL_CMD_CAP_HASPOL; |
305 | 319 | ||
306 | genl_lock(); | 320 | genl_lock_all(); |
307 | list_add_tail(&ops->ops_list, &family->ops_list); | 321 | list_add_tail(&ops->ops_list, &family->ops_list); |
308 | genl_unlock(); | 322 | genl_unlock_all(); |
309 | 323 | ||
310 | genl_ctrl_event(CTRL_CMD_NEWOPS, ops); | 324 | genl_ctrl_event(CTRL_CMD_NEWOPS, ops); |
311 | err = 0; | 325 | err = 0; |
@@ -334,16 +348,16 @@ int genl_unregister_ops(struct genl_family *family, struct genl_ops *ops) | |||
334 | { | 348 | { |
335 | struct genl_ops *rc; | 349 | struct genl_ops *rc; |
336 | 350 | ||
337 | genl_lock(); | 351 | genl_lock_all(); |
338 | list_for_each_entry(rc, &family->ops_list, ops_list) { | 352 | list_for_each_entry(rc, &family->ops_list, ops_list) { |
339 | if (rc == ops) { | 353 | if (rc == ops) { |
340 | list_del(&ops->ops_list); | 354 | list_del(&ops->ops_list); |
341 | genl_unlock(); | 355 | genl_unlock_all(); |
342 | genl_ctrl_event(CTRL_CMD_DELOPS, ops); | 356 | genl_ctrl_event(CTRL_CMD_DELOPS, ops); |
343 | return 0; | 357 | return 0; |
344 | } | 358 | } |
345 | } | 359 | } |
346 | genl_unlock(); | 360 | genl_unlock_all(); |
347 | 361 | ||
348 | return -ENOENT; | 362 | return -ENOENT; |
349 | } | 363 | } |
@@ -373,7 +387,7 @@ int genl_register_family(struct genl_family *family) | |||
373 | INIT_LIST_HEAD(&family->ops_list); | 387 | INIT_LIST_HEAD(&family->ops_list); |
374 | INIT_LIST_HEAD(&family->mcast_groups); | 388 | INIT_LIST_HEAD(&family->mcast_groups); |
375 | 389 | ||
376 | genl_lock(); | 390 | genl_lock_all(); |
377 | 391 | ||
378 | if (genl_family_find_byname(family->name)) { | 392 | if (genl_family_find_byname(family->name)) { |
379 | err = -EEXIST; | 393 | err = -EEXIST; |
@@ -394,7 +408,7 @@ int genl_register_family(struct genl_family *family) | |||
394 | goto errout_locked; | 408 | goto errout_locked; |
395 | } | 409 | } |
396 | 410 | ||
397 | if (family->maxattr) { | 411 | if (family->maxattr && !family->parallel_ops) { |
398 | family->attrbuf = kmalloc((family->maxattr+1) * | 412 | family->attrbuf = kmalloc((family->maxattr+1) * |
399 | sizeof(struct nlattr *), GFP_KERNEL); | 413 | sizeof(struct nlattr *), GFP_KERNEL); |
400 | if (family->attrbuf == NULL) { | 414 | if (family->attrbuf == NULL) { |
@@ -405,14 +419,14 @@ int genl_register_family(struct genl_family *family) | |||
405 | family->attrbuf = NULL; | 419 | family->attrbuf = NULL; |
406 | 420 | ||
407 | list_add_tail(&family->family_list, genl_family_chain(family->id)); | 421 | list_add_tail(&family->family_list, genl_family_chain(family->id)); |
408 | genl_unlock(); | 422 | genl_unlock_all(); |
409 | 423 | ||
410 | genl_ctrl_event(CTRL_CMD_NEWFAMILY, family); | 424 | genl_ctrl_event(CTRL_CMD_NEWFAMILY, family); |
411 | 425 | ||
412 | return 0; | 426 | return 0; |
413 | 427 | ||
414 | errout_locked: | 428 | errout_locked: |
415 | genl_unlock(); | 429 | genl_unlock_all(); |
416 | errout: | 430 | errout: |
417 | return err; | 431 | return err; |
418 | } | 432 | } |
@@ -476,7 +490,7 @@ int genl_unregister_family(struct genl_family *family) | |||
476 | { | 490 | { |
477 | struct genl_family *rc; | 491 | struct genl_family *rc; |
478 | 492 | ||
479 | genl_lock(); | 493 | genl_lock_all(); |
480 | 494 | ||
481 | genl_unregister_mc_groups(family); | 495 | genl_unregister_mc_groups(family); |
482 | 496 | ||
@@ -486,14 +500,14 @@ int genl_unregister_family(struct genl_family *family) | |||
486 | 500 | ||
487 | list_del(&rc->family_list); | 501 | list_del(&rc->family_list); |
488 | INIT_LIST_HEAD(&family->ops_list); | 502 | INIT_LIST_HEAD(&family->ops_list); |
489 | genl_unlock(); | 503 | genl_unlock_all(); |
490 | 504 | ||
491 | kfree(family->attrbuf); | 505 | kfree(family->attrbuf); |
492 | genl_ctrl_event(CTRL_CMD_DELFAMILY, family); | 506 | genl_ctrl_event(CTRL_CMD_DELFAMILY, family); |
493 | return 0; | 507 | return 0; |
494 | } | 508 | } |
495 | 509 | ||
496 | genl_unlock(); | 510 | genl_unlock_all(); |
497 | 511 | ||
498 | return -ENOENT; | 512 | return -ENOENT; |
499 | } | 513 | } |
@@ -530,19 +544,17 @@ void *genlmsg_put(struct sk_buff *skb, u32 portid, u32 seq, | |||
530 | } | 544 | } |
531 | EXPORT_SYMBOL(genlmsg_put); | 545 | EXPORT_SYMBOL(genlmsg_put); |
532 | 546 | ||
533 | static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) | 547 | static int genl_family_rcv_msg(struct genl_family *family, |
548 | struct sk_buff *skb, | ||
549 | struct nlmsghdr *nlh) | ||
534 | { | 550 | { |
535 | struct genl_ops *ops; | 551 | struct genl_ops *ops; |
536 | struct genl_family *family; | ||
537 | struct net *net = sock_net(skb->sk); | 552 | struct net *net = sock_net(skb->sk); |
538 | struct genl_info info; | 553 | struct genl_info info; |
539 | struct genlmsghdr *hdr = nlmsg_data(nlh); | 554 | struct genlmsghdr *hdr = nlmsg_data(nlh); |
555 | struct nlattr **attrbuf; | ||
540 | int hdrlen, err; | 556 | int hdrlen, err; |
541 | 557 | ||
542 | family = genl_family_find_byid(nlh->nlmsg_type); | ||
543 | if (family == NULL) | ||
544 | return -ENOENT; | ||
545 | |||
546 | /* this family doesn't exist in this netns */ | 558 | /* this family doesn't exist in this netns */ |
547 | if (!family->netnsok && !net_eq(net, &init_net)) | 559 | if (!family->netnsok && !net_eq(net, &init_net)) |
548 | return -ENOENT; | 560 | return -ENOENT; |
@@ -560,26 +572,30 @@ static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) | |||
560 | return -EPERM; | 572 | return -EPERM; |
561 | 573 | ||
562 | if (nlh->nlmsg_flags & NLM_F_DUMP) { | 574 | if (nlh->nlmsg_flags & NLM_F_DUMP) { |
575 | struct netlink_dump_control c = { | ||
576 | .dump = ops->dumpit, | ||
577 | .done = ops->done, | ||
578 | }; | ||
579 | |||
563 | if (ops->dumpit == NULL) | 580 | if (ops->dumpit == NULL) |
564 | return -EOPNOTSUPP; | 581 | return -EOPNOTSUPP; |
565 | 582 | ||
566 | genl_unlock(); | 583 | return netlink_dump_start(net->genl_sock, skb, nlh, &c); |
567 | { | ||
568 | struct netlink_dump_control c = { | ||
569 | .dump = ops->dumpit, | ||
570 | .done = ops->done, | ||
571 | }; | ||
572 | err = netlink_dump_start(net->genl_sock, skb, nlh, &c); | ||
573 | } | ||
574 | genl_lock(); | ||
575 | return err; | ||
576 | } | 584 | } |
577 | 585 | ||
578 | if (ops->doit == NULL) | 586 | if (ops->doit == NULL) |
579 | return -EOPNOTSUPP; | 587 | return -EOPNOTSUPP; |
580 | 588 | ||
581 | if (family->attrbuf) { | 589 | if (family->maxattr && family->parallel_ops) { |
582 | err = nlmsg_parse(nlh, hdrlen, family->attrbuf, family->maxattr, | 590 | attrbuf = kmalloc((family->maxattr+1) * |
591 | sizeof(struct nlattr *), GFP_KERNEL); | ||
592 | if (attrbuf == NULL) | ||
593 | return -ENOMEM; | ||
594 | } else | ||
595 | attrbuf = family->attrbuf; | ||
596 | |||
597 | if (attrbuf) { | ||
598 | err = nlmsg_parse(nlh, hdrlen, attrbuf, family->maxattr, | ||
583 | ops->policy); | 599 | ops->policy); |
584 | if (err < 0) | 600 | if (err < 0) |
585 | return err; | 601 | return err; |
@@ -590,7 +606,7 @@ static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) | |||
590 | info.nlhdr = nlh; | 606 | info.nlhdr = nlh; |
591 | info.genlhdr = nlmsg_data(nlh); | 607 | info.genlhdr = nlmsg_data(nlh); |
592 | info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN; | 608 | info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN; |
593 | info.attrs = family->attrbuf; | 609 | info.attrs = attrbuf; |
594 | genl_info_net_set(&info, net); | 610 | genl_info_net_set(&info, net); |
595 | memset(&info.user_ptr, 0, sizeof(info.user_ptr)); | 611 | memset(&info.user_ptr, 0, sizeof(info.user_ptr)); |
596 | 612 | ||
@@ -605,14 +621,37 @@ static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) | |||
605 | if (family->post_doit) | 621 | if (family->post_doit) |
606 | family->post_doit(ops, skb, &info); | 622 | family->post_doit(ops, skb, &info); |
607 | 623 | ||
624 | if (family->parallel_ops) | ||
625 | kfree(attrbuf); | ||
626 | |||
627 | return err; | ||
628 | } | ||
629 | |||
630 | static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) | ||
631 | { | ||
632 | struct genl_family *family; | ||
633 | int err; | ||
634 | |||
635 | family = genl_family_find_byid(nlh->nlmsg_type); | ||
636 | if (family == NULL) | ||
637 | return -ENOENT; | ||
638 | |||
639 | if (!family->parallel_ops) | ||
640 | genl_lock(); | ||
641 | |||
642 | err = genl_family_rcv_msg(family, skb, nlh); | ||
643 | |||
644 | if (!family->parallel_ops) | ||
645 | genl_unlock(); | ||
646 | |||
608 | return err; | 647 | return err; |
609 | } | 648 | } |
610 | 649 | ||
611 | static void genl_rcv(struct sk_buff *skb) | 650 | static void genl_rcv(struct sk_buff *skb) |
612 | { | 651 | { |
613 | genl_lock(); | 652 | down_read(&cb_lock); |
614 | netlink_rcv_skb(skb, &genl_rcv_msg); | 653 | netlink_rcv_skb(skb, &genl_rcv_msg); |
615 | genl_unlock(); | 654 | up_read(&cb_lock); |
616 | } | 655 | } |
617 | 656 | ||
618 | /************************************************************************** | 657 | /************************************************************************** |
@@ -918,7 +957,6 @@ static int __net_init genl_pernet_init(struct net *net) | |||
918 | { | 957 | { |
919 | struct netlink_kernel_cfg cfg = { | 958 | struct netlink_kernel_cfg cfg = { |
920 | .input = genl_rcv, | 959 | .input = genl_rcv, |
921 | .cb_mutex = &genl_mutex, | ||
922 | .flags = NL_CFG_F_NONROOT_RECV, | 960 | .flags = NL_CFG_F_NONROOT_RECV, |
923 | }; | 961 | }; |
924 | 962 | ||