net: dsa: fix switchdev objects on bridge master mistakenly being applied on ports
authorVladimir Oltean <vladimir.oltean@nxp.com>
Sun, 7 Mar 2021 10:21:56 +0000 (12:21 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 8 Mar 2021 19:59:00 +0000 (11:59 -0800)
Tobias reports that after the blamed patch, VLAN objects being added to
a bridge device are being added to all slave ports instead (swp2, swp3).

ip link add br0 type bridge vlan_filtering 1
ip link set swp2 master br0
ip link set swp3 master br0
bridge vlan add dev br0 vid 100 self

This is because the fix was too broad: we made dsa_port_offloads_netdev
say "yes, I offload the br0 bridge" for all slave ports, but we didn't
add the checks whether the switchdev object was in fact meant for the
physical port or for the bridge itself. So we are reacting on events in
a way in which we shouldn't.

The reason why the fix was too broad is because the question itself,
"does this DSA port offload this netdev", was too broad in the first
place. The solution is to disambiguate the question and separate it into
two different functions, one to be called for each switchdev attribute /
object that has an orig_dev == net_bridge (dsa_port_offloads_bridge),
and the other for orig_dev == net_bridge_port (*_offloads_bridge_port).

In the case of VLAN objects on the bridge interface, this solves the
problem because we know that VLAN objects are per bridge port and not
per bridge. And when orig_dev is equal to the net_bridge, we offload it
as a bridge, but not as a bridge port; that's how we are able to skip
reacting on those events. Note that this is compatible with future plans
to have explicit offloading of VLAN objects on the bridge interface as a
bridge port (in DSA, this signifies that we should add that VLAN towards
the CPU port).

Fixes: 99b8202b179f ("net: dsa: fix SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING getting ignored")
Reported-by: Tobias Waldekranz <tobias@waldekranz.com>
Signed-off-by: Vladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: Tobias Waldekranz <tobias@waldekranz.com>
Tested-by: Tobias Waldekranz <tobias@waldekranz.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/dsa/dsa_priv.h
net/dsa/slave.c

index 2eeaa42f2e083aa1287cd34ba2fbd582e0cb6d88..9d4b0e9b1aa1e3e720ee51670b7558d7ab79122c 100644 (file)
@@ -230,8 +230,8 @@ int dsa_port_hsr_join(struct dsa_port *dp, struct net_device *hsr);
 void dsa_port_hsr_leave(struct dsa_port *dp, struct net_device *hsr);
 extern const struct phylink_mac_ops dsa_port_phylink_mac_ops;
 
-static inline bool dsa_port_offloads_netdev(struct dsa_port *dp,
-                                           struct net_device *dev)
+static inline bool dsa_port_offloads_bridge_port(struct dsa_port *dp,
+                                                struct net_device *dev)
 {
        /* Switchdev offloading can be configured on: */
 
@@ -241,12 +241,6 @@ static inline bool dsa_port_offloads_netdev(struct dsa_port *dp,
                 */
                return true;
 
-       if (dp->bridge_dev == dev)
-               /* DSA ports connected to a bridge, and event was emitted
-                * for the bridge.
-                */
-               return true;
-
        if (dp->lag_dev == dev)
                /* DSA ports connected to a bridge via a LAG */
                return true;
@@ -254,14 +248,23 @@ static inline bool dsa_port_offloads_netdev(struct dsa_port *dp,
        return false;
 }
 
+static inline bool dsa_port_offloads_bridge(struct dsa_port *dp,
+                                           struct net_device *bridge_dev)
+{
+       /* DSA ports connected to a bridge, and event was emitted
+        * for the bridge.
+        */
+       return dp->bridge_dev == bridge_dev;
+}
+
 /* Returns true if any port of this tree offloads the given net_device */
-static inline bool dsa_tree_offloads_netdev(struct dsa_switch_tree *dst,
-                                           struct net_device *dev)
+static inline bool dsa_tree_offloads_bridge_port(struct dsa_switch_tree *dst,
+                                                struct net_device *dev)
 {
        struct dsa_port *dp;
 
        list_for_each_entry(dp, &dst->ports, list)
-               if (dsa_port_offloads_netdev(dp, dev))
+               if (dsa_port_offloads_bridge_port(dp, dev))
                        return true;
 
        return false;
index 491e3761b5f4a443ec76e077a6fcff97ee6e9c49..992fcab4b55293e6629ea079d48766fa246b733c 100644 (file)
@@ -278,28 +278,43 @@ static int dsa_slave_port_attr_set(struct net_device *dev,
        struct dsa_port *dp = dsa_slave_to_port(dev);
        int ret;
 
-       if (!dsa_port_offloads_netdev(dp, attr->orig_dev))
-               return -EOPNOTSUPP;
-
        switch (attr->id) {
        case SWITCHDEV_ATTR_ID_PORT_STP_STATE:
+               if (!dsa_port_offloads_bridge_port(dp, attr->orig_dev))
+                       return -EOPNOTSUPP;
+
                ret = dsa_port_set_state(dp, attr->u.stp_state);
                break;
        case SWITCHDEV_ATTR_ID_BRIDGE_VLAN_FILTERING:
+               if (!dsa_port_offloads_bridge(dp, attr->orig_dev))
+                       return -EOPNOTSUPP;
+
                ret = dsa_port_vlan_filtering(dp, attr->u.vlan_filtering,
                                              extack);
                break;
        case SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME:
+               if (!dsa_port_offloads_bridge(dp, attr->orig_dev))
+                       return -EOPNOTSUPP;
+
                ret = dsa_port_ageing_time(dp, attr->u.ageing_time);
                break;
        case SWITCHDEV_ATTR_ID_PORT_PRE_BRIDGE_FLAGS:
+               if (!dsa_port_offloads_bridge_port(dp, attr->orig_dev))
+                       return -EOPNOTSUPP;
+
                ret = dsa_port_pre_bridge_flags(dp, attr->u.brport_flags,
                                                extack);
                break;
        case SWITCHDEV_ATTR_ID_PORT_BRIDGE_FLAGS:
+               if (!dsa_port_offloads_bridge_port(dp, attr->orig_dev))
+                       return -EOPNOTSUPP;
+
                ret = dsa_port_bridge_flags(dp, attr->u.brport_flags, extack);
                break;
        case SWITCHDEV_ATTR_ID_BRIDGE_MROUTER:
+               if (!dsa_port_offloads_bridge(dp, attr->orig_dev))
+                       return -EOPNOTSUPP;
+
                ret = dsa_port_mrouter(dp->cpu_dp, attr->u.mrouter, extack);
                break;
        default:
@@ -341,9 +356,6 @@ static int dsa_slave_vlan_add(struct net_device *dev,
        struct switchdev_obj_port_vlan vlan;
        int err;
 
-       if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
-               return -EOPNOTSUPP;
-
        if (dsa_port_skip_vlan_configuration(dp)) {
                NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
                return 0;
@@ -391,27 +403,36 @@ static int dsa_slave_port_obj_add(struct net_device *dev,
 
        switch (obj->id) {
        case SWITCHDEV_OBJ_ID_PORT_MDB:
-               if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
+               if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
                        return -EOPNOTSUPP;
+
                err = dsa_port_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
                break;
        case SWITCHDEV_OBJ_ID_HOST_MDB:
+               if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
+                       return -EOPNOTSUPP;
+
                /* DSA can directly translate this to a normal MDB add,
                 * but on the CPU port.
                 */
                err = dsa_port_mdb_add(dp->cpu_dp, SWITCHDEV_OBJ_PORT_MDB(obj));
                break;
        case SWITCHDEV_OBJ_ID_PORT_VLAN:
+               if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
+                       return -EOPNOTSUPP;
+
                err = dsa_slave_vlan_add(dev, obj, extack);
                break;
        case SWITCHDEV_OBJ_ID_MRP:
-               if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
+               if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
                        return -EOPNOTSUPP;
+
                err = dsa_port_mrp_add(dp, SWITCHDEV_OBJ_MRP(obj));
                break;
        case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
-               if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
+               if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
                        return -EOPNOTSUPP;
+
                err = dsa_port_mrp_add_ring_role(dp,
                                                 SWITCHDEV_OBJ_RING_ROLE_MRP(obj));
                break;
@@ -431,9 +452,6 @@ static int dsa_slave_vlan_del(struct net_device *dev,
        struct switchdev_obj_port_vlan *vlan;
        int err;
 
-       if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
-               return -EOPNOTSUPP;
-
        if (dsa_port_skip_vlan_configuration(dp))
                return 0;
 
@@ -459,27 +477,36 @@ static int dsa_slave_port_obj_del(struct net_device *dev,
 
        switch (obj->id) {
        case SWITCHDEV_OBJ_ID_PORT_MDB:
-               if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
+               if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
                        return -EOPNOTSUPP;
+
                err = dsa_port_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
                break;
        case SWITCHDEV_OBJ_ID_HOST_MDB:
+               if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
+                       return -EOPNOTSUPP;
+
                /* DSA can directly translate this to a normal MDB add,
                 * but on the CPU port.
                 */
                err = dsa_port_mdb_del(dp->cpu_dp, SWITCHDEV_OBJ_PORT_MDB(obj));
                break;
        case SWITCHDEV_OBJ_ID_PORT_VLAN:
+               if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
+                       return -EOPNOTSUPP;
+
                err = dsa_slave_vlan_del(dev, obj);
                break;
        case SWITCHDEV_OBJ_ID_MRP:
-               if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
+               if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
                        return -EOPNOTSUPP;
+
                err = dsa_port_mrp_del(dp, SWITCHDEV_OBJ_MRP(obj));
                break;
        case SWITCHDEV_OBJ_ID_RING_ROLE_MRP:
-               if (!dsa_port_offloads_netdev(dp, obj->orig_dev))
+               if (!dsa_port_offloads_bridge(dp, obj->orig_dev))
                        return -EOPNOTSUPP;
+
                err = dsa_port_mrp_del_ring_role(dp,
                                                 SWITCHDEV_OBJ_RING_ROLE_MRP(obj));
                break;
@@ -2298,7 +2325,7 @@ static int dsa_slave_switchdev_event(struct notifier_block *unused,
                         * other ports bridged with the LAG should be able to
                         * autonomously forward towards it.
                         */
-                       if (dsa_tree_offloads_netdev(dp->ds->dst, dev))
+                       if (dsa_tree_offloads_bridge_port(dp->ds->dst, dev))
                                return NOTIFY_DONE;
                }