mirror of
https://github.com/torvalds/linux.git
synced 2024-11-11 14:42:24 +00:00
9fe8bcec0d
When a new static FDB is added to the bridge a notification is sent to the driver for offload. In case of successful offload the driver should notify the bridge back, which in turn should mark the FDB as offloaded. Currently, externally learned is equivalent for being offloaded which is not correct due to the fact that FDBs which are added from user-space are also marked as externally learned. In order to specify if an FDB was successfully offloaded a new flag is introduced. Signed-off-by: Arkadi Sharshevsky <arkadis@mellanox.com> Reviewed-by: Ido Schimmel <idosch@mellanox.com> Reviewed-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com> Signed-off-by: Jiri Pirko <jiri@mellanox.com> Signed-off-by: David S. Miller <davem@davemloft.net>
285 lines
6.1 KiB
C
285 lines
6.1 KiB
C
/*
|
|
* Generic parts
|
|
* Linux ethernet bridge
|
|
*
|
|
* Authors:
|
|
* Lennert Buytenhek <buytenh@gnu.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version
|
|
* 2 of the License, or (at your option) any later version.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/init.h>
|
|
#include <linux/llc.h>
|
|
#include <net/llc.h>
|
|
#include <net/stp.h>
|
|
#include <net/switchdev.h>
|
|
|
|
#include "br_private.h"
|
|
|
|
/*
|
|
* Handle changes in state of network devices enslaved to a bridge.
|
|
*
|
|
* Note: don't care about up/down if bridge itself is down, because
|
|
* port state is checked when bridge is brought up.
|
|
*/
|
|
static int br_device_event(struct notifier_block *unused, unsigned long event, void *ptr)
|
|
{
|
|
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
|
|
struct net_bridge_port *p;
|
|
struct net_bridge *br;
|
|
bool changed_addr;
|
|
int err;
|
|
|
|
/* register of bridge completed, add sysfs entries */
|
|
if ((dev->priv_flags & IFF_EBRIDGE) && event == NETDEV_REGISTER) {
|
|
br_sysfs_addbr(dev);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
/* not a port of a bridge */
|
|
p = br_port_get_rtnl(dev);
|
|
if (!p)
|
|
return NOTIFY_DONE;
|
|
|
|
br = p->br;
|
|
|
|
switch (event) {
|
|
case NETDEV_CHANGEMTU:
|
|
dev_set_mtu(br->dev, br_min_mtu(br));
|
|
break;
|
|
|
|
case NETDEV_CHANGEADDR:
|
|
spin_lock_bh(&br->lock);
|
|
br_fdb_changeaddr(p, dev->dev_addr);
|
|
changed_addr = br_stp_recalculate_bridge_id(br);
|
|
spin_unlock_bh(&br->lock);
|
|
|
|
if (changed_addr)
|
|
call_netdevice_notifiers(NETDEV_CHANGEADDR, br->dev);
|
|
|
|
break;
|
|
|
|
case NETDEV_CHANGE:
|
|
br_port_carrier_check(p);
|
|
break;
|
|
|
|
case NETDEV_FEAT_CHANGE:
|
|
netdev_update_features(br->dev);
|
|
break;
|
|
|
|
case NETDEV_DOWN:
|
|
spin_lock_bh(&br->lock);
|
|
if (br->dev->flags & IFF_UP)
|
|
br_stp_disable_port(p);
|
|
spin_unlock_bh(&br->lock);
|
|
break;
|
|
|
|
case NETDEV_UP:
|
|
if (netif_running(br->dev) && netif_oper_up(dev)) {
|
|
spin_lock_bh(&br->lock);
|
|
br_stp_enable_port(p);
|
|
spin_unlock_bh(&br->lock);
|
|
}
|
|
break;
|
|
|
|
case NETDEV_UNREGISTER:
|
|
br_del_if(br, dev);
|
|
break;
|
|
|
|
case NETDEV_CHANGENAME:
|
|
err = br_sysfs_renameif(p);
|
|
if (err)
|
|
return notifier_from_errno(err);
|
|
break;
|
|
|
|
case NETDEV_PRE_TYPE_CHANGE:
|
|
/* Forbid underlaying device to change its type. */
|
|
return NOTIFY_BAD;
|
|
|
|
case NETDEV_RESEND_IGMP:
|
|
/* Propagate to master device */
|
|
call_netdevice_notifiers(event, br->dev);
|
|
break;
|
|
}
|
|
|
|
/* Events that may cause spanning tree to refresh */
|
|
if (event == NETDEV_CHANGEADDR || event == NETDEV_UP ||
|
|
event == NETDEV_CHANGE || event == NETDEV_DOWN)
|
|
br_ifinfo_notify(RTM_NEWLINK, p);
|
|
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block br_device_notifier = {
|
|
.notifier_call = br_device_event
|
|
};
|
|
|
|
/* called with RTNL or RCU */
|
|
static int br_switchdev_event(struct notifier_block *unused,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
struct net_device *dev = switchdev_notifier_info_to_dev(ptr);
|
|
struct net_bridge_port *p;
|
|
struct net_bridge *br;
|
|
struct switchdev_notifier_fdb_info *fdb_info;
|
|
int err = NOTIFY_DONE;
|
|
|
|
p = br_port_get_rtnl_rcu(dev);
|
|
if (!p)
|
|
goto out;
|
|
|
|
br = p->br;
|
|
|
|
switch (event) {
|
|
case SWITCHDEV_FDB_ADD_TO_BRIDGE:
|
|
fdb_info = ptr;
|
|
err = br_fdb_external_learn_add(br, p, fdb_info->addr,
|
|
fdb_info->vid);
|
|
if (err) {
|
|
err = notifier_from_errno(err);
|
|
break;
|
|
}
|
|
br_fdb_offloaded_set(br, p, fdb_info->addr,
|
|
fdb_info->vid);
|
|
break;
|
|
case SWITCHDEV_FDB_DEL_TO_BRIDGE:
|
|
fdb_info = ptr;
|
|
err = br_fdb_external_learn_del(br, p, fdb_info->addr,
|
|
fdb_info->vid);
|
|
if (err)
|
|
err = notifier_from_errno(err);
|
|
break;
|
|
case SWITCHDEV_FDB_OFFLOADED:
|
|
fdb_info = ptr;
|
|
br_fdb_offloaded_set(br, p, fdb_info->addr,
|
|
fdb_info->vid);
|
|
break;
|
|
}
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static struct notifier_block br_switchdev_notifier = {
|
|
.notifier_call = br_switchdev_event,
|
|
};
|
|
|
|
static void __net_exit br_net_exit(struct net *net)
|
|
{
|
|
struct net_device *dev;
|
|
LIST_HEAD(list);
|
|
|
|
rtnl_lock();
|
|
for_each_netdev(net, dev)
|
|
if (dev->priv_flags & IFF_EBRIDGE)
|
|
br_dev_delete(dev, &list);
|
|
|
|
unregister_netdevice_many(&list);
|
|
rtnl_unlock();
|
|
|
|
}
|
|
|
|
static struct pernet_operations br_net_ops = {
|
|
.exit = br_net_exit,
|
|
};
|
|
|
|
static const struct stp_proto br_stp_proto = {
|
|
.rcv = br_stp_rcv,
|
|
};
|
|
|
|
static int __init br_init(void)
|
|
{
|
|
int err;
|
|
|
|
BUILD_BUG_ON(sizeof(struct br_input_skb_cb) > FIELD_SIZEOF(struct sk_buff, cb));
|
|
|
|
err = stp_proto_register(&br_stp_proto);
|
|
if (err < 0) {
|
|
pr_err("bridge: can't register sap for STP\n");
|
|
return err;
|
|
}
|
|
|
|
err = br_fdb_init();
|
|
if (err)
|
|
goto err_out;
|
|
|
|
err = register_pernet_subsys(&br_net_ops);
|
|
if (err)
|
|
goto err_out1;
|
|
|
|
err = br_nf_core_init();
|
|
if (err)
|
|
goto err_out2;
|
|
|
|
err = register_netdevice_notifier(&br_device_notifier);
|
|
if (err)
|
|
goto err_out3;
|
|
|
|
err = register_switchdev_notifier(&br_switchdev_notifier);
|
|
if (err)
|
|
goto err_out4;
|
|
|
|
err = br_netlink_init();
|
|
if (err)
|
|
goto err_out5;
|
|
|
|
brioctl_set(br_ioctl_deviceless_stub);
|
|
|
|
#if IS_ENABLED(CONFIG_ATM_LANE)
|
|
br_fdb_test_addr_hook = br_fdb_test_addr;
|
|
#endif
|
|
|
|
#if IS_MODULE(CONFIG_BRIDGE_NETFILTER)
|
|
pr_info("bridge: filtering via arp/ip/ip6tables is no longer available "
|
|
"by default. Update your scripts to load br_netfilter if you "
|
|
"need this.\n");
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
err_out5:
|
|
unregister_switchdev_notifier(&br_switchdev_notifier);
|
|
err_out4:
|
|
unregister_netdevice_notifier(&br_device_notifier);
|
|
err_out3:
|
|
br_nf_core_fini();
|
|
err_out2:
|
|
unregister_pernet_subsys(&br_net_ops);
|
|
err_out1:
|
|
br_fdb_fini();
|
|
err_out:
|
|
stp_proto_unregister(&br_stp_proto);
|
|
return err;
|
|
}
|
|
|
|
static void __exit br_deinit(void)
|
|
{
|
|
stp_proto_unregister(&br_stp_proto);
|
|
br_netlink_fini();
|
|
unregister_switchdev_notifier(&br_switchdev_notifier);
|
|
unregister_netdevice_notifier(&br_device_notifier);
|
|
brioctl_set(NULL);
|
|
unregister_pernet_subsys(&br_net_ops);
|
|
|
|
rcu_barrier(); /* Wait for completion of call_rcu()'s */
|
|
|
|
br_nf_core_fini();
|
|
#if IS_ENABLED(CONFIG_ATM_LANE)
|
|
br_fdb_test_addr_hook = NULL;
|
|
#endif
|
|
br_fdb_fini();
|
|
}
|
|
|
|
module_init(br_init)
|
|
module_exit(br_deinit)
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(BR_VERSION);
|
|
MODULE_ALIAS_RTNL_LINK("bridge");
|