net: bridge: resolve forwarding path for VLAN tag actions in bridge devices
authorFelix Fietkau <nbd@nbd.name>
Wed, 24 Mar 2021 01:30:35 +0000 (02:30 +0100)
committerDavid S. Miller <davem@davemloft.net>
Wed, 24 Mar 2021 19:48:38 +0000 (12:48 -0700)
Depending on the VLAN settings of the bridge and the port, the bridge can
either add or remove a tag. When vlan filtering is enabled, the fdb lookup
also needs to know the VLAN tag/proto for the destination address
To provide this, keep track of the stack of VLAN tags for the path in the
lookup context

Signed-off-by: Felix Fietkau <nbd@nbd.name>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/linux/netdevice.h
net/8021q/vlan_dev.c
net/bridge/br_device.c
net/bridge/br_private.h
net/bridge/br_vlan.c

index a24270b..344d9c0 100644 (file)
@@ -862,10 +862,20 @@ struct net_device_path {
                        u16             id;
                        __be16          proto;
                } encap;
+               struct {
+                       enum {
+                               DEV_PATH_BR_VLAN_KEEP,
+                               DEV_PATH_BR_VLAN_TAG,
+                               DEV_PATH_BR_VLAN_UNTAG,
+                       }               vlan_mode;
+                       u16             vlan_id;
+                       __be16          vlan_proto;
+               } bridge;
        };
 };
 
 #define NET_DEVICE_PATH_STACK_MAX      5
+#define NET_DEVICE_PATH_VLAN_MAX       2
 
 struct net_device_path_stack {
        int                     num_paths;
@@ -875,6 +885,12 @@ struct net_device_path_stack {
 struct net_device_path_ctx {
        const struct net_device *dev;
        const u8                *daddr;
+
+       int                     num_vlans;
+       struct {
+               u16             id;
+               __be16          proto;
+       } vlan[NET_DEVICE_PATH_VLAN_MAX];
 };
 
 enum tc_setup_type {
index 1b1955a..4db3f06 100644 (file)
@@ -786,6 +786,12 @@ static int vlan_dev_fill_forward_path(struct net_device_path_ctx *ctx,
        path->encap.proto = vlan->vlan_proto;
        path->dev = ctx->dev;
        ctx->dev = vlan->real_dev;
+       if (ctx->num_vlans >= ARRAY_SIZE(ctx->vlan))
+               return -ENOSPC;
+
+       ctx->vlan[ctx->num_vlans].id = vlan->vlan_id;
+       ctx->vlan[ctx->num_vlans].proto = vlan->vlan_proto;
+       ctx->num_vlans++;
 
        return 0;
 }
index c241719..0c72503 100644 (file)
@@ -396,7 +396,10 @@ static int br_fill_forward_path(struct net_device_path_ctx *ctx,
                return -1;
 
        br = netdev_priv(ctx->dev);
-       f = br_fdb_find_rcu(br, ctx->daddr, 0);
+
+       br_vlan_fill_forward_path_pvid(br, ctx, path);
+
+       f = br_fdb_find_rcu(br, ctx->daddr, path->bridge.vlan_id);
        if (!f || !f->dst)
                return -1;
 
@@ -404,10 +407,28 @@ static int br_fill_forward_path(struct net_device_path_ctx *ctx,
        if (!dst)
                return -1;
 
+       if (br_vlan_fill_forward_path_mode(br, dst, path))
+               return -1;
+
        path->type = DEV_PATH_BRIDGE;
        path->dev = dst->br->dev;
        ctx->dev = dst->dev;
 
+       switch (path->bridge.vlan_mode) {
+       case DEV_PATH_BR_VLAN_TAG:
+               if (ctx->num_vlans >= ARRAY_SIZE(ctx->vlan))
+                       return -ENOSPC;
+               ctx->vlan[ctx->num_vlans].id = path->bridge.vlan_id;
+               ctx->vlan[ctx->num_vlans].proto = path->bridge.vlan_proto;
+               ctx->num_vlans++;
+               break;
+       case DEV_PATH_BR_VLAN_UNTAG:
+               ctx->num_vlans--;
+               break;
+       case DEV_PATH_BR_VLAN_KEEP:
+               break;
+       }
+
        return 0;
 }
 
index d7d167e..5074799 100644 (file)
@@ -1118,6 +1118,13 @@ void br_vlan_notify(const struct net_bridge *br,
 bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
                             const struct net_bridge_vlan *range_end);
 
+void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+                                   struct net_device_path_ctx *ctx,
+                                   struct net_device_path *path);
+int br_vlan_fill_forward_path_mode(struct net_bridge *br,
+                                  struct net_bridge_port *dst,
+                                  struct net_device_path *path);
+
 static inline struct net_bridge_vlan_group *br_vlan_group(
                                        const struct net_bridge *br)
 {
@@ -1277,6 +1284,19 @@ static inline int nbp_get_num_vlan_infos(struct net_bridge_port *p,
        return 0;
 }
 
+static inline void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+                                                 struct net_device_path_ctx *ctx,
+                                                 struct net_device_path *path)
+{
+}
+
+static inline int br_vlan_fill_forward_path_mode(struct net_bridge *br,
+                                                struct net_bridge_port *dst,
+                                                struct net_device_path *path)
+{
+       return 0;
+}
+
 static inline struct net_bridge_vlan_group *br_vlan_group(
                                        const struct net_bridge *br)
 {
index 6f961cb..c92240b 100644 (file)
@@ -1339,6 +1339,59 @@ int br_vlan_get_pvid_rcu(const struct net_device *dev, u16 *p_pvid)
 }
 EXPORT_SYMBOL_GPL(br_vlan_get_pvid_rcu);
 
+void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+                                   struct net_device_path_ctx *ctx,
+                                   struct net_device_path *path)
+{
+       struct net_bridge_vlan_group *vg;
+       int idx = ctx->num_vlans - 1;
+       u16 vid;
+
+       path->bridge.vlan_mode = DEV_PATH_BR_VLAN_KEEP;
+
+       if (!br_opt_get(br, BROPT_VLAN_ENABLED))
+               return;
+
+       vg = br_vlan_group(br);
+
+       if (idx >= 0 &&
+           ctx->vlan[idx].proto == br->vlan_proto) {
+               vid = ctx->vlan[idx].id;
+       } else {
+               path->bridge.vlan_mode = DEV_PATH_BR_VLAN_TAG;
+               vid = br_get_pvid(vg);
+       }
+
+       path->bridge.vlan_id = vid;
+       path->bridge.vlan_proto = br->vlan_proto;
+}
+
+int br_vlan_fill_forward_path_mode(struct net_bridge *br,
+                                  struct net_bridge_port *dst,
+                                  struct net_device_path *path)
+{
+       struct net_bridge_vlan_group *vg;
+       struct net_bridge_vlan *v;
+
+       if (!br_opt_get(br, BROPT_VLAN_ENABLED))
+               return 0;
+
+       vg = nbp_vlan_group_rcu(dst);
+       v = br_vlan_find(vg, path->bridge.vlan_id);
+       if (!v || !br_vlan_should_use(v))
+               return -EINVAL;
+
+       if (!(v->flags & BRIDGE_VLAN_INFO_UNTAGGED))
+               return 0;
+
+       if (path->bridge.vlan_mode == DEV_PATH_BR_VLAN_TAG)
+               path->bridge.vlan_mode = DEV_PATH_BR_VLAN_KEEP;
+       else
+               path->bridge.vlan_mode = DEV_PATH_BR_VLAN_UNTAG;
+
+       return 0;
+}
+
 int br_vlan_get_info(const struct net_device *dev, u16 vid,
                     struct bridge_vlan_info *p_vinfo)
 {