From cc1d5bda17c8eb4cd195f05a5558ed63df057fce Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 20 Feb 2019 14:35:38 -0800 Subject: net: dsa: Deny enslaving VLAN devices into VLAN aware bridge VLAN devices on top of a DSA network device which is already part of a bridge and with said bridge being VLAN aware should not be allowed to be enslaved into that bridge. For one, this duplicates functionality offered by the VLAN aware bridge which supports tagged and untagged VLAN frames processing and it would make things needlessly complex to e.g.: propagate FDB/MDB accordingly. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- net/dsa/slave.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/net/dsa/slave.c b/net/dsa/slave.c index a78b2bba0332..f7f5d126a704 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -1408,16 +1408,49 @@ static int dsa_slave_changeupper(struct net_device *dev, return err; } +static int dsa_slave_upper_vlan_check(struct net_device *dev, + struct netdev_notifier_changeupper_info * + info) +{ + struct netlink_ext_ack *ext_ack; + struct net_device *slave; + struct dsa_port *dp; + + ext_ack = netdev_notifier_info_to_extack(&info->info); + + if (!is_vlan_dev(dev)) + return NOTIFY_DONE; + + slave = vlan_dev_real_dev(dev); + if (!dsa_slave_dev_check(slave)) + return NOTIFY_DONE; + + dp = dsa_slave_to_port(slave); + if (!dp->bridge_dev) + return NOTIFY_DONE; + + /* Deny enslaving a VLAN device into a VLAN-aware bridge */ + if (br_vlan_enabled(dp->bridge_dev) && + netif_is_bridge_master(info->upper_dev) && info->linking) { + NL_SET_ERR_MSG_MOD(ext_ack, + "Cannot enslave VLAN device into VLAN aware bridge"); + return notifier_from_errno(-EINVAL); + } + + return NOTIFY_DONE; +} + static int dsa_slave_netdevice_event(struct notifier_block *nb, unsigned long event, void *ptr) { struct net_device *dev = netdev_notifier_info_to_dev(ptr); - if (!dsa_slave_dev_check(dev)) - return NOTIFY_DONE; + if (event == NETDEV_CHANGEUPPER) { + if (!dsa_slave_dev_check(dev)) + return dsa_slave_upper_vlan_check(dev, ptr); - if (event == NETDEV_CHANGEUPPER) return dsa_slave_changeupper(dev, ptr); + } return NOTIFY_DONE; } -- cgit v1.2.3-59-g8ed1b From 061f6a505ac33659eab007731c0f6374df39ab55 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 20 Feb 2019 14:35:39 -0800 Subject: net: dsa: Add ndo_vlan_rx_{add, kill}_vid implementation In order to properly support VLAN filtering being enabled/disabled on a bridge, while having other ports being non bridge port members, we need to support the ndo_vlan_rx_{add,kill}_vid callbacks in order to make sure the non-bridge ports can continue receiving VLAN tags, even when the switch is globally configured to do ingress/egress VID checking. Since we can call dsa_port_vlan_{add,del} with a bridge_dev pointer NULL, we now need to check that in these two functions. We specifically deal with two possibly problematic cases: - creating a bridge VLAN entry while there is an existing VLAN device claiming that same VID - creating a VLAN device while there is an existing bridge VLAN entry with that VID Those are both resolved with returning -EBUSY back to user-space. Signed-off-by: Florian Fainelli Signed-off-by: David S. Miller --- net/dsa/port.c | 12 +++++++--- net/dsa/slave.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- net/dsa/switch.c | 42 +++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 4 deletions(-) diff --git a/net/dsa/port.c b/net/dsa/port.c index e9b5b50f8cf1..c011dff523d0 100644 --- a/net/dsa/port.c +++ b/net/dsa/port.c @@ -291,7 +291,10 @@ int dsa_port_vlan_add(struct dsa_port *dp, .vlan = vlan, }; - if (br_vlan_enabled(dp->bridge_dev)) + /* Can be called from dsa_slave_port_obj_add() or + * dsa_slave_vlan_rx_add_vid() + */ + if (!dp->bridge_dev || br_vlan_enabled(dp->bridge_dev)) return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_ADD, &info); return 0; @@ -306,10 +309,13 @@ int dsa_port_vlan_del(struct dsa_port *dp, .vlan = vlan, }; - if (netif_is_bridge_master(vlan->obj.orig_dev)) + if (vlan->obj.orig_dev && netif_is_bridge_master(vlan->obj.orig_dev)) return -EOPNOTSUPP; - if (br_vlan_enabled(dp->bridge_dev)) + /* Can be called from dsa_slave_port_obj_del() or + * dsa_slave_vlan_rx_kill_vid() + */ + if (!dp->bridge_dev || br_vlan_enabled(dp->bridge_dev)) return dsa_port_notify(dp, DSA_NOTIFIER_VLAN_DEL, &info); return 0; diff --git a/net/dsa/slave.c b/net/dsa/slave.c index f7f5d126a704..90629b12beaf 100644 --- a/net/dsa/slave.c +++ b/net/dsa/slave.c @@ -983,6 +983,72 @@ static int dsa_slave_get_ts_info(struct net_device *dev, return ds->ops->get_ts_info(ds, p->dp->index, ts); } +static int dsa_slave_vlan_rx_add_vid(struct net_device *dev, __be16 proto, + u16 vid) +{ + struct dsa_port *dp = dsa_slave_to_port(dev); + struct switchdev_obj_port_vlan vlan = { + .vid_begin = vid, + .vid_end = vid, + /* This API only allows programming tagged, non-PVID VIDs */ + .flags = 0, + }; + struct bridge_vlan_info info; + int ret; + + /* Check for a possible bridge VLAN entry now since there is no + * need to emulate the switchdev prepare + commit phase. + */ + if (dp->bridge_dev) { + /* br_vlan_get_info() returns -EINVAL or -ENOENT if the + * device, respectively the VID is not found, returning + * 0 means success, which is a failure for us here. + */ + ret = br_vlan_get_info(dp->bridge_dev, vid, &info); + if (ret == 0) + return -EBUSY; + } + + ret = dsa_port_vlan_add(dp, &vlan, NULL); + if (ret == -EOPNOTSUPP) + ret = 0; + + return ret; +} + +static int dsa_slave_vlan_rx_kill_vid(struct net_device *dev, __be16 proto, + u16 vid) +{ + struct dsa_port *dp = dsa_slave_to_port(dev); + struct switchdev_obj_port_vlan vlan = { + .vid_begin = vid, + .vid_end = vid, + /* This API only allows programming tagged, non-PVID VIDs */ + .flags = 0, + }; + struct bridge_vlan_info info; + int ret; + + /* Check for a possible bridge VLAN entry now since there is no + * need to emulate the switchdev prepare + commit phase. + */ + if (dp->bridge_dev) { + /* br_vlan_get_info() returns -EINVAL or -ENOENT if the + * device, respectively the VID is not found, returning + * 0 means success, which is a failure for us here. + */ + ret = br_vlan_get_info(dp->bridge_dev, vid, &info); + if (ret == 0) + return -EBUSY; + } + + ret = dsa_port_vlan_del(dp, &vlan); + if (ret == -EOPNOTSUPP) + ret = 0; + + return ret; +} + static const struct ethtool_ops dsa_slave_ethtool_ops = { .get_drvinfo = dsa_slave_get_drvinfo, .get_regs_len = dsa_slave_get_regs_len, @@ -1048,6 +1114,8 @@ static const struct net_device_ops dsa_slave_netdev_ops = { .ndo_setup_tc = dsa_slave_setup_tc, .ndo_get_stats64 = dsa_slave_get_stats64, .ndo_get_port_parent_id = dsa_slave_get_port_parent_id, + .ndo_vlan_rx_add_vid = dsa_slave_vlan_rx_add_vid, + .ndo_vlan_rx_kill_vid = dsa_slave_vlan_rx_kill_vid, }; static const struct switchdev_ops dsa_slave_switchdev_ops = { @@ -1307,7 +1375,8 @@ int dsa_slave_create(struct dsa_port *port) if (slave_dev == NULL) return -ENOMEM; - slave_dev->features = master->vlan_features | NETIF_F_HW_TC; + slave_dev->features = master->vlan_features | NETIF_F_HW_TC | + NETIF_F_HW_VLAN_CTAG_FILTER; slave_dev->hw_features |= NETIF_F_HW_TC; slave_dev->ethtool_ops = &dsa_slave_ethtool_ops; eth_hw_addr_inherit(slave_dev, master); diff --git a/net/dsa/switch.c b/net/dsa/switch.c index 142b294d3446..e1fae969aa73 100644 --- a/net/dsa/switch.c +++ b/net/dsa/switch.c @@ -12,6 +12,7 @@ #include #include +#include #include #include "dsa_priv.h" @@ -168,6 +169,43 @@ static int dsa_switch_mdb_del(struct dsa_switch *ds, return 0; } +static int dsa_port_vlan_device_check(struct net_device *vlan_dev, + int vlan_dev_vid, + void *arg) +{ + struct switchdev_obj_port_vlan *vlan = arg; + u16 vid; + + for (vid = vlan->vid_begin; vid <= vlan->vid_end; ++vid) { + if (vid == vlan_dev_vid) + return -EBUSY; + } + + return 0; +} + +static int dsa_port_vlan_check(struct dsa_switch *ds, int port, + const struct switchdev_obj_port_vlan *vlan) +{ + const struct dsa_port *dp = dsa_to_port(ds, port); + int err = 0; + + /* Device is not bridged, let it proceed with the VLAN device + * creation. + */ + if (!dp->bridge_dev) + return err; + + /* dsa_slave_vlan_rx_{add,kill}_vid() cannot use the prepare pharse and + * already checks whether there is an overlapping bridge VLAN entry + * with the same VID, so here we only need to check that if we are + * adding a bridge VLAN entry there is not an overlapping VLAN device + * claiming that VID. + */ + return vlan_for_each(dp->slave, dsa_port_vlan_device_check, + (void *)vlan); +} + static int dsa_switch_vlan_prepare_bitmap(struct dsa_switch *ds, const struct switchdev_obj_port_vlan *vlan, @@ -179,6 +217,10 @@ dsa_switch_vlan_prepare_bitmap(struct dsa_switch *ds, return -EOPNOTSUPP; for_each_set_bit(port, bitmap, ds->num_ports) { + err = dsa_port_vlan_check(ds, port, vlan); + if (err) + return err; + err = ds->ops->port_vlan_prepare(ds, port, vlan); if (err) return err; -- cgit v1.2.3-59-g8ed1b