Skip to content

Commit

Permalink
Merge branch 'mlxsw-rif-pvid'
Browse files Browse the repository at this point in the history
Petr Machata says:

====================
mlxsw: Manage RIF across PVID changes

The mlxsw driver currently makes the assumption that the user applies
configuration in a bottom-up manner. Thus netdevices need to be added to
the bridge before IP addresses are configured on that bridge or SVI added
on top of it. Enslaving a netdevice to another netdevice that already has
uppers is in fact forbidden by mlxsw for this reason. Despite this safety,
it is rather easy to get into situations where the offloaded configuration
is just plain wrong.

As an example, take a front panel port, configure an IP address: it gets a
RIF. Now enslave the port to the bridge, and the RIF is gone. Remove the
port from the bridge again, but the RIF never comes back. There is a number
of similar situations, where changing the configuration there and back
utterly breaks the offload.

The situation is going to be made better by implementing a range of replays
and post-hoc offloads.

In this patch set, address the ordering issues related to creation of
bridge RIFs. Currently, mlxsw has several shortcomings with regards to RIF
handling due to PVID changes:

- In order to cause RIF for a bridge device to be created, the user is
  expected first to set PVID, then to add an IP address. The reverse
  ordering is disallowed, which is not very user-friendly.

- When such bridge gets a VLAN upper whose VID was the same as the existing
  PVID, and this VLAN netdevice gets an IP address, a RIF is created for
  this netdevice. The new RIF is then assigned to the 802.1Q FID for the
  given VID. This results in a working configuration. However, then, when
  the VLAN netdevice is removed again, the RIF for the bridge itself is
  never reassociated to the PVID.

- PVID cannot be changed once the bridge has uppers. Presumably this is
  because the driver does not manage RIFs properly in face of PVID changes.
  However, as the previous point shows, it is still possible to get into
  invalid configurations.

This patch set addresses these issues and relaxes some of the ordering
requirements that mlxsw had. The patch set proceeds as follows:

- In patch #1, pass extack to mlxsw_sp_br_ban_rif_pvid_change()

- To relax ordering between setting PVID and adding an IP address to a
  bridge, mlxsw must be able to request that a RIF is created with a given
  VLAN ID, instead of trying to deduce it from the current netdevice
  settings, which do not reflect the user-requested values yet. This is
  done in patches #2 and #3.

- Similarly, mlxsw_sp_inetaddr_bridge_event() will need to make decisions
  based on the user-requested value of PVID, not the current value. Thus in
  patches #4 and #5, add a new argument which carries the requested PVID
  value.

- Finally in patch #6 relax the ban on PVID changes when a bridge has
  uppers. Instead, add the logic necessary for creation of a RIF as a
  result of PVID change.

- Relevant selftests are presented afterwards. In patch #7 a preparatory
  helper is added to lib.sh. Patches #8, #9, #10 and #11 include selftests
  themselves.
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
davem330 committed Jul 14, 2023
2 parents f16276a + 9cbb3da commit 382d7dc
Show file tree
Hide file tree
Showing 9 changed files with 643 additions and 58 deletions.
169 changes: 149 additions & 20 deletions drivers/net/ethernet/mellanox/mlxsw/spectrum_router.c
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ struct mlxsw_sp_rif_ops {
struct netlink_ext_ack *extack);
void (*deconfigure)(struct mlxsw_sp_rif *rif);
struct mlxsw_sp_fid * (*fid_get)(struct mlxsw_sp_rif *rif,
const struct mlxsw_sp_rif_params *params,
struct netlink_ext_ack *extack);
void (*fdb_del)(struct mlxsw_sp_rif *rif, const char *mac);
};
Expand Down Expand Up @@ -8300,7 +8301,7 @@ mlxsw_sp_rif_create(struct mlxsw_sp *mlxsw_sp,
rif->rif_entries = rif_entries;

if (ops->fid_get) {
fid = ops->fid_get(rif, extack);
fid = ops->fid_get(rif, params, extack);
if (IS_ERR(fid)) {
err = PTR_ERR(fid);
goto err_fid_get;
Expand Down Expand Up @@ -8410,6 +8411,110 @@ void mlxsw_sp_rif_destroy_by_dev(struct mlxsw_sp *mlxsw_sp,
mutex_unlock(&mlxsw_sp->router->lock);
}

static void mlxsw_sp_rif_destroy_vlan_upper(struct mlxsw_sp *mlxsw_sp,
struct net_device *br_dev,
u16 vid)
{
struct net_device *upper_dev;
struct mlxsw_sp_crif *crif;

rcu_read_lock();
upper_dev = __vlan_find_dev_deep_rcu(br_dev, htons(ETH_P_8021Q), vid);
rcu_read_unlock();

if (!upper_dev)
return;

crif = mlxsw_sp_crif_lookup(mlxsw_sp->router, upper_dev);
if (!crif || !crif->rif)
return;

mlxsw_sp_rif_destroy(crif->rif);
}

static int mlxsw_sp_inetaddr_bridge_event(struct mlxsw_sp *mlxsw_sp,
struct net_device *l3_dev,
int lower_pvid,
unsigned long event,
struct netlink_ext_ack *extack);

int mlxsw_sp_router_bridge_vlan_add(struct mlxsw_sp *mlxsw_sp,
struct net_device *br_dev,
u16 new_vid, bool is_pvid,
struct netlink_ext_ack *extack)
{
struct mlxsw_sp_rif *old_rif;
struct mlxsw_sp_rif *new_rif;
struct net_device *upper_dev;
u16 old_pvid = 0;
u16 new_pvid;
int err = 0;

mutex_lock(&mlxsw_sp->router->lock);
old_rif = mlxsw_sp_rif_find_by_dev(mlxsw_sp, br_dev);
if (old_rif) {
/* If the RIF on the bridge is not a VLAN RIF, we shouldn't have
* gotten a PVID notification.
*/
if (WARN_ON(old_rif->ops->type != MLXSW_SP_RIF_TYPE_VLAN))
old_rif = NULL;
else
old_pvid = mlxsw_sp_fid_8021q_vid(old_rif->fid);
}

if (is_pvid)
new_pvid = new_vid;
else if (old_pvid == new_vid)
new_pvid = 0;
else
goto out;

if (old_pvid == new_pvid)
goto out;

if (new_pvid) {
struct mlxsw_sp_rif_params params = {
.dev = br_dev,
.vid = new_pvid,
};

/* If there is a VLAN upper with the same VID as the new PVID,
* kill its RIF, if there is one.
*/
mlxsw_sp_rif_destroy_vlan_upper(mlxsw_sp, br_dev, new_pvid);

if (mlxsw_sp_dev_addr_list_empty(br_dev))
goto out;
new_rif = mlxsw_sp_rif_create(mlxsw_sp, &params, extack);
if (IS_ERR(new_rif)) {
err = PTR_ERR(new_rif);
goto out;
}

if (old_pvid)
mlxsw_sp_rif_migrate_destroy(mlxsw_sp, old_rif, new_rif,
true);
} else {
mlxsw_sp_rif_destroy(old_rif);
}

if (old_pvid) {
rcu_read_lock();
upper_dev = __vlan_find_dev_deep_rcu(br_dev, htons(ETH_P_8021Q),
old_pvid);
rcu_read_unlock();
if (upper_dev)
err = mlxsw_sp_inetaddr_bridge_event(mlxsw_sp,
upper_dev,
new_pvid,
NETDEV_UP, extack);
}

out:
mutex_unlock(&mlxsw_sp->router->lock);
return err;
}

static void
mlxsw_sp_rif_subport_params_init(struct mlxsw_sp_rif_params *params,
struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan)
Expand Down Expand Up @@ -8664,21 +8769,24 @@ __mlxsw_sp_port_vlan_router_join(struct mlxsw_sp_port_vlan *mlxsw_sp_port_vlan,
{
struct mlxsw_sp_port *mlxsw_sp_port = mlxsw_sp_port_vlan->mlxsw_sp_port;
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
struct mlxsw_sp_rif_params params = {
.dev = l3_dev,
};
struct mlxsw_sp_rif_params params;
u16 vid = mlxsw_sp_port_vlan->vid;
struct mlxsw_sp_rif *rif;
struct mlxsw_sp_fid *fid;
int err;

params = (struct mlxsw_sp_rif_params) {
.dev = l3_dev,
.vid = vid,
};

mlxsw_sp_rif_subport_params_init(&params, mlxsw_sp_port_vlan);
rif = mlxsw_sp_rif_subport_get(mlxsw_sp, &params, extack);
if (IS_ERR(rif))
return PTR_ERR(rif);

/* FID was already created, just take a reference */
fid = rif->ops->fid_get(rif, extack);
fid = rif->ops->fid_get(rif, &params, extack);
err = mlxsw_sp_fid_port_vid_map(fid, mlxsw_sp_port, vid);
if (err)
goto err_fid_port_vid_map;
Expand Down Expand Up @@ -8822,13 +8930,15 @@ static int mlxsw_sp_inetaddr_lag_event(struct net_device *lag_dev,

static int mlxsw_sp_inetaddr_bridge_event(struct mlxsw_sp *mlxsw_sp,
struct net_device *l3_dev,
int lower_pvid,
unsigned long event,
struct netlink_ext_ack *extack)
{
struct mlxsw_sp_rif_params params = {
.dev = l3_dev,
};
struct mlxsw_sp_rif *rif;
int err;

switch (event) {
case NETDEV_UP:
Expand All @@ -8840,7 +8950,21 @@ static int mlxsw_sp_inetaddr_bridge_event(struct mlxsw_sp *mlxsw_sp,
NL_SET_ERR_MSG_MOD(extack, "Adding an IP address to 802.1ad bridge is not supported");
return -EOPNOTSUPP;
}
err = br_vlan_get_pvid(l3_dev, &params.vid);
if (err)
return err;
if (!params.vid)
return 0;
} else if (is_vlan_dev(l3_dev)) {
params.vid = vlan_dev_vlan_id(l3_dev);

/* If the VID matches PVID of the bridge below, the
* bridge owns the RIF for this VLAN. Don't do anything.
*/
if ((int)params.vid == lower_pvid)
return 0;
}

rif = mlxsw_sp_rif_create(mlxsw_sp, &params, extack);
if (IS_ERR(rif))
return PTR_ERR(rif);
Expand All @@ -8861,19 +8985,27 @@ static int mlxsw_sp_inetaddr_vlan_event(struct mlxsw_sp *mlxsw_sp,
{
struct net_device *real_dev = vlan_dev_real_dev(vlan_dev);
u16 vid = vlan_dev_vlan_id(vlan_dev);
u16 lower_pvid;
int err;

if (netif_is_bridge_port(vlan_dev))
return 0;

if (mlxsw_sp_port_dev_check(real_dev))
if (mlxsw_sp_port_dev_check(real_dev)) {
return mlxsw_sp_inetaddr_port_vlan_event(vlan_dev, real_dev,
event, vid, extack);
else if (netif_is_lag_master(real_dev))
} else if (netif_is_lag_master(real_dev)) {
return __mlxsw_sp_inetaddr_lag_event(vlan_dev, real_dev, event,
vid, extack);
else if (netif_is_bridge_master(real_dev) && br_vlan_enabled(real_dev))
return mlxsw_sp_inetaddr_bridge_event(mlxsw_sp, vlan_dev, event,
} else if (netif_is_bridge_master(real_dev) &&
br_vlan_enabled(real_dev)) {
err = br_vlan_get_pvid(real_dev, &lower_pvid);
if (err)
return err;
return mlxsw_sp_inetaddr_bridge_event(mlxsw_sp, vlan_dev,
lower_pvid, event,
extack);
}

return 0;
}
Expand Down Expand Up @@ -9008,7 +9140,7 @@ static int __mlxsw_sp_inetaddr_event(struct mlxsw_sp *mlxsw_sp,
else if (netif_is_lag_master(dev))
return mlxsw_sp_inetaddr_lag_event(dev, event, extack);
else if (netif_is_bridge_master(dev))
return mlxsw_sp_inetaddr_bridge_event(mlxsw_sp, dev, event,
return mlxsw_sp_inetaddr_bridge_event(mlxsw_sp, dev, -1, event,
extack);
else if (is_vlan_dev(dev))
return mlxsw_sp_inetaddr_vlan_event(mlxsw_sp, dev, event,
Expand Down Expand Up @@ -9724,6 +9856,7 @@ static void mlxsw_sp_rif_subport_deconfigure(struct mlxsw_sp_rif *rif)

static struct mlxsw_sp_fid *
mlxsw_sp_rif_subport_fid_get(struct mlxsw_sp_rif *rif,
const struct mlxsw_sp_rif_params *params,
struct netlink_ext_ack *extack)
{
return mlxsw_sp_fid_rfid_get(rif->mlxsw_sp, rif->rif_index);
Expand Down Expand Up @@ -9836,6 +9969,7 @@ static void mlxsw_sp_rif_fid_deconfigure(struct mlxsw_sp_rif *rif)

static struct mlxsw_sp_fid *
mlxsw_sp_rif_fid_fid_get(struct mlxsw_sp_rif *rif,
const struct mlxsw_sp_rif_params *params,
struct netlink_ext_ack *extack)
{
int rif_ifindex = mlxsw_sp_rif_dev_ifindex(rif);
Expand Down Expand Up @@ -9869,27 +10003,22 @@ static const struct mlxsw_sp_rif_ops mlxsw_sp_rif_fid_ops = {

static struct mlxsw_sp_fid *
mlxsw_sp_rif_vlan_fid_get(struct mlxsw_sp_rif *rif,
const struct mlxsw_sp_rif_params *params,
struct netlink_ext_ack *extack)
{
struct net_device *dev = mlxsw_sp_rif_dev(rif);
struct net_device *br_dev;
u16 vid;
int err;

if (WARN_ON(!params->vid))
return ERR_PTR(-EINVAL);

if (is_vlan_dev(dev)) {
vid = vlan_dev_vlan_id(dev);
br_dev = vlan_dev_real_dev(dev);
if (WARN_ON(!netif_is_bridge_master(br_dev)))
return ERR_PTR(-EINVAL);
} else {
err = br_vlan_get_pvid(dev, &vid);
if (err < 0 || !vid) {
NL_SET_ERR_MSG_MOD(extack, "Couldn't determine bridge PVID");
return ERR_PTR(-EINVAL);
}
}

return mlxsw_sp_fid_8021q_get(rif->mlxsw_sp, vid);
return mlxsw_sp_fid_8021q_get(rif->mlxsw_sp, params->vid);
}

static void mlxsw_sp_rif_vlan_fdb_del(struct mlxsw_sp_rif *rif, const char *mac)
Expand Down
4 changes: 4 additions & 0 deletions drivers/net/ethernet/mellanox/mlxsw/spectrum_router.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ int mlxsw_sp_ipip_ecn_encap_init(struct mlxsw_sp *mlxsw_sp);
int mlxsw_sp_ipip_ecn_decap_init(struct mlxsw_sp *mlxsw_sp);
struct net_device *
mlxsw_sp_ipip_netdev_ul_dev_get(const struct net_device *ol_dev);
int mlxsw_sp_router_bridge_vlan_add(struct mlxsw_sp *mlxsw_sp,
struct net_device *dev,
u16 new_vid, bool is_pvid,
struct netlink_ext_ack *extack);
int mlxsw_sp_router_port_join_lag(struct mlxsw_sp_port *mlxsw_sp_port,
struct net_device *lag_dev,
struct netlink_ext_ack *extack);
Expand Down
32 changes: 9 additions & 23 deletions drivers/net/ethernet/mellanox/mlxsw/spectrum_switchdev.c
Original file line number Diff line number Diff line change
Expand Up @@ -1479,29 +1479,15 @@ mlxsw_sp_bridge_port_vlan_add(struct mlxsw_sp_port *mlxsw_sp_port,
}

static int
mlxsw_sp_br_ban_rif_pvid_change(struct mlxsw_sp *mlxsw_sp,
const struct net_device *br_dev,
const struct switchdev_obj_port_vlan *vlan)
mlxsw_sp_br_rif_pvid_change(struct mlxsw_sp *mlxsw_sp,
struct net_device *br_dev,
const struct switchdev_obj_port_vlan *vlan,
struct netlink_ext_ack *extack)
{
u16 pvid;

pvid = mlxsw_sp_rif_vid(mlxsw_sp, br_dev);
if (!pvid)
return 0;

if (vlan->flags & BRIDGE_VLAN_INFO_PVID) {
if (vlan->vid != pvid) {
netdev_err(br_dev, "Can't change PVID, it's used by router interface\n");
return -EBUSY;
}
} else {
if (vlan->vid == pvid) {
netdev_err(br_dev, "Can't remove PVID, it's used by router interface\n");
return -EBUSY;
}
}
bool flag_pvid = vlan->flags & BRIDGE_VLAN_INFO_PVID;

return 0;
return mlxsw_sp_router_bridge_vlan_add(mlxsw_sp, br_dev, vlan->vid,
flag_pvid, extack);
}

static int mlxsw_sp_port_vlans_add(struct mlxsw_sp_port *mlxsw_sp_port,
Expand All @@ -1518,8 +1504,8 @@ static int mlxsw_sp_port_vlans_add(struct mlxsw_sp_port *mlxsw_sp_port,
int err = 0;

if (br_vlan_enabled(orig_dev))
err = mlxsw_sp_br_ban_rif_pvid_change(mlxsw_sp,
orig_dev, vlan);
err = mlxsw_sp_br_rif_pvid_change(mlxsw_sp, orig_dev,
vlan, extack);
if (!err)
err = -EOPNOTSUPP;
return err;
Expand Down
2 changes: 2 additions & 0 deletions tools/testing/selftests/net/forwarding/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ TEST_PROGS = bridge_igmp.sh \
q_in_vni.sh \
router_bridge.sh \
router_bridge_vlan.sh \
router_bridge_pvid_vlan_upper.sh \
router_bridge_vlan_upper_pvid.sh \
router_broadcast.sh \
router_mpath_nh_res.sh \
router_mpath_nh.sh \
Expand Down
18 changes: 18 additions & 0 deletions tools/testing/selftests/net/forwarding/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1215,6 +1215,15 @@ ping_test()
log_test "ping$3"
}

ping_test_fails()
{
RET=0

ping_do $1 $2
check_fail $?
log_test "ping fails$3"
}

ping6_do()
{
local if_name=$1
Expand All @@ -1237,6 +1246,15 @@ ping6_test()
log_test "ping6$3"
}

ping6_test_fails()
{
RET=0

ping6_do $1 $2
check_fail $?
log_test "ping6 fails$3"
}

learning_test()
{
local bridge=$1
Expand Down
Loading

0 comments on commit 382d7dc

Please sign in to comment.