diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2007-07-18 18:46:06 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2007-07-18 18:46:06 -0400 |
commit | b4ff4f0419ae5db83553fab79d03a89c10d540a8 (patch) | |
tree | 17695af46692c31ec1f33ffb68bc686064227913 | |
parent | c81de6addb913423acef6e692fd70688180ab5dd (diff) |
[NETLINK]: allocate group bitmaps dynamically
Allow changing the number of groups for a netlink family
after it has been created, use RCU to protect the listeners
bitmap keeping netlink_has_listeners() lock-free.
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Acked-by: Patrick McHardy <kaber@trash.net>
Acked-by: Jamal Hadi Salim <hadi@cyberus.ca>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/netlink.h | 1 | ||||
-rw-r--r-- | net/netlink/af_netlink.c | 106 |
2 files changed, 83 insertions, 24 deletions
diff --git a/include/linux/netlink.h b/include/linux/netlink.h index 2e23353c28a5..b971ddd24090 100644 --- a/include/linux/netlink.h +++ b/include/linux/netlink.h | |||
@@ -161,6 +161,7 @@ extern struct sock *netlink_kernel_create(int unit, unsigned int groups, | |||
161 | void (*input)(struct sock *sk, int len), | 161 | void (*input)(struct sock *sk, int len), |
162 | struct mutex *cb_mutex, | 162 | struct mutex *cb_mutex, |
163 | struct module *module); | 163 | struct module *module); |
164 | extern int netlink_change_ngroups(struct sock *sk, unsigned int groups); | ||
164 | extern void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err); | 165 | extern void netlink_ack(struct sk_buff *in_skb, struct nlmsghdr *nlh, int err); |
165 | extern int netlink_has_listeners(struct sock *sk, unsigned int group); | 166 | extern int netlink_has_listeners(struct sock *sk, unsigned int group); |
166 | extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 pid, int nonblock); | 167 | extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 pid, int nonblock); |
diff --git a/net/netlink/af_netlink.c b/net/netlink/af_netlink.c index 641cfbc278d8..c386eaf6ad5b 100644 --- a/net/netlink/af_netlink.c +++ b/net/netlink/af_netlink.c | |||
@@ -62,6 +62,7 @@ | |||
62 | #include <net/netlink.h> | 62 | #include <net/netlink.h> |
63 | 63 | ||
64 | #define NLGRPSZ(x) (ALIGN(x, sizeof(unsigned long) * 8) / 8) | 64 | #define NLGRPSZ(x) (ALIGN(x, sizeof(unsigned long) * 8) / 8) |
65 | #define NLGRPLONGS(x) (NLGRPSZ(x)/sizeof(unsigned long)) | ||
65 | 66 | ||
66 | struct netlink_sock { | 67 | struct netlink_sock { |
67 | /* struct sock has to be the first member of netlink_sock */ | 68 | /* struct sock has to be the first member of netlink_sock */ |
@@ -314,10 +315,12 @@ netlink_update_listeners(struct sock *sk) | |||
314 | unsigned long mask; | 315 | unsigned long mask; |
315 | unsigned int i; | 316 | unsigned int i; |
316 | 317 | ||
317 | for (i = 0; i < NLGRPSZ(tbl->groups)/sizeof(unsigned long); i++) { | 318 | for (i = 0; i < NLGRPLONGS(tbl->groups); i++) { |
318 | mask = 0; | 319 | mask = 0; |
319 | sk_for_each_bound(sk, node, &tbl->mc_list) | 320 | sk_for_each_bound(sk, node, &tbl->mc_list) { |
320 | mask |= nlk_sk(sk)->groups[i]; | 321 | if (i < NLGRPLONGS(nlk_sk(sk)->ngroups)) |
322 | mask |= nlk_sk(sk)->groups[i]; | ||
323 | } | ||
321 | tbl->listeners[i] = mask; | 324 | tbl->listeners[i] = mask; |
322 | } | 325 | } |
323 | /* this function is only called with the netlink table "grabbed", which | 326 | /* this function is only called with the netlink table "grabbed", which |
@@ -555,26 +558,37 @@ netlink_update_subscriptions(struct sock *sk, unsigned int subscriptions) | |||
555 | nlk->subscriptions = subscriptions; | 558 | nlk->subscriptions = subscriptions; |
556 | } | 559 | } |
557 | 560 | ||
558 | static int netlink_alloc_groups(struct sock *sk) | 561 | static int netlink_realloc_groups(struct sock *sk) |
559 | { | 562 | { |
560 | struct netlink_sock *nlk = nlk_sk(sk); | 563 | struct netlink_sock *nlk = nlk_sk(sk); |
561 | unsigned int groups; | 564 | unsigned int groups; |
565 | unsigned long *new_groups; | ||
562 | int err = 0; | 566 | int err = 0; |
563 | 567 | ||
564 | netlink_lock_table(); | 568 | netlink_table_grab(); |
569 | |||
565 | groups = nl_table[sk->sk_protocol].groups; | 570 | groups = nl_table[sk->sk_protocol].groups; |
566 | if (!nl_table[sk->sk_protocol].registered) | 571 | if (!nl_table[sk->sk_protocol].registered) { |
567 | err = -ENOENT; | 572 | err = -ENOENT; |
568 | netlink_unlock_table(); | 573 | goto out_unlock; |
574 | } | ||
569 | 575 | ||
570 | if (err) | 576 | if (nlk->ngroups >= groups) |
571 | return err; | 577 | goto out_unlock; |
572 | 578 | ||
573 | nlk->groups = kzalloc(NLGRPSZ(groups), GFP_KERNEL); | 579 | new_groups = krealloc(nlk->groups, NLGRPSZ(groups), GFP_ATOMIC); |
574 | if (nlk->groups == NULL) | 580 | if (new_groups == NULL) { |
575 | return -ENOMEM; | 581 | err = -ENOMEM; |
582 | goto out_unlock; | ||
583 | } | ||
584 | memset((char*)new_groups + NLGRPSZ(nlk->ngroups), 0, | ||
585 | NLGRPSZ(groups) - NLGRPSZ(nlk->ngroups)); | ||
586 | |||
587 | nlk->groups = new_groups; | ||
576 | nlk->ngroups = groups; | 588 | nlk->ngroups = groups; |
577 | return 0; | 589 | out_unlock: |
590 | netlink_table_ungrab(); | ||
591 | return err; | ||
578 | } | 592 | } |
579 | 593 | ||
580 | static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len) | 594 | static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len) |
@@ -591,11 +605,9 @@ static int netlink_bind(struct socket *sock, struct sockaddr *addr, int addr_len | |||
591 | if (nladdr->nl_groups) { | 605 | if (nladdr->nl_groups) { |
592 | if (!netlink_capable(sock, NL_NONROOT_RECV)) | 606 | if (!netlink_capable(sock, NL_NONROOT_RECV)) |
593 | return -EPERM; | 607 | return -EPERM; |
594 | if (nlk->groups == NULL) { | 608 | err = netlink_realloc_groups(sk); |
595 | err = netlink_alloc_groups(sk); | 609 | if (err) |
596 | if (err) | 610 | return err; |
597 | return err; | ||
598 | } | ||
599 | } | 611 | } |
600 | 612 | ||
601 | if (nlk->pid) { | 613 | if (nlk->pid) { |
@@ -839,10 +851,18 @@ retry: | |||
839 | int netlink_has_listeners(struct sock *sk, unsigned int group) | 851 | int netlink_has_listeners(struct sock *sk, unsigned int group) |
840 | { | 852 | { |
841 | int res = 0; | 853 | int res = 0; |
854 | unsigned long *listeners; | ||
842 | 855 | ||
843 | BUG_ON(!(nlk_sk(sk)->flags & NETLINK_KERNEL_SOCKET)); | 856 | BUG_ON(!(nlk_sk(sk)->flags & NETLINK_KERNEL_SOCKET)); |
857 | |||
858 | rcu_read_lock(); | ||
859 | listeners = rcu_dereference(nl_table[sk->sk_protocol].listeners); | ||
860 | |||
844 | if (group - 1 < nl_table[sk->sk_protocol].groups) | 861 | if (group - 1 < nl_table[sk->sk_protocol].groups) |
845 | res = test_bit(group - 1, nl_table[sk->sk_protocol].listeners); | 862 | res = test_bit(group - 1, listeners); |
863 | |||
864 | rcu_read_unlock(); | ||
865 | |||
846 | return res; | 866 | return res; |
847 | } | 867 | } |
848 | EXPORT_SYMBOL_GPL(netlink_has_listeners); | 868 | EXPORT_SYMBOL_GPL(netlink_has_listeners); |
@@ -1037,11 +1057,9 @@ static int netlink_setsockopt(struct socket *sock, int level, int optname, | |||
1037 | 1057 | ||
1038 | if (!netlink_capable(sock, NL_NONROOT_RECV)) | 1058 | if (!netlink_capable(sock, NL_NONROOT_RECV)) |
1039 | return -EPERM; | 1059 | return -EPERM; |
1040 | if (nlk->groups == NULL) { | 1060 | err = netlink_realloc_groups(sk); |
1041 | err = netlink_alloc_groups(sk); | 1061 | if (err) |
1042 | if (err) | 1062 | return err; |
1043 | return err; | ||
1044 | } | ||
1045 | if (!val || val - 1 >= nlk->ngroups) | 1063 | if (!val || val - 1 >= nlk->ngroups) |
1046 | return -EINVAL; | 1064 | return -EINVAL; |
1047 | netlink_table_grab(); | 1065 | netlink_table_grab(); |
@@ -1328,6 +1346,46 @@ out_sock_release: | |||
1328 | return NULL; | 1346 | return NULL; |
1329 | } | 1347 | } |
1330 | 1348 | ||
1349 | /** | ||
1350 | * netlink_change_ngroups - change number of multicast groups | ||
1351 | * | ||
1352 | * This changes the number of multicast groups that are available | ||
1353 | * on a certain netlink family. Note that it is not possible to | ||
1354 | * change the number of groups to below 32. | ||
1355 | * | ||
1356 | * @sk: The kernel netlink socket, as returned by netlink_kernel_create(). | ||
1357 | * @groups: The new number of groups. | ||
1358 | */ | ||
1359 | int netlink_change_ngroups(struct sock *sk, unsigned int groups) | ||
1360 | { | ||
1361 | unsigned long *listeners, *old = NULL; | ||
1362 | struct netlink_table *tbl = &nl_table[sk->sk_protocol]; | ||
1363 | int err = 0; | ||
1364 | |||
1365 | if (groups < 32) | ||
1366 | groups = 32; | ||
1367 | |||
1368 | netlink_table_grab(); | ||
1369 | if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) { | ||
1370 | listeners = kzalloc(NLGRPSZ(groups), GFP_ATOMIC); | ||
1371 | if (!listeners) { | ||
1372 | err = -ENOMEM; | ||
1373 | goto out_ungrab; | ||
1374 | } | ||
1375 | old = tbl->listeners; | ||
1376 | memcpy(listeners, old, NLGRPSZ(tbl->groups)); | ||
1377 | rcu_assign_pointer(tbl->listeners, listeners); | ||
1378 | } | ||
1379 | tbl->groups = groups; | ||
1380 | |||
1381 | out_ungrab: | ||
1382 | netlink_table_ungrab(); | ||
1383 | synchronize_rcu(); | ||
1384 | kfree(old); | ||
1385 | return err; | ||
1386 | } | ||
1387 | EXPORT_SYMBOL(netlink_change_ngroups); | ||
1388 | |||
1331 | void netlink_set_nonroot(int protocol, unsigned int flags) | 1389 | void netlink_set_nonroot(int protocol, unsigned int flags) |
1332 | { | 1390 | { |
1333 | if ((unsigned int)protocol < MAX_LINKS) | 1391 | if ((unsigned int)protocol < MAX_LINKS) |