Merge branch 'dsa-mv88e6xxx-port-isolation'

Tobias Waldekranz says:

====================
net: dsa: mv88e6xxx: Improve standalone port isolation

The ideal isolation between standalone ports satisfies two properties:
1. Packets from one standalone port must not be forwarded to any other
   port.
2. Packets from a standalone port must be sent to the CPU port.

mv88e6xxx solves (1) by isolating standalone ports using the PVT. Up
to this point though, (2) has not guaranteed; as the ATU is still
consulted, there is a chance that incoming packets never reach the CPU
if its DA has previously been used as the SA of an earlier packet (see
1/5 for more details). This is typically not a problem, except for one
very useful setup in which switch ports are looped in order to run the
bridge kselftests in tools/testing/selftests/net/forwarding. This
series attempts to solve (2).

Ideally, we could simply use the "ForceMap" bit of more modern chips
(Agate and newer) to classify all incoming packets as MGMT. This is
not available on older silicon that is still widely used (Opal Plus
chips like the 6097 for example).

Instead, this series takes a two pronged approach:

1/5: Always clear MapDA on standalone ports to make sure that no ATU
     entry can lead packets astray. This solves (2) for single-chip
     systems.

2/5: Trivial prep work for 4/5.
3/5: Trivial prep work for 4/5.

4/5: On multi-chip systems though, this is not enough. On the incoming
     chip, the packet will be forced out towards the CPU thanks to
     1/5, but on any intermediate chips the ATU is still consulted. We
     override this behavior by marking the reserved standalone VID (0)
     as a policy VID, the DSA ports' VID policy is set to TRAP. This
     will cause the packet to be reclassified as MGMT on the first
     intermediate chip, after which it's a straight shot towards the
     CPU.

Finally, we allow more tests to be run on mv88e6xxx:

5/5: The bridge_vlan{,un}aware suites sets an ageing_time of 10s on
     the bridge it creates, but mv88e6xxx has a minimum supported time
     of 15s. Allow this time to be overridden in forwarding.config.

With this series in place, mv88e6xxx passes the following kselftest
suites:

- bridge_port_isolation.sh
- bridge_sticky_fdb.sh
- bridge_vlan_aware.sh
- bridge_vlan_unaware.sh

v1 -> v2:
  - Wording/spelling (Vladimir)
  - Use standard iterator in dsa_switch_upstream_port (Vladimir)
  - Limit enabling of VTU port policy to downstream DSA ports (Vladimir)
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2022-02-03 14:05:57 +00:00
commit 0947644332
11 changed files with 110 additions and 34 deletions

View File

@ -1290,8 +1290,15 @@ static u16 mv88e6xxx_port_vlan(struct mv88e6xxx_chip *chip, int dev, int port)
pvlan = 0;
/* Frames from user ports can egress any local DSA links and CPU ports,
* as well as any local member of their bridge group.
/* Frames from standalone user ports can only egress on the
* upstream port.
*/
if (!dsa_port_bridge_dev_get(dp))
return BIT(dsa_switch_upstream_port(ds));
/* Frames from bridged user ports can egress any local DSA
* links and CPU ports, as well as any local member of their
* bridge group.
*/
dsa_switch_for_each_port(other_dp, ds)
if (other_dp->type == DSA_PORT_TYPE_CPU ||
@ -1623,21 +1630,11 @@ static int mv88e6xxx_fid_map_vlan(struct mv88e6xxx_chip *chip,
int mv88e6xxx_fid_map(struct mv88e6xxx_chip *chip, unsigned long *fid_bitmap)
{
int i, err;
u16 fid;
bitmap_zero(fid_bitmap, MV88E6XXX_N_FID);
/* Set every FID bit used by the (un)bridged ports */
for (i = 0; i < mv88e6xxx_num_ports(chip); ++i) {
err = mv88e6xxx_port_get_fid(chip, i, &fid);
if (err)
return err;
set_bit(fid, fid_bitmap);
}
/* Set every FID bit used by the VLAN entries */
/* Every FID has an associated VID, so walking the VTU
* will discover the full set of FIDs in use.
*/
return mv88e6xxx_vtu_walk(chip, mv88e6xxx_fid_map_vlan, fid_bitmap);
}
@ -1650,10 +1647,7 @@ static int mv88e6xxx_atu_new(struct mv88e6xxx_chip *chip, u16 *fid)
if (err)
return err;
/* The reset value 0x000 is used to indicate that multiple address
* databases are not needed. Return the next positive available.
*/
*fid = find_next_zero_bit(fid_bitmap, MV88E6XXX_N_FID, 1);
*fid = find_first_zero_bit(fid_bitmap, MV88E6XXX_N_FID);
if (unlikely(*fid >= mv88e6xxx_num_databases(chip)))
return -ENOSPC;
@ -2145,6 +2139,9 @@ static int mv88e6xxx_port_vlan_join(struct mv88e6xxx_chip *chip, int port,
if (!vlan.valid) {
memset(&vlan, 0, sizeof(vlan));
if (vid == MV88E6XXX_VID_STANDALONE)
vlan.policy = true;
err = mv88e6xxx_atu_new(chip, &vlan.fid);
if (err)
return err;
@ -2487,6 +2484,10 @@ static int mv88e6xxx_port_bridge_join(struct dsa_switch *ds, int port,
if (err)
goto unlock;
err = mv88e6xxx_port_set_map_da(chip, port, true);
if (err)
return err;
err = mv88e6xxx_port_commit_pvid(chip, port);
if (err)
goto unlock;
@ -2521,6 +2522,12 @@ static void mv88e6xxx_port_bridge_leave(struct dsa_switch *ds, int port,
mv88e6xxx_port_vlan_map(chip, port))
dev_err(ds->dev, "failed to remap in-chip Port VLAN\n");
err = mv88e6xxx_port_set_map_da(chip, port, false);
if (err)
dev_err(ds->dev,
"port %d failed to restore map-DA: %pe\n",
port, ERR_PTR(err));
err = mv88e6xxx_port_commit_pvid(chip, port);
if (err)
dev_err(ds->dev,
@ -2918,12 +2925,13 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
return err;
/* Port Control 2: don't force a good FCS, set the MTU size to
* 10222 bytes, disable 802.1q tags checking, don't discard tagged or
* untagged frames on this port, do a destination address lookup on all
* received packets as usual, disable ARP mirroring and don't send a
* copy of all transmitted/received frames on this port to the CPU.
* 10222 bytes, disable 802.1q tags checking, don't discard
* tagged or untagged frames on this port, skip destination
* address lookup on user ports, disable ARP mirroring and don't
* send a copy of all transmitted/received frames on this port
* to the CPU.
*/
err = mv88e6xxx_port_set_map_da(chip, port);
err = mv88e6xxx_port_set_map_da(chip, port, !dsa_is_user_port(ds, port));
if (err)
return err;
@ -2931,8 +2939,44 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
if (err)
return err;
/* On chips that support it, set all downstream DSA ports'
* VLAN policy to TRAP. In combination with loading
* MV88E6XXX_VID_STANDALONE as a policy entry in the VTU, this
* provides a better isolation barrier between standalone
* ports, as the ATU is bypassed on any intermediate switches
* between the incoming port and the CPU.
*/
if (dsa_is_downstream_port(ds, port) &&
chip->info->ops->port_set_policy) {
err = chip->info->ops->port_set_policy(chip, port,
MV88E6XXX_POLICY_MAPPING_VTU,
MV88E6XXX_POLICY_ACTION_TRAP);
if (err)
return err;
}
/* User ports start out in standalone mode and 802.1Q is
* therefore disabled. On DSA ports, all valid VIDs are always
* loaded in the VTU - therefore, enable 802.1Q in order to take
* advantage of VLAN policy on chips that supports it.
*/
err = mv88e6xxx_port_set_8021q_mode(chip, port,
MV88E6XXX_PORT_CTL2_8021Q_MODE_DISABLED);
dsa_is_user_port(ds, port) ?
MV88E6XXX_PORT_CTL2_8021Q_MODE_DISABLED :
MV88E6XXX_PORT_CTL2_8021Q_MODE_SECURE);
if (err)
return err;
/* Bind MV88E6XXX_VID_STANDALONE to MV88E6XXX_FID_STANDALONE by
* virtue of the fact that mv88e6xxx_atu_new() will pick it as
* the first free FID. This will be used as the private PVID for
* unbridged ports. Shared (DSA and CPU) ports must also be
* members of this VID, in order to trap all frames assigned to
* it to the CPU.
*/
err = mv88e6xxx_port_vlan_join(chip, port, MV88E6XXX_VID_STANDALONE,
MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED,
false);
if (err)
return err;
@ -2945,7 +2989,7 @@ static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port)
* relying on their port default FID.
*/
err = mv88e6xxx_port_vlan_join(chip, port, MV88E6XXX_VID_BRIDGED,
MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNTAGGED,
MV88E6XXX_G1_VTU_DATA_MEMBER_TAG_UNMODIFIED,
false);
if (err)
return err;
@ -3634,6 +3678,7 @@ static const struct mv88e6xxx_ops mv88e6097_ops = {
.port_sync_link = mv88e6185_port_sync_link,
.port_set_speed_duplex = mv88e6185_port_set_speed_duplex,
.port_tag_remap = mv88e6095_port_tag_remap,
.port_set_policy = mv88e6352_port_set_policy,
.port_set_frame_mode = mv88e6351_port_set_frame_mode,
.port_set_ucast_flood = mv88e6352_port_set_ucast_flood,
.port_set_mcast_flood = mv88e6352_port_set_mcast_flood,

View File

@ -179,6 +179,7 @@ struct mv88e6xxx_vtu_entry {
u16 fid;
u8 sid;
bool valid;
bool policy;
u8 member[DSA_MAX_PORTS];
u8 state[DSA_MAX_PORTS];
};

View File

@ -46,6 +46,7 @@
/* Offset 0x02: VTU FID Register */
#define MV88E6352_G1_VTU_FID 0x02
#define MV88E6352_G1_VTU_FID_VID_POLICY 0x1000
#define MV88E6352_G1_VTU_FID_MASK 0x0fff
/* Offset 0x03: VTU SID Register */

View File

@ -27,7 +27,7 @@ static int mv88e6xxx_g1_vtu_fid_read(struct mv88e6xxx_chip *chip,
return err;
entry->fid = val & MV88E6352_G1_VTU_FID_MASK;
entry->policy = !!(val & MV88E6352_G1_VTU_FID_VID_POLICY);
return 0;
}
@ -36,6 +36,9 @@ static int mv88e6xxx_g1_vtu_fid_write(struct mv88e6xxx_chip *chip,
{
u16 val = entry->fid & MV88E6352_G1_VTU_FID_MASK;
if (entry->policy)
val |= MV88E6352_G1_VTU_FID_VID_POLICY;
return mv88e6xxx_g1_write(chip, MV88E6352_G1_VTU_FID, val);
}

View File

@ -1278,7 +1278,7 @@ int mv88e6xxx_port_drop_untagged(struct mv88e6xxx_chip *chip, int port,
return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL2, new);
}
int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port)
int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port, bool map)
{
u16 reg;
int err;
@ -1287,7 +1287,10 @@ int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port)
if (err)
return err;
reg |= MV88E6XXX_PORT_CTL2_MAP_DA;
if (map)
reg |= MV88E6XXX_PORT_CTL2_MAP_DA;
else
reg &= ~MV88E6XXX_PORT_CTL2_MAP_DA;
return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_CTL2, reg);
}

View File

@ -425,7 +425,7 @@ int mv88e6185_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
int mv88e6xxx_port_drop_untagged(struct mv88e6xxx_chip *chip, int port,
bool drop_untagged);
int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port);
int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port, bool map);
int mv88e6095_port_set_upstream_port(struct mv88e6xxx_chip *chip, int port,
int upstream_port);
int mv88e6xxx_port_set_mirror(struct mv88e6xxx_chip *chip, int port,

View File

@ -591,6 +591,24 @@ static inline bool dsa_is_upstream_port(struct dsa_switch *ds, int port)
return port == dsa_upstream_port(ds, port);
}
/* Return true if this is a DSA port leading away from the CPU */
static inline bool dsa_is_downstream_port(struct dsa_switch *ds, int port)
{
return dsa_is_dsa_port(ds, port) && !dsa_is_upstream_port(ds, port);
}
/* Return the local port used to reach the CPU port */
static inline unsigned int dsa_switch_upstream_port(struct dsa_switch *ds)
{
struct dsa_port *dp;
dsa_switch_for_each_available_port(dp, ds) {
return dsa_upstream_port(ds, dp->index);
}
return ds->num_ports;
}
/* Return true if @upstream_ds is an upstream switch of @downstream_ds, meaning
* that the routing port from @downstream_ds to @upstream_ds is also the port
* which @downstream_ds uses to reach its dedicated CPU.

View File

@ -28,8 +28,9 @@ h2_destroy()
switch_create()
{
# 10 Seconds ageing time.
ip link add dev br0 type bridge vlan_filtering 1 ageing_time 1000 \
ip link add dev br0 type bridge \
vlan_filtering 1 \
ageing_time $LOW_AGEING_TIME \
mcast_snooping 0
ip link set dev $swp1 master br0

View File

@ -27,8 +27,9 @@ h2_destroy()
switch_create()
{
# 10 Seconds ageing time.
ip link add dev br0 type bridge ageing_time 1000 mcast_snooping 0
ip link add dev br0 type bridge \
ageing_time $LOW_AGEING_TIME \
mcast_snooping 0
ip link set dev $swp1 master br0
ip link set dev $swp2 master br0

View File

@ -41,6 +41,8 @@ NETIF_CREATE=yes
# Timeout (in seconds) before ping exits regardless of how many packets have
# been sent or received
PING_TIMEOUT=5
# Minimum ageing_time (in centiseconds) supported by hardware
LOW_AGEING_TIME=1000
# Flag for tc match, supposed to be skip_sw/skip_hw which means do not process
# filter by software/hardware
TC_FLAG=skip_hw

View File

@ -24,6 +24,7 @@ PING_COUNT=${PING_COUNT:=10}
PING_TIMEOUT=${PING_TIMEOUT:=5}
WAIT_TIMEOUT=${WAIT_TIMEOUT:=20}
INTERFACE_TIMEOUT=${INTERFACE_TIMEOUT:=600}
LOW_AGEING_TIME=${LOW_AGEING_TIME:=1000}
REQUIRE_JQ=${REQUIRE_JQ:=yes}
REQUIRE_MZ=${REQUIRE_MZ:=yes}