Commit 58adf9dc authored by Vladimir Oltean's avatar Vladimir Oltean Committed by David S. Miller

net: dsa: let drivers state that they need VLAN filtering while standalone

As explained in commit e358bef7 ("net: dsa: Give drivers the chance
to veto certain upper devices"), the hellcreek driver uses some tricks
to comply with the network stack expectations: it enforces port
separation in standalone mode using VLANs. For untagged traffic,
bridging between ports is prevented by using different PVIDs, and for
VLAN-tagged traffic, it never accepts 8021q uppers with the same VID on
two ports, so packets with one VLAN cannot leak from one port to another.

That is almost fine*, and has worked because hellcreek relied on an
implicit behavior of the DSA core that was changed by the previous
patch: the standalone ports declare the 'rx-vlan-filter' feature as 'on
[fixed]'. Since most of the DSA drivers are actually VLAN-unaware in
standalone mode, that feature was actually incorrectly reflecting the
hardware/driver state, so there was a desire to fix it. This leaves the
hellcreek driver in a situation where it has to explicitly request this
behavior from the DSA framework.

We configure the ports as follows:

- Standalone: 'rx-vlan-filter' is on. An 8021q upper on top of a
  standalone hellcreek port will go through dsa_slave_vlan_rx_add_vid
  and will add a VLAN to the hardware tables, giving the driver the
  opportunity to refuse it through .port_prechangeupper.

- Bridged with vlan_filtering=0: 'rx-vlan-filter' is off. An 8021q upper
  on top of a bridged hellcreek port will not go through
  dsa_slave_vlan_rx_add_vid, because there will not be any attempt to
  offload this VLAN. The driver already disables VLAN awareness, so that
  upper should receive the traffic it needs.

- Bridged with vlan_filtering=1: 'rx-vlan-filter' is on. An 8021q upper
  on top of a bridged hellcreek port will call dsa_slave_vlan_rx_add_vid,
  and can again be vetoed through .port_prechangeupper.

*It is not actually completely fine, because if I follow through
correctly, we can have the following situation:

ip link add br0 type bridge vlan_filtering 0
ip link set lan0 master br0 # lan0 now becomes VLAN-unaware
ip link set lan0 nomaster # lan0 fails to become VLAN-aware again, therefore breaking isolation

This patch fixes that corner case by extending the DSA core logic, based
on this requested attribute, to change the VLAN awareness state of the
switch (port) when it leaves the bridge.
Signed-off-by: default avatarVladimir Oltean <vladimir.oltean@nxp.com>
Reviewed-by: default avatarFlorian Fainelli <f.fainelli@gmail.com>
Acked-by: Kurt Kanzenbach's avatarKurt Kanzenbach <kurt@linutronix.de>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 06cfb2df
...@@ -1345,6 +1345,7 @@ static int hellcreek_setup(struct dsa_switch *ds) ...@@ -1345,6 +1345,7 @@ static int hellcreek_setup(struct dsa_switch *ds)
* filtering setups are not supported. * filtering setups are not supported.
*/ */
ds->vlan_filtering_is_global = true; ds->vlan_filtering_is_global = true;
ds->needs_standalone_vlan_filtering = true;
/* Intercept _all_ PTP multicast traffic */ /* Intercept _all_ PTP multicast traffic */
ret = hellcreek_setup_fdb(hellcreek); ret = hellcreek_setup_fdb(hellcreek);
......
...@@ -363,6 +363,9 @@ struct dsa_switch { ...@@ -363,6 +363,9 @@ struct dsa_switch {
*/ */
bool vlan_filtering_is_global; bool vlan_filtering_is_global;
/* Keep VLAN filtering enabled on ports not offloading any upper. */
bool needs_standalone_vlan_filtering;
/* Pass .port_vlan_add and .port_vlan_del to drivers even for bridges /* Pass .port_vlan_add and .port_vlan_del to drivers even for bridges
* that have vlan_filtering=0. All drivers should ideally set this (and * that have vlan_filtering=0. All drivers should ideally set this (and
* then the option would get removed), but it is unknown whether this * then the option would get removed), but it is unknown whether this
......
...@@ -1435,11 +1435,12 @@ static int dsa_slave_clear_vlan(struct net_device *vdev, int vid, void *arg) ...@@ -1435,11 +1435,12 @@ static int dsa_slave_clear_vlan(struct net_device *vdev, int vid, void *arg)
* To summarize, a DSA switch port offloads: * To summarize, a DSA switch port offloads:
* *
* - If standalone (this includes software bridge, software LAG): * - If standalone (this includes software bridge, software LAG):
* - if ds->vlan_filtering_is_global = true AND there are bridges spanning * - if ds->needs_standalone_vlan_filtering = true, OR if
* this switch chip which have vlan_filtering=1: * (ds->vlan_filtering_is_global = true AND there are bridges spanning
* this switch chip which have vlan_filtering=1)
* - the 8021q upper VLANs * - the 8021q upper VLANs
* - else (VLAN filtering is not global, or it is, but no port is under a * - else (standalone VLAN filtering is not needed, VLAN filtering is not
* VLAN-aware bridge): * global, or it is, but no port is under a VLAN-aware bridge):
* - no VLAN (any 8021q upper is a software VLAN) * - no VLAN (any 8021q upper is a software VLAN)
* *
* - If under a vlan_filtering=0 bridge which it offload: * - If under a vlan_filtering=0 bridge which it offload:
...@@ -1871,6 +1872,7 @@ void dsa_slave_setup_tagger(struct net_device *slave) ...@@ -1871,6 +1872,7 @@ void dsa_slave_setup_tagger(struct net_device *slave)
struct dsa_slave_priv *p = netdev_priv(slave); struct dsa_slave_priv *p = netdev_priv(slave);
const struct dsa_port *cpu_dp = dp->cpu_dp; const struct dsa_port *cpu_dp = dp->cpu_dp;
struct net_device *master = cpu_dp->master; struct net_device *master = cpu_dp->master;
const struct dsa_switch *ds = dp->ds;
slave->needed_headroom = cpu_dp->tag_ops->needed_headroom; slave->needed_headroom = cpu_dp->tag_ops->needed_headroom;
slave->needed_tailroom = cpu_dp->tag_ops->needed_tailroom; slave->needed_tailroom = cpu_dp->tag_ops->needed_tailroom;
...@@ -1888,6 +1890,8 @@ void dsa_slave_setup_tagger(struct net_device *slave) ...@@ -1888,6 +1890,8 @@ void dsa_slave_setup_tagger(struct net_device *slave)
slave->features |= NETIF_F_LLTX; slave->features |= NETIF_F_LLTX;
if (slave->needed_tailroom) if (slave->needed_tailroom)
slave->features &= ~(NETIF_F_SG | NETIF_F_FRAGLIST); slave->features &= ~(NETIF_F_SG | NETIF_F_FRAGLIST);
if (ds->needs_standalone_vlan_filtering)
slave->features |= NETIF_F_HW_VLAN_CTAG_FILTER;
} }
static struct lock_class_key dsa_slave_netdev_xmit_lock_key; static struct lock_class_key dsa_slave_netdev_xmit_lock_key;
......
...@@ -116,9 +116,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds, ...@@ -116,9 +116,10 @@ static int dsa_switch_bridge_join(struct dsa_switch *ds,
static int dsa_switch_bridge_leave(struct dsa_switch *ds, static int dsa_switch_bridge_leave(struct dsa_switch *ds,
struct dsa_notifier_bridge_info *info) struct dsa_notifier_bridge_info *info)
{ {
bool unset_vlan_filtering = br_vlan_enabled(info->br);
struct dsa_switch_tree *dst = ds->dst; struct dsa_switch_tree *dst = ds->dst;
struct netlink_ext_ack extack = {0}; struct netlink_ext_ack extack = {0};
bool change_vlan_filtering = false;
bool vlan_filtering;
int err, port; int err, port;
if (dst->index == info->tree_index && ds->index == info->sw_index && if (dst->index == info->tree_index && ds->index == info->sw_index &&
...@@ -131,6 +132,15 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds, ...@@ -131,6 +132,15 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
info->sw_index, info->port, info->sw_index, info->port,
info->br); info->br);
if (ds->needs_standalone_vlan_filtering && !br_vlan_enabled(info->br)) {
change_vlan_filtering = true;
vlan_filtering = true;
} else if (!ds->needs_standalone_vlan_filtering &&
br_vlan_enabled(info->br)) {
change_vlan_filtering = true;
vlan_filtering = false;
}
/* If the bridge was vlan_filtering, the bridge core doesn't trigger an /* If the bridge was vlan_filtering, the bridge core doesn't trigger an
* event for changing vlan_filtering setting upon slave ports leaving * event for changing vlan_filtering setting upon slave ports leaving
* it. That is a good thing, because that lets us handle it and also * it. That is a good thing, because that lets us handle it and also
...@@ -139,21 +149,22 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds, ...@@ -139,21 +149,22 @@ static int dsa_switch_bridge_leave(struct dsa_switch *ds,
* vlan_filtering callback is only when the last port leaves the last * vlan_filtering callback is only when the last port leaves the last
* VLAN-aware bridge. * VLAN-aware bridge.
*/ */
if (unset_vlan_filtering && ds->vlan_filtering_is_global) { if (change_vlan_filtering && ds->vlan_filtering_is_global) {
for (port = 0; port < ds->num_ports; port++) { for (port = 0; port < ds->num_ports; port++) {
struct net_device *bridge_dev; struct net_device *bridge_dev;
bridge_dev = dsa_to_port(ds, port)->bridge_dev; bridge_dev = dsa_to_port(ds, port)->bridge_dev;
if (bridge_dev && br_vlan_enabled(bridge_dev)) { if (bridge_dev && br_vlan_enabled(bridge_dev)) {
unset_vlan_filtering = false; change_vlan_filtering = false;
break; break;
} }
} }
} }
if (unset_vlan_filtering) {
if (change_vlan_filtering) {
err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port), err = dsa_port_vlan_filtering(dsa_to_port(ds, info->port),
false, &extack); vlan_filtering, &extack);
if (extack._msg) if (extack._msg)
dev_err(ds->dev, "port %d: %s\n", info->port, dev_err(ds->dev, "port %d: %s\n", info->port,
extack._msg); extack._msg);
......
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