diff options
author | Cong Wang <amwang@redhat.com> | 2012-12-11 17:23:08 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2012-12-12 13:02:30 -0500 |
commit | cfd567543590f71ca0af397437e2554f9756d750 (patch) | |
tree | b21b43cc137cb00386bd2d0d1533c9742224a050 /net/bridge | |
parent | 37a393bc4932d7bac360f40064aaafc01ab44901 (diff) |
bridge: add support of adding and deleting mdb entries
This patch implents adding/deleting mdb entries via netlink.
Currently all entries are temp, we probably need a flag to distinguish
permanent entries too.
Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: Stephen Hemminger <shemminger@vyatta.com>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Thomas Graf <tgraf@suug.ch>
Signed-off-by: Cong Wang <amwang@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/bridge')
-rw-r--r-- | net/bridge/br_mdb.c | 240 | ||||
-rw-r--r-- | net/bridge/br_multicast.c | 55 | ||||
-rw-r--r-- | net/bridge/br_private.h | 23 |
3 files changed, 289 insertions, 29 deletions
diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c index a8cfbf5f3c68..6f0a2eebcb27 100644 --- a/net/bridge/br_mdb.c +++ b/net/bridge/br_mdb.c | |||
@@ -4,6 +4,7 @@ | |||
4 | #include <linux/netdevice.h> | 4 | #include <linux/netdevice.h> |
5 | #include <linux/rculist.h> | 5 | #include <linux/rculist.h> |
6 | #include <linux/skbuff.h> | 6 | #include <linux/skbuff.h> |
7 | #include <linux/if_ether.h> | ||
7 | #include <net/ip.h> | 8 | #include <net/ip.h> |
8 | #include <net/netlink.h> | 9 | #include <net/netlink.h> |
9 | #if IS_ENABLED(CONFIG_IPV6) | 10 | #if IS_ENABLED(CONFIG_IPV6) |
@@ -235,7 +236,246 @@ void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port, | |||
235 | __br_mdb_notify(dev, &entry, type); | 236 | __br_mdb_notify(dev, &entry, type); |
236 | } | 237 | } |
237 | 238 | ||
239 | static bool is_valid_mdb_entry(struct br_mdb_entry *entry) | ||
240 | { | ||
241 | if (entry->ifindex == 0) | ||
242 | return false; | ||
243 | |||
244 | if (entry->addr.proto == htons(ETH_P_IP)) { | ||
245 | if (!ipv4_is_multicast(entry->addr.u.ip4)) | ||
246 | return false; | ||
247 | if (ipv4_is_local_multicast(entry->addr.u.ip4)) | ||
248 | return false; | ||
249 | #if IS_ENABLED(CONFIG_IPV6) | ||
250 | } else if (entry->addr.proto == htons(ETH_P_IPV6)) { | ||
251 | if (!ipv6_is_transient_multicast(&entry->addr.u.ip6)) | ||
252 | return false; | ||
253 | #endif | ||
254 | } else | ||
255 | return false; | ||
256 | |||
257 | return true; | ||
258 | } | ||
259 | |||
260 | static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh, | ||
261 | struct net_device **pdev, struct br_mdb_entry **pentry) | ||
262 | { | ||
263 | struct net *net = sock_net(skb->sk); | ||
264 | struct br_mdb_entry *entry; | ||
265 | struct br_port_msg *bpm; | ||
266 | struct nlattr *tb[MDBA_SET_ENTRY_MAX+1]; | ||
267 | struct net_device *dev; | ||
268 | int err; | ||
269 | |||
270 | if (!capable(CAP_NET_ADMIN)) | ||
271 | return -EPERM; | ||
272 | |||
273 | err = nlmsg_parse(nlh, sizeof(*bpm), tb, MDBA_SET_ENTRY, NULL); | ||
274 | if (err < 0) | ||
275 | return err; | ||
276 | |||
277 | bpm = nlmsg_data(nlh); | ||
278 | if (bpm->ifindex == 0) { | ||
279 | pr_info("PF_BRIDGE: br_mdb_parse() with invalid ifindex\n"); | ||
280 | return -EINVAL; | ||
281 | } | ||
282 | |||
283 | dev = __dev_get_by_index(net, bpm->ifindex); | ||
284 | if (dev == NULL) { | ||
285 | pr_info("PF_BRIDGE: br_mdb_parse() with unknown ifindex\n"); | ||
286 | return -ENODEV; | ||
287 | } | ||
288 | |||
289 | if (!(dev->priv_flags & IFF_EBRIDGE)) { | ||
290 | pr_info("PF_BRIDGE: br_mdb_parse() with non-bridge\n"); | ||
291 | return -EOPNOTSUPP; | ||
292 | } | ||
293 | |||
294 | *pdev = dev; | ||
295 | |||
296 | if (!tb[MDBA_SET_ENTRY] || | ||
297 | nla_len(tb[MDBA_SET_ENTRY]) != sizeof(struct br_mdb_entry)) { | ||
298 | pr_info("PF_BRIDGE: br_mdb_parse() with invalid attr\n"); | ||
299 | return -EINVAL; | ||
300 | } | ||
301 | |||
302 | entry = nla_data(tb[MDBA_SET_ENTRY]); | ||
303 | if (!is_valid_mdb_entry(entry)) { | ||
304 | pr_info("PF_BRIDGE: br_mdb_parse() with invalid entry\n"); | ||
305 | return -EINVAL; | ||
306 | } | ||
307 | |||
308 | *pentry = entry; | ||
309 | return 0; | ||
310 | } | ||
311 | |||
312 | static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, | ||
313 | struct br_ip *group) | ||
314 | { | ||
315 | struct net_bridge_mdb_entry *mp; | ||
316 | struct net_bridge_port_group *p; | ||
317 | struct net_bridge_port_group __rcu **pp; | ||
318 | struct net_bridge_mdb_htable *mdb; | ||
319 | int err; | ||
320 | |||
321 | mdb = mlock_dereference(br->mdb, br); | ||
322 | mp = br_mdb_ip_get(mdb, group); | ||
323 | if (!mp) { | ||
324 | mp = br_multicast_new_group(br, port, group); | ||
325 | err = PTR_ERR(mp); | ||
326 | if (IS_ERR(mp)) | ||
327 | return err; | ||
328 | } | ||
329 | |||
330 | for (pp = &mp->ports; | ||
331 | (p = mlock_dereference(*pp, br)) != NULL; | ||
332 | pp = &p->next) { | ||
333 | if (p->port == port) | ||
334 | return -EEXIST; | ||
335 | if ((unsigned long)p->port < (unsigned long)port) | ||
336 | break; | ||
337 | } | ||
338 | |||
339 | p = br_multicast_new_port_group(port, group, *pp); | ||
340 | if (unlikely(!p)) | ||
341 | return -ENOMEM; | ||
342 | rcu_assign_pointer(*pp, p); | ||
343 | |||
344 | br_mdb_notify(br->dev, port, group, RTM_NEWMDB); | ||
345 | return 0; | ||
346 | } | ||
347 | |||
348 | static int __br_mdb_add(struct net *net, struct net_bridge *br, | ||
349 | struct br_mdb_entry *entry) | ||
350 | { | ||
351 | struct br_ip ip; | ||
352 | struct net_device *dev; | ||
353 | struct net_bridge_port *p; | ||
354 | int ret; | ||
355 | |||
356 | if (!netif_running(br->dev) || br->multicast_disabled) | ||
357 | return -EINVAL; | ||
358 | |||
359 | dev = __dev_get_by_index(net, entry->ifindex); | ||
360 | if (!dev) | ||
361 | return -ENODEV; | ||
362 | |||
363 | p = br_port_get_rtnl(dev); | ||
364 | if (!p || p->br != br || p->state == BR_STATE_DISABLED) | ||
365 | return -EINVAL; | ||
366 | |||
367 | ip.proto = entry->addr.proto; | ||
368 | if (ip.proto == htons(ETH_P_IP)) | ||
369 | ip.u.ip4 = entry->addr.u.ip4; | ||
370 | #if IS_ENABLED(CONFIG_IPV6) | ||
371 | else | ||
372 | ip.u.ip6 = entry->addr.u.ip6; | ||
373 | #endif | ||
374 | |||
375 | spin_lock_bh(&br->multicast_lock); | ||
376 | ret = br_mdb_add_group(br, p, &ip); | ||
377 | spin_unlock_bh(&br->multicast_lock); | ||
378 | return ret; | ||
379 | } | ||
380 | |||
381 | static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) | ||
382 | { | ||
383 | struct net *net = sock_net(skb->sk); | ||
384 | struct br_mdb_entry *entry; | ||
385 | struct net_device *dev; | ||
386 | struct net_bridge *br; | ||
387 | int err; | ||
388 | |||
389 | err = br_mdb_parse(skb, nlh, &dev, &entry); | ||
390 | if (err < 0) | ||
391 | return err; | ||
392 | |||
393 | br = netdev_priv(dev); | ||
394 | |||
395 | err = __br_mdb_add(net, br, entry); | ||
396 | if (!err) | ||
397 | __br_mdb_notify(dev, entry, RTM_NEWMDB); | ||
398 | return err; | ||
399 | } | ||
400 | |||
401 | static int __br_mdb_del(struct net_bridge *br, struct br_mdb_entry *entry) | ||
402 | { | ||
403 | struct net_bridge_mdb_htable *mdb; | ||
404 | struct net_bridge_mdb_entry *mp; | ||
405 | struct net_bridge_port_group *p; | ||
406 | struct net_bridge_port_group __rcu **pp; | ||
407 | struct br_ip ip; | ||
408 | int err = -EINVAL; | ||
409 | |||
410 | if (!netif_running(br->dev) || br->multicast_disabled) | ||
411 | return -EINVAL; | ||
412 | |||
413 | if (timer_pending(&br->multicast_querier_timer)) | ||
414 | return -EBUSY; | ||
415 | |||
416 | ip.proto = entry->addr.proto; | ||
417 | if (ip.proto == htons(ETH_P_IP)) | ||
418 | ip.u.ip4 = entry->addr.u.ip4; | ||
419 | #if IS_ENABLED(CONFIG_IPV6) | ||
420 | else | ||
421 | ip.u.ip6 = entry->addr.u.ip6; | ||
422 | #endif | ||
423 | |||
424 | spin_lock_bh(&br->multicast_lock); | ||
425 | mdb = mlock_dereference(br->mdb, br); | ||
426 | |||
427 | mp = br_mdb_ip_get(mdb, &ip); | ||
428 | if (!mp) | ||
429 | goto unlock; | ||
430 | |||
431 | for (pp = &mp->ports; | ||
432 | (p = mlock_dereference(*pp, br)) != NULL; | ||
433 | pp = &p->next) { | ||
434 | if (!p->port || p->port->dev->ifindex != entry->ifindex) | ||
435 | continue; | ||
436 | |||
437 | if (p->port->state == BR_STATE_DISABLED) | ||
438 | goto unlock; | ||
439 | |||
440 | rcu_assign_pointer(*pp, p->next); | ||
441 | hlist_del_init(&p->mglist); | ||
442 | del_timer(&p->timer); | ||
443 | call_rcu_bh(&p->rcu, br_multicast_free_pg); | ||
444 | err = 0; | ||
445 | |||
446 | if (!mp->ports && !mp->mglist && | ||
447 | netif_running(br->dev)) | ||
448 | mod_timer(&mp->timer, jiffies); | ||
449 | break; | ||
450 | } | ||
451 | |||
452 | unlock: | ||
453 | spin_unlock_bh(&br->multicast_lock); | ||
454 | return err; | ||
455 | } | ||
456 | |||
457 | static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg) | ||
458 | { | ||
459 | struct net_device *dev; | ||
460 | struct br_mdb_entry *entry; | ||
461 | struct net_bridge *br; | ||
462 | int err; | ||
463 | |||
464 | err = br_mdb_parse(skb, nlh, &dev, &entry); | ||
465 | if (err < 0) | ||
466 | return err; | ||
467 | |||
468 | br = netdev_priv(dev); | ||
469 | |||
470 | err = __br_mdb_del(br, entry); | ||
471 | if (!err) | ||
472 | __br_mdb_notify(dev, entry, RTM_DELMDB); | ||
473 | return err; | ||
474 | } | ||
475 | |||
238 | void br_mdb_init(void) | 476 | void br_mdb_init(void) |
239 | { | 477 | { |
240 | rtnl_register(PF_BRIDGE, RTM_GETMDB, NULL, br_mdb_dump, NULL); | 478 | rtnl_register(PF_BRIDGE, RTM_GETMDB, NULL, br_mdb_dump, NULL); |
479 | rtnl_register(PF_BRIDGE, RTM_NEWMDB, br_mdb_add, NULL, NULL); | ||
480 | rtnl_register(PF_BRIDGE, RTM_DELMDB, br_mdb_del, NULL, NULL); | ||
241 | } | 481 | } |
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index d929586ce39e..977c3ee02e65 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c | |||
@@ -27,27 +27,14 @@ | |||
27 | #if IS_ENABLED(CONFIG_IPV6) | 27 | #if IS_ENABLED(CONFIG_IPV6) |
28 | #include <net/ipv6.h> | 28 | #include <net/ipv6.h> |
29 | #include <net/mld.h> | 29 | #include <net/mld.h> |
30 | #include <net/addrconf.h> | ||
31 | #include <net/ip6_checksum.h> | 30 | #include <net/ip6_checksum.h> |
32 | #endif | 31 | #endif |
33 | 32 | ||
34 | #include "br_private.h" | 33 | #include "br_private.h" |
35 | 34 | ||
36 | #define mlock_dereference(X, br) \ | ||
37 | rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock)) | ||
38 | |||
39 | static void br_multicast_start_querier(struct net_bridge *br); | 35 | static void br_multicast_start_querier(struct net_bridge *br); |
40 | unsigned int br_mdb_rehash_seq; | 36 | unsigned int br_mdb_rehash_seq; |
41 | 37 | ||
42 | #if IS_ENABLED(CONFIG_IPV6) | ||
43 | static inline int ipv6_is_transient_multicast(const struct in6_addr *addr) | ||
44 | { | ||
45 | if (ipv6_addr_is_multicast(addr) && IPV6_ADDR_MC_FLAG_TRANSIENT(addr)) | ||
46 | return 1; | ||
47 | return 0; | ||
48 | } | ||
49 | #endif | ||
50 | |||
51 | static inline int br_ip_equal(const struct br_ip *a, const struct br_ip *b) | 38 | static inline int br_ip_equal(const struct br_ip *a, const struct br_ip *b) |
52 | { | 39 | { |
53 | if (a->proto != b->proto) | 40 | if (a->proto != b->proto) |
@@ -104,8 +91,8 @@ static struct net_bridge_mdb_entry *__br_mdb_ip_get( | |||
104 | return NULL; | 91 | return NULL; |
105 | } | 92 | } |
106 | 93 | ||
107 | static struct net_bridge_mdb_entry *br_mdb_ip_get( | 94 | struct net_bridge_mdb_entry *br_mdb_ip_get(struct net_bridge_mdb_htable *mdb, |
108 | struct net_bridge_mdb_htable *mdb, struct br_ip *dst) | 95 | struct br_ip *dst) |
109 | { | 96 | { |
110 | if (!mdb) | 97 | if (!mdb) |
111 | return NULL; | 98 | return NULL; |
@@ -208,7 +195,7 @@ static int br_mdb_copy(struct net_bridge_mdb_htable *new, | |||
208 | return maxlen > elasticity ? -EINVAL : 0; | 195 | return maxlen > elasticity ? -EINVAL : 0; |
209 | } | 196 | } |
210 | 197 | ||
211 | static void br_multicast_free_pg(struct rcu_head *head) | 198 | void br_multicast_free_pg(struct rcu_head *head) |
212 | { | 199 | { |
213 | struct net_bridge_port_group *p = | 200 | struct net_bridge_port_group *p = |
214 | container_of(head, struct net_bridge_port_group, rcu); | 201 | container_of(head, struct net_bridge_port_group, rcu); |
@@ -584,9 +571,8 @@ err: | |||
584 | return mp; | 571 | return mp; |
585 | } | 572 | } |
586 | 573 | ||
587 | static struct net_bridge_mdb_entry *br_multicast_new_group( | 574 | struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br, |
588 | struct net_bridge *br, struct net_bridge_port *port, | 575 | struct net_bridge_port *port, struct br_ip *group) |
589 | struct br_ip *group) | ||
590 | { | 576 | { |
591 | struct net_bridge_mdb_htable *mdb; | 577 | struct net_bridge_mdb_htable *mdb; |
592 | struct net_bridge_mdb_entry *mp; | 578 | struct net_bridge_mdb_entry *mp; |
@@ -633,6 +619,26 @@ out: | |||
633 | return mp; | 619 | return mp; |
634 | } | 620 | } |
635 | 621 | ||
622 | struct net_bridge_port_group *br_multicast_new_port_group( | ||
623 | struct net_bridge_port *port, | ||
624 | struct br_ip *group, | ||
625 | struct net_bridge_port_group *next) | ||
626 | { | ||
627 | struct net_bridge_port_group *p; | ||
628 | |||
629 | p = kzalloc(sizeof(*p), GFP_ATOMIC); | ||
630 | if (unlikely(!p)) | ||
631 | return NULL; | ||
632 | |||
633 | p->addr = *group; | ||
634 | p->port = port; | ||
635 | p->next = next; | ||
636 | hlist_add_head(&p->mglist, &port->mglist); | ||
637 | setup_timer(&p->timer, br_multicast_port_group_expired, | ||
638 | (unsigned long)p); | ||
639 | return p; | ||
640 | } | ||
641 | |||
636 | static int br_multicast_add_group(struct net_bridge *br, | 642 | static int br_multicast_add_group(struct net_bridge *br, |
637 | struct net_bridge_port *port, | 643 | struct net_bridge_port *port, |
638 | struct br_ip *group) | 644 | struct br_ip *group) |
@@ -668,18 +674,9 @@ static int br_multicast_add_group(struct net_bridge *br, | |||
668 | break; | 674 | break; |
669 | } | 675 | } |
670 | 676 | ||
671 | p = kzalloc(sizeof(*p), GFP_ATOMIC); | 677 | p = br_multicast_new_port_group(port, group, *pp); |
672 | err = -ENOMEM; | ||
673 | if (unlikely(!p)) | 678 | if (unlikely(!p)) |
674 | goto err; | 679 | goto err; |
675 | |||
676 | p->addr = *group; | ||
677 | p->port = port; | ||
678 | p->next = *pp; | ||
679 | hlist_add_head(&p->mglist, &port->mglist); | ||
680 | setup_timer(&p->timer, br_multicast_port_group_expired, | ||
681 | (unsigned long)p); | ||
682 | |||
683 | rcu_assign_pointer(*pp, p); | 680 | rcu_assign_pointer(*pp, p); |
684 | br_mdb_notify(br->dev, port, group, RTM_NEWMDB); | 681 | br_mdb_notify(br->dev, port, group, RTM_NEWMDB); |
685 | 682 | ||
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 2807c7680c38..f21a739a6186 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h | |||
@@ -434,10 +434,33 @@ extern int br_multicast_set_port_router(struct net_bridge_port *p, | |||
434 | extern int br_multicast_toggle(struct net_bridge *br, unsigned long val); | 434 | extern int br_multicast_toggle(struct net_bridge *br, unsigned long val); |
435 | extern int br_multicast_set_querier(struct net_bridge *br, unsigned long val); | 435 | extern int br_multicast_set_querier(struct net_bridge *br, unsigned long val); |
436 | extern int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val); | 436 | extern int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val); |
437 | extern struct net_bridge_mdb_entry *br_mdb_ip_get( | ||
438 | struct net_bridge_mdb_htable *mdb, | ||
439 | struct br_ip *dst); | ||
440 | extern struct net_bridge_mdb_entry *br_multicast_new_group(struct net_bridge *br, | ||
441 | struct net_bridge_port *port, struct br_ip *group); | ||
442 | extern void br_multicast_free_pg(struct rcu_head *head); | ||
443 | extern struct net_bridge_port_group *br_multicast_new_port_group( | ||
444 | struct net_bridge_port *port, | ||
445 | struct br_ip *group, | ||
446 | struct net_bridge_port_group *next); | ||
437 | extern void br_mdb_init(void); | 447 | extern void br_mdb_init(void); |
438 | extern void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port, | 448 | extern void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port, |
439 | struct br_ip *group, int type); | 449 | struct br_ip *group, int type); |
440 | 450 | ||
451 | #define mlock_dereference(X, br) \ | ||
452 | rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock)) | ||
453 | |||
454 | #if IS_ENABLED(CONFIG_IPV6) | ||
455 | #include <net/addrconf.h> | ||
456 | static inline int ipv6_is_transient_multicast(const struct in6_addr *addr) | ||
457 | { | ||
458 | if (ipv6_addr_is_multicast(addr) && IPV6_ADDR_MC_FLAG_TRANSIENT(addr)) | ||
459 | return 1; | ||
460 | return 0; | ||
461 | } | ||
462 | #endif | ||
463 | |||
441 | static inline bool br_multicast_is_router(struct net_bridge *br) | 464 | static inline bool br_multicast_is_router(struct net_bridge *br) |
442 | { | 465 | { |
443 | return br->multicast_router == 2 || | 466 | return br->multicast_router == 2 || |