Commit 164f861b authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller

net: dsa: offload bridge port VLANs on foreign interfaces

DSA now explicitly handles VLANs installed with the 'self' flag on the
bridge as host VLANs, instead of just replicating every bridge port VLAN
also on the CPU port and never deleting it, which is what it did before.

However, this leaves a corner case uncovered, as explained by
Tobias Waldekranz:
https://patchwork.kernel.org/project/netdevbpf/patch/20220209213044.2353153-6-vladimir.oltean@nxp.com/#24735260

Forwarding towards a bridge port VLAN installed on a bridge port foreign
to DSA (separate NIC, Wi-Fi AP) used to work by virtue of the fact that
DSA itself needed to have at least one port in that VLAN (therefore, it
also had the CPU port in said VLAN). However, now that the CPU port may
not be member of all VLANs that user ports are members of, we need to
ensure this isn't the case if software forwarding to a foreign interface
is required.

The solution is to treat bridge port VLANs on standalone interfaces in
the exact same way as host VLANs. From DSA's perspective, there is no
difference between local termination and software forwarding; packets in
that VLAN must reach the CPU in both cases.
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 134ef238
...@@ -565,6 +565,7 @@ static void dsa_port_teardown(struct dsa_port *dp) ...@@ -565,6 +565,7 @@ static void dsa_port_teardown(struct dsa_port *dp)
struct dsa_switch *ds = dp->ds; struct dsa_switch *ds = dp->ds;
struct dsa_mac_addr *a, *tmp; struct dsa_mac_addr *a, *tmp;
struct net_device *slave; struct net_device *slave;
struct dsa_vlan *v, *n;
if (!dp->setup) if (!dp->setup)
return; return;
...@@ -605,6 +606,11 @@ static void dsa_port_teardown(struct dsa_port *dp) ...@@ -605,6 +606,11 @@ static void dsa_port_teardown(struct dsa_port *dp)
kfree(a); kfree(a);
} }
list_for_each_entry_safe(v, n, &dp->vlans, list) {
list_del(&v->list);
kfree(v);
}
dp->setup = false; dp->setup = false;
} }
......
...@@ -376,6 +376,9 @@ static int dsa_slave_vlan_add(struct net_device *dev, ...@@ -376,6 +376,9 @@ static int dsa_slave_vlan_add(struct net_device *dev,
return dsa_port_vlan_add(dp, vlan, extack); return dsa_port_vlan_add(dp, vlan, extack);
} }
/* Offload a VLAN installed on the bridge or on a foreign interface by
* installing it as a VLAN towards the CPU port.
*/
static int dsa_slave_host_vlan_add(struct net_device *dev, static int dsa_slave_host_vlan_add(struct net_device *dev,
const struct switchdev_obj *obj, const struct switchdev_obj *obj,
struct netlink_ext_ack *extack) struct netlink_ext_ack *extack)
...@@ -383,6 +386,10 @@ static int dsa_slave_host_vlan_add(struct net_device *dev, ...@@ -383,6 +386,10 @@ static int dsa_slave_host_vlan_add(struct net_device *dev,
struct dsa_port *dp = dsa_slave_to_port(dev); struct dsa_port *dp = dsa_slave_to_port(dev);
struct switchdev_obj_port_vlan vlan; struct switchdev_obj_port_vlan vlan;
/* Do nothing if this is a software bridge */
if (!dp->bridge)
return -EOPNOTSUPP;
if (dsa_port_skip_vlan_configuration(dp)) { if (dsa_port_skip_vlan_configuration(dp)) {
NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN"); NL_SET_ERR_MSG_MOD(extack, "skipping configuration of VLAN");
return 0; return 0;
...@@ -422,17 +429,10 @@ static int dsa_slave_port_obj_add(struct net_device *dev, const void *ctx, ...@@ -422,17 +429,10 @@ static int dsa_slave_port_obj_add(struct net_device *dev, const void *ctx,
err = dsa_port_host_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj)); err = dsa_port_host_mdb_add(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
break; break;
case SWITCHDEV_OBJ_ID_PORT_VLAN: case SWITCHDEV_OBJ_ID_PORT_VLAN:
if (netif_is_bridge_master(obj->orig_dev)) { if (dsa_port_offloads_bridge_port(dp, obj->orig_dev))
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
return -EOPNOTSUPP;
err = dsa_slave_host_vlan_add(dev, obj, extack);
} else {
if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
return -EOPNOTSUPP;
err = dsa_slave_vlan_add(dev, obj, extack); err = dsa_slave_vlan_add(dev, obj, extack);
} else
err = dsa_slave_host_vlan_add(dev, obj, extack);
break; break;
case SWITCHDEV_OBJ_ID_MRP: case SWITCHDEV_OBJ_ID_MRP:
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev)) if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
...@@ -475,6 +475,10 @@ static int dsa_slave_host_vlan_del(struct net_device *dev, ...@@ -475,6 +475,10 @@ static int dsa_slave_host_vlan_del(struct net_device *dev,
struct dsa_port *dp = dsa_slave_to_port(dev); struct dsa_port *dp = dsa_slave_to_port(dev);
struct switchdev_obj_port_vlan *vlan; struct switchdev_obj_port_vlan *vlan;
/* Do nothing if this is a software bridge */
if (!dp->bridge)
return -EOPNOTSUPP;
if (dsa_port_skip_vlan_configuration(dp)) if (dsa_port_skip_vlan_configuration(dp))
return 0; return 0;
...@@ -506,17 +510,10 @@ static int dsa_slave_port_obj_del(struct net_device *dev, const void *ctx, ...@@ -506,17 +510,10 @@ static int dsa_slave_port_obj_del(struct net_device *dev, const void *ctx,
err = dsa_port_host_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj)); err = dsa_port_host_mdb_del(dp, SWITCHDEV_OBJ_PORT_MDB(obj));
break; break;
case SWITCHDEV_OBJ_ID_PORT_VLAN: case SWITCHDEV_OBJ_ID_PORT_VLAN:
if (netif_is_bridge_master(obj->orig_dev)) { if (dsa_port_offloads_bridge_port(dp, obj->orig_dev))
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
return -EOPNOTSUPP;
err = dsa_slave_host_vlan_del(dev, obj);
} else {
if (!dsa_port_offloads_bridge_port(dp, obj->orig_dev))
return -EOPNOTSUPP;
err = dsa_slave_vlan_del(dev, obj); err = dsa_slave_vlan_del(dev, obj);
} else
err = dsa_slave_host_vlan_del(dev, obj);
break; break;
case SWITCHDEV_OBJ_ID_MRP: case SWITCHDEV_OBJ_ID_MRP:
if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev)) if (!dsa_port_offloads_bridge_dev(dp, obj->orig_dev))
...@@ -2547,13 +2544,15 @@ static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused, ...@@ -2547,13 +2544,15 @@ static int dsa_slave_switchdev_blocking_event(struct notifier_block *unused,
switch (event) { switch (event) {
case SWITCHDEV_PORT_OBJ_ADD: case SWITCHDEV_PORT_OBJ_ADD:
err = switchdev_handle_port_obj_add(dev, ptr, err = switchdev_handle_port_obj_add_foreign(dev, ptr,
dsa_slave_dev_check, dsa_slave_dev_check,
dsa_foreign_dev_check,
dsa_slave_port_obj_add); dsa_slave_port_obj_add);
return notifier_from_errno(err); return notifier_from_errno(err);
case SWITCHDEV_PORT_OBJ_DEL: case SWITCHDEV_PORT_OBJ_DEL:
err = switchdev_handle_port_obj_del(dev, ptr, err = switchdev_handle_port_obj_del_foreign(dev, ptr,
dsa_slave_dev_check, dsa_slave_dev_check,
dsa_foreign_dev_check,
dsa_slave_port_obj_del); dsa_slave_port_obj_del);
return notifier_from_errno(err); return notifier_from_errno(err);
case SWITCHDEV_PORT_ATTR_SET: case SWITCHDEV_PORT_ATTR_SET:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment