Commit bcf2766b authored by Felix Fietkau's avatar Felix Fietkau Committed by David S. Miller
Browse files

net: bridge: resolve forwarding path for VLAN tag actions in bridge devices



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: default avatarFelix Fietkau <nbd@nbd.name>
Signed-off-by: default avatarPablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent ec9d16ba
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -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 {
+6 −0
Original line number Diff line number Diff line
@@ -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;
}
+22 −1
Original line number Diff line number Diff line
@@ -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;
}

+20 −0
Original line number Diff line number Diff line
@@ -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)
{
+53 −0
Original line number Diff line number Diff line
@@ -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)
{