diff options
author | Elad Raz <eladr@mellanox.com> | 2016-04-21 06:52:45 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2016-04-24 14:23:32 -0400 |
commit | 45ebcce56823d14d196dbdecd26783b3d5f464a6 (patch) | |
tree | 3fab543500b3d8ee7ccb7def33b957e2c97886f7 | |
parent | 6dd684c0feb207f30180570bad24264b922d9476 (diff) |
bridge: mdb: Marking port-group as offloaded
There is a race-condition when updating the mdb offload flag without using
the mulicast_lock. This reverts commit 9e8430f8d60d98 ("bridge: mdb:
Passing the port-group pointer to br_mdb module").
This patch marks offloaded MDB entry as "offload" by changing the port-
group flags and marks it as MDB_PG_FLAGS_OFFLOAD.
When switchdev PORT_MDB succeeded and adds a multicast group, a completion
callback is been invoked "br_mdb_complete". The completion function
locks the multicast_lock and finds the right net_bridge_port_group and
marks it as offloaded.
Fixes: 9e8430f8d60d98 ("bridge: mdb: Passing the port-group pointer to br_mdb module")
Reported-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: Elad Raz <eladr@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Reviewed-by: Ido Schimmel <idosch@mellanox.com>
Acked-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | net/bridge/br_mdb.c | 91 | ||||
-rw-r--r-- | net/bridge/br_multicast.c | 8 | ||||
-rw-r--r-- | net/bridge/br_private.h | 4 |
3 files changed, 71 insertions, 32 deletions
diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c index b258120e1145..7dbc80d01eb0 100644 --- a/net/bridge/br_mdb.c +++ b/net/bridge/br_mdb.c | |||
@@ -256,9 +256,45 @@ static inline size_t rtnl_mdb_nlmsg_size(void) | |||
256 | + nla_total_size(sizeof(struct br_mdb_entry)); | 256 | + nla_total_size(sizeof(struct br_mdb_entry)); |
257 | } | 257 | } |
258 | 258 | ||
259 | static void __br_mdb_notify(struct net_device *dev, struct br_mdb_entry *entry, | 259 | struct br_mdb_complete_info { |
260 | int type, struct net_bridge_port_group *pg) | 260 | struct net_bridge_port *port; |
261 | struct br_ip ip; | ||
262 | }; | ||
263 | |||
264 | static void br_mdb_complete(struct net_device *dev, int err, void *priv) | ||
261 | { | 265 | { |
266 | struct br_mdb_complete_info *data = priv; | ||
267 | struct net_bridge_port_group __rcu **pp; | ||
268 | struct net_bridge_port_group *p; | ||
269 | struct net_bridge_mdb_htable *mdb; | ||
270 | struct net_bridge_mdb_entry *mp; | ||
271 | struct net_bridge_port *port = data->port; | ||
272 | struct net_bridge *br = port->br; | ||
273 | |||
274 | if (err) | ||
275 | goto err; | ||
276 | |||
277 | spin_lock_bh(&br->multicast_lock); | ||
278 | mdb = mlock_dereference(br->mdb, br); | ||
279 | mp = br_mdb_ip_get(mdb, &data->ip); | ||
280 | if (!mp) | ||
281 | goto out; | ||
282 | for (pp = &mp->ports; (p = mlock_dereference(*pp, br)) != NULL; | ||
283 | pp = &p->next) { | ||
284 | if (p->port != port) | ||
285 | continue; | ||
286 | p->flags |= MDB_PG_FLAGS_OFFLOAD; | ||
287 | } | ||
288 | out: | ||
289 | spin_unlock_bh(&br->multicast_lock); | ||
290 | err: | ||
291 | kfree(priv); | ||
292 | } | ||
293 | |||
294 | static void __br_mdb_notify(struct net_device *dev, struct net_bridge_port *p, | ||
295 | struct br_mdb_entry *entry, int type) | ||
296 | { | ||
297 | struct br_mdb_complete_info *complete_info; | ||
262 | struct switchdev_obj_port_mdb mdb = { | 298 | struct switchdev_obj_port_mdb mdb = { |
263 | .obj = { | 299 | .obj = { |
264 | .id = SWITCHDEV_OBJ_ID_PORT_MDB, | 300 | .id = SWITCHDEV_OBJ_ID_PORT_MDB, |
@@ -281,9 +317,14 @@ static void __br_mdb_notify(struct net_device *dev, struct br_mdb_entry *entry, | |||
281 | 317 | ||
282 | mdb.obj.orig_dev = port_dev; | 318 | mdb.obj.orig_dev = port_dev; |
283 | if (port_dev && type == RTM_NEWMDB) { | 319 | if (port_dev && type == RTM_NEWMDB) { |
284 | err = switchdev_port_obj_add(port_dev, &mdb.obj); | 320 | complete_info = kmalloc(sizeof(*complete_info), GFP_ATOMIC); |
285 | if (!err && pg) | 321 | if (complete_info) { |
286 | pg->flags |= MDB_PG_FLAGS_OFFLOAD; | 322 | complete_info->port = p; |
323 | __mdb_entry_to_br_ip(entry, &complete_info->ip); | ||
324 | mdb.obj.complete_priv = complete_info; | ||
325 | mdb.obj.complete = br_mdb_complete; | ||
326 | switchdev_port_obj_add(port_dev, &mdb.obj); | ||
327 | } | ||
287 | } else if (port_dev && type == RTM_DELMDB) { | 328 | } else if (port_dev && type == RTM_DELMDB) { |
288 | switchdev_port_obj_del(port_dev, &mdb.obj); | 329 | switchdev_port_obj_del(port_dev, &mdb.obj); |
289 | } | 330 | } |
@@ -304,21 +345,21 @@ errout: | |||
304 | rtnl_set_sk_err(net, RTNLGRP_MDB, err); | 345 | rtnl_set_sk_err(net, RTNLGRP_MDB, err); |
305 | } | 346 | } |
306 | 347 | ||
307 | void br_mdb_notify(struct net_device *dev, struct net_bridge_port_group *pg, | 348 | void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port, |
308 | int type) | 349 | struct br_ip *group, int type, u8 flags) |
309 | { | 350 | { |
310 | struct br_mdb_entry entry; | 351 | struct br_mdb_entry entry; |
311 | 352 | ||
312 | memset(&entry, 0, sizeof(entry)); | 353 | memset(&entry, 0, sizeof(entry)); |
313 | entry.ifindex = pg->port->dev->ifindex; | 354 | entry.ifindex = port->dev->ifindex; |
314 | entry.addr.proto = pg->addr.proto; | 355 | entry.addr.proto = group->proto; |
315 | entry.addr.u.ip4 = pg->addr.u.ip4; | 356 | entry.addr.u.ip4 = group->u.ip4; |
316 | #if IS_ENABLED(CONFIG_IPV6) | 357 | #if IS_ENABLED(CONFIG_IPV6) |
317 | entry.addr.u.ip6 = pg->addr.u.ip6; | 358 | entry.addr.u.ip6 = group->u.ip6; |
318 | #endif | 359 | #endif |
319 | entry.vid = pg->addr.vid; | 360 | entry.vid = group->vid; |
320 | __mdb_entry_fill_flags(&entry, pg->flags); | 361 | __mdb_entry_fill_flags(&entry, flags); |
321 | __br_mdb_notify(dev, &entry, type, pg); | 362 | __br_mdb_notify(dev, port, &entry, type); |
322 | } | 363 | } |
323 | 364 | ||
324 | static int nlmsg_populate_rtr_fill(struct sk_buff *skb, | 365 | static int nlmsg_populate_rtr_fill(struct sk_buff *skb, |
@@ -463,8 +504,7 @@ static int br_mdb_parse(struct sk_buff *skb, struct nlmsghdr *nlh, | |||
463 | } | 504 | } |
464 | 505 | ||
465 | static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, | 506 | static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, |
466 | struct br_ip *group, unsigned char state, | 507 | struct br_ip *group, unsigned char state) |
467 | struct net_bridge_port_group **pg) | ||
468 | { | 508 | { |
469 | struct net_bridge_mdb_entry *mp; | 509 | struct net_bridge_mdb_entry *mp; |
470 | struct net_bridge_port_group *p; | 510 | struct net_bridge_port_group *p; |
@@ -495,7 +535,6 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, | |||
495 | if (unlikely(!p)) | 535 | if (unlikely(!p)) |
496 | return -ENOMEM; | 536 | return -ENOMEM; |
497 | rcu_assign_pointer(*pp, p); | 537 | rcu_assign_pointer(*pp, p); |
498 | *pg = p; | ||
499 | if (state == MDB_TEMPORARY) | 538 | if (state == MDB_TEMPORARY) |
500 | mod_timer(&p->timer, now + br->multicast_membership_interval); | 539 | mod_timer(&p->timer, now + br->multicast_membership_interval); |
501 | 540 | ||
@@ -503,8 +542,7 @@ static int br_mdb_add_group(struct net_bridge *br, struct net_bridge_port *port, | |||
503 | } | 542 | } |
504 | 543 | ||
505 | static int __br_mdb_add(struct net *net, struct net_bridge *br, | 544 | static int __br_mdb_add(struct net *net, struct net_bridge *br, |
506 | struct br_mdb_entry *entry, | 545 | struct br_mdb_entry *entry) |
507 | struct net_bridge_port_group **pg) | ||
508 | { | 546 | { |
509 | struct br_ip ip; | 547 | struct br_ip ip; |
510 | struct net_device *dev; | 548 | struct net_device *dev; |
@@ -525,7 +563,7 @@ static int __br_mdb_add(struct net *net, struct net_bridge *br, | |||
525 | __mdb_entry_to_br_ip(entry, &ip); | 563 | __mdb_entry_to_br_ip(entry, &ip); |
526 | 564 | ||
527 | spin_lock_bh(&br->multicast_lock); | 565 | spin_lock_bh(&br->multicast_lock); |
528 | ret = br_mdb_add_group(br, p, &ip, entry->state, pg); | 566 | ret = br_mdb_add_group(br, p, &ip, entry->state); |
529 | spin_unlock_bh(&br->multicast_lock); | 567 | spin_unlock_bh(&br->multicast_lock); |
530 | return ret; | 568 | return ret; |
531 | } | 569 | } |
@@ -533,7 +571,6 @@ static int __br_mdb_add(struct net *net, struct net_bridge *br, | |||
533 | static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh) | 571 | static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh) |
534 | { | 572 | { |
535 | struct net *net = sock_net(skb->sk); | 573 | struct net *net = sock_net(skb->sk); |
536 | struct net_bridge_port_group *pg; | ||
537 | struct net_bridge_vlan_group *vg; | 574 | struct net_bridge_vlan_group *vg; |
538 | struct net_device *dev, *pdev; | 575 | struct net_device *dev, *pdev; |
539 | struct br_mdb_entry *entry; | 576 | struct br_mdb_entry *entry; |
@@ -563,15 +600,15 @@ static int br_mdb_add(struct sk_buff *skb, struct nlmsghdr *nlh) | |||
563 | if (br_vlan_enabled(br) && vg && entry->vid == 0) { | 600 | if (br_vlan_enabled(br) && vg && entry->vid == 0) { |
564 | list_for_each_entry(v, &vg->vlan_list, vlist) { | 601 | list_for_each_entry(v, &vg->vlan_list, vlist) { |
565 | entry->vid = v->vid; | 602 | entry->vid = v->vid; |
566 | err = __br_mdb_add(net, br, entry, &pg); | 603 | err = __br_mdb_add(net, br, entry); |
567 | if (err) | 604 | if (err) |
568 | break; | 605 | break; |
569 | __br_mdb_notify(dev, entry, RTM_NEWMDB, pg); | 606 | __br_mdb_notify(dev, p, entry, RTM_NEWMDB); |
570 | } | 607 | } |
571 | } else { | 608 | } else { |
572 | err = __br_mdb_add(net, br, entry, &pg); | 609 | err = __br_mdb_add(net, br, entry); |
573 | if (!err) | 610 | if (!err) |
574 | __br_mdb_notify(dev, entry, RTM_NEWMDB, pg); | 611 | __br_mdb_notify(dev, p, entry, RTM_NEWMDB); |
575 | } | 612 | } |
576 | 613 | ||
577 | return err; | 614 | return err; |
@@ -659,12 +696,12 @@ static int br_mdb_del(struct sk_buff *skb, struct nlmsghdr *nlh) | |||
659 | entry->vid = v->vid; | 696 | entry->vid = v->vid; |
660 | err = __br_mdb_del(br, entry); | 697 | err = __br_mdb_del(br, entry); |
661 | if (!err) | 698 | if (!err) |
662 | __br_mdb_notify(dev, entry, RTM_DELMDB, NULL); | 699 | __br_mdb_notify(dev, p, entry, RTM_DELMDB); |
663 | } | 700 | } |
664 | } else { | 701 | } else { |
665 | err = __br_mdb_del(br, entry); | 702 | err = __br_mdb_del(br, entry); |
666 | if (!err) | 703 | if (!err) |
667 | __br_mdb_notify(dev, entry, RTM_DELMDB, NULL); | 704 | __br_mdb_notify(dev, p, entry, RTM_DELMDB); |
668 | } | 705 | } |
669 | 706 | ||
670 | return err; | 707 | return err; |
diff --git a/net/bridge/br_multicast.c b/net/bridge/br_multicast.c index a4c15df2b792..191ea66e4d92 100644 --- a/net/bridge/br_multicast.c +++ b/net/bridge/br_multicast.c | |||
@@ -283,7 +283,8 @@ static void br_multicast_del_pg(struct net_bridge *br, | |||
283 | rcu_assign_pointer(*pp, p->next); | 283 | rcu_assign_pointer(*pp, p->next); |
284 | hlist_del_init(&p->mglist); | 284 | hlist_del_init(&p->mglist); |
285 | del_timer(&p->timer); | 285 | del_timer(&p->timer); |
286 | br_mdb_notify(br->dev, p, RTM_DELMDB); | 286 | br_mdb_notify(br->dev, p->port, &pg->addr, RTM_DELMDB, |
287 | p->flags); | ||
287 | call_rcu_bh(&p->rcu, br_multicast_free_pg); | 288 | call_rcu_bh(&p->rcu, br_multicast_free_pg); |
288 | 289 | ||
289 | if (!mp->ports && !mp->mglist && | 290 | if (!mp->ports && !mp->mglist && |
@@ -705,7 +706,7 @@ static int br_multicast_add_group(struct net_bridge *br, | |||
705 | if (unlikely(!p)) | 706 | if (unlikely(!p)) |
706 | goto err; | 707 | goto err; |
707 | rcu_assign_pointer(*pp, p); | 708 | rcu_assign_pointer(*pp, p); |
708 | br_mdb_notify(br->dev, p, RTM_NEWMDB); | 709 | br_mdb_notify(br->dev, port, group, RTM_NEWMDB, 0); |
709 | 710 | ||
710 | found: | 711 | found: |
711 | mod_timer(&p->timer, now + br->multicast_membership_interval); | 712 | mod_timer(&p->timer, now + br->multicast_membership_interval); |
@@ -1461,7 +1462,8 @@ br_multicast_leave_group(struct net_bridge *br, | |||
1461 | hlist_del_init(&p->mglist); | 1462 | hlist_del_init(&p->mglist); |
1462 | del_timer(&p->timer); | 1463 | del_timer(&p->timer); |
1463 | call_rcu_bh(&p->rcu, br_multicast_free_pg); | 1464 | call_rcu_bh(&p->rcu, br_multicast_free_pg); |
1464 | br_mdb_notify(br->dev, p, RTM_DELMDB); | 1465 | br_mdb_notify(br->dev, port, group, RTM_DELMDB, |
1466 | p->flags); | ||
1465 | 1467 | ||
1466 | if (!mp->ports && !mp->mglist && | 1468 | if (!mp->ports && !mp->mglist && |
1467 | netif_running(br->dev)) | 1469 | netif_running(br->dev)) |
diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h index 1b5d145dfcbf..d9da857182ef 100644 --- a/net/bridge/br_private.h +++ b/net/bridge/br_private.h | |||
@@ -560,8 +560,8 @@ br_multicast_new_port_group(struct net_bridge_port *port, struct br_ip *group, | |||
560 | unsigned char flags); | 560 | unsigned char flags); |
561 | void br_mdb_init(void); | 561 | void br_mdb_init(void); |
562 | void br_mdb_uninit(void); | 562 | void br_mdb_uninit(void); |
563 | void br_mdb_notify(struct net_device *dev, struct net_bridge_port_group *pg, | 563 | void br_mdb_notify(struct net_device *dev, struct net_bridge_port *port, |
564 | int type); | 564 | struct br_ip *group, int type, u8 flags); |
565 | void br_rtr_notify(struct net_device *dev, struct net_bridge_port *port, | 565 | void br_rtr_notify(struct net_device *dev, struct net_bridge_port *port, |
566 | int type); | 566 | int type); |
567 | 567 | ||