162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *	Bridge per vlan tunnel port dst_metadata netlink control interface
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *	Authors:
662306a36Sopenharmony_ci *	Roopa Prabhu		<roopa@cumulusnetworks.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci#include <linux/etherdevice.h>
1262306a36Sopenharmony_ci#include <net/rtnetlink.h>
1362306a36Sopenharmony_ci#include <net/net_namespace.h>
1462306a36Sopenharmony_ci#include <net/sock.h>
1562306a36Sopenharmony_ci#include <uapi/linux/if_bridge.h>
1662306a36Sopenharmony_ci#include <net/dst_metadata.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#include "br_private.h"
1962306a36Sopenharmony_ci#include "br_private_tunnel.h"
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic size_t __get_vlan_tinfo_size(void)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	return nla_total_size(0) + /* nest IFLA_BRIDGE_VLAN_TUNNEL_INFO */
2462306a36Sopenharmony_ci		  nla_total_size(sizeof(u32)) + /* IFLA_BRIDGE_VLAN_TUNNEL_ID */
2562306a36Sopenharmony_ci		  nla_total_size(sizeof(u16)) + /* IFLA_BRIDGE_VLAN_TUNNEL_VID */
2662306a36Sopenharmony_ci		  nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS */
2762306a36Sopenharmony_ci}
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cibool vlan_tunid_inrange(const struct net_bridge_vlan *v_curr,
3062306a36Sopenharmony_ci			const struct net_bridge_vlan *v_last)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	__be32 tunid_curr = tunnel_id_to_key32(v_curr->tinfo.tunnel_id);
3362306a36Sopenharmony_ci	__be32 tunid_last = tunnel_id_to_key32(v_last->tinfo.tunnel_id);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	return (be32_to_cpu(tunid_curr) - be32_to_cpu(tunid_last)) == 1;
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic int __get_num_vlan_tunnel_infos(struct net_bridge_vlan_group *vg)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	struct net_bridge_vlan *v, *vtbegin = NULL, *vtend = NULL;
4162306a36Sopenharmony_ci	int num_tinfos = 0;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	/* Count number of vlan infos */
4462306a36Sopenharmony_ci	list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
4562306a36Sopenharmony_ci		/* only a context, bridge vlan not activated */
4662306a36Sopenharmony_ci		if (!br_vlan_should_use(v) || !v->tinfo.tunnel_id)
4762306a36Sopenharmony_ci			continue;
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci		if (!vtbegin) {
5062306a36Sopenharmony_ci			goto initvars;
5162306a36Sopenharmony_ci		} else if ((v->vid - vtend->vid) == 1 &&
5262306a36Sopenharmony_ci			   vlan_tunid_inrange(v, vtend)) {
5362306a36Sopenharmony_ci			vtend = v;
5462306a36Sopenharmony_ci			continue;
5562306a36Sopenharmony_ci		} else {
5662306a36Sopenharmony_ci			if ((vtend->vid - vtbegin->vid) > 0)
5762306a36Sopenharmony_ci				num_tinfos += 2;
5862306a36Sopenharmony_ci			else
5962306a36Sopenharmony_ci				num_tinfos += 1;
6062306a36Sopenharmony_ci		}
6162306a36Sopenharmony_ciinitvars:
6262306a36Sopenharmony_ci		vtbegin = v;
6362306a36Sopenharmony_ci		vtend = v;
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	if (vtbegin && vtend) {
6762306a36Sopenharmony_ci		if ((vtend->vid - vtbegin->vid) > 0)
6862306a36Sopenharmony_ci			num_tinfos += 2;
6962306a36Sopenharmony_ci		else
7062306a36Sopenharmony_ci			num_tinfos += 1;
7162306a36Sopenharmony_ci	}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return num_tinfos;
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ciint br_get_vlan_tunnel_info_size(struct net_bridge_vlan_group *vg)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	int num_tinfos;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (!vg)
8162306a36Sopenharmony_ci		return 0;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	rcu_read_lock();
8462306a36Sopenharmony_ci	num_tinfos = __get_num_vlan_tunnel_infos(vg);
8562306a36Sopenharmony_ci	rcu_read_unlock();
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return num_tinfos * __get_vlan_tinfo_size();
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int br_fill_vlan_tinfo(struct sk_buff *skb, u16 vid,
9162306a36Sopenharmony_ci			      __be64 tunnel_id, u16 flags)
9262306a36Sopenharmony_ci{
9362306a36Sopenharmony_ci	__be32 tid = tunnel_id_to_key32(tunnel_id);
9462306a36Sopenharmony_ci	struct nlattr *tmap;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	tmap = nla_nest_start_noflag(skb, IFLA_BRIDGE_VLAN_TUNNEL_INFO);
9762306a36Sopenharmony_ci	if (!tmap)
9862306a36Sopenharmony_ci		return -EMSGSIZE;
9962306a36Sopenharmony_ci	if (nla_put_u32(skb, IFLA_BRIDGE_VLAN_TUNNEL_ID,
10062306a36Sopenharmony_ci			be32_to_cpu(tid)))
10162306a36Sopenharmony_ci		goto nla_put_failure;
10262306a36Sopenharmony_ci	if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_VID,
10362306a36Sopenharmony_ci			vid))
10462306a36Sopenharmony_ci		goto nla_put_failure;
10562306a36Sopenharmony_ci	if (nla_put_u16(skb, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS,
10662306a36Sopenharmony_ci			flags))
10762306a36Sopenharmony_ci		goto nla_put_failure;
10862306a36Sopenharmony_ci	nla_nest_end(skb, tmap);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	return 0;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cinla_put_failure:
11362306a36Sopenharmony_ci	nla_nest_cancel(skb, tmap);
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	return -EMSGSIZE;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int br_fill_vlan_tinfo_range(struct sk_buff *skb,
11962306a36Sopenharmony_ci				    struct net_bridge_vlan *vtbegin,
12062306a36Sopenharmony_ci				    struct net_bridge_vlan *vtend)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	int err;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	if (vtend && (vtend->vid - vtbegin->vid) > 0) {
12562306a36Sopenharmony_ci		/* add range to skb */
12662306a36Sopenharmony_ci		err = br_fill_vlan_tinfo(skb, vtbegin->vid,
12762306a36Sopenharmony_ci					 vtbegin->tinfo.tunnel_id,
12862306a36Sopenharmony_ci					 BRIDGE_VLAN_INFO_RANGE_BEGIN);
12962306a36Sopenharmony_ci		if (err)
13062306a36Sopenharmony_ci			return err;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		err = br_fill_vlan_tinfo(skb, vtend->vid,
13362306a36Sopenharmony_ci					 vtend->tinfo.tunnel_id,
13462306a36Sopenharmony_ci					 BRIDGE_VLAN_INFO_RANGE_END);
13562306a36Sopenharmony_ci		if (err)
13662306a36Sopenharmony_ci			return err;
13762306a36Sopenharmony_ci	} else {
13862306a36Sopenharmony_ci		err = br_fill_vlan_tinfo(skb, vtbegin->vid,
13962306a36Sopenharmony_ci					 vtbegin->tinfo.tunnel_id,
14062306a36Sopenharmony_ci					 0);
14162306a36Sopenharmony_ci		if (err)
14262306a36Sopenharmony_ci			return err;
14362306a36Sopenharmony_ci	}
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	return 0;
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ciint br_fill_vlan_tunnel_info(struct sk_buff *skb,
14962306a36Sopenharmony_ci			     struct net_bridge_vlan_group *vg)
15062306a36Sopenharmony_ci{
15162306a36Sopenharmony_ci	struct net_bridge_vlan *vtbegin = NULL;
15262306a36Sopenharmony_ci	struct net_bridge_vlan *vtend = NULL;
15362306a36Sopenharmony_ci	struct net_bridge_vlan *v;
15462306a36Sopenharmony_ci	int err;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	/* Count number of vlan infos */
15762306a36Sopenharmony_ci	list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
15862306a36Sopenharmony_ci		/* only a context, bridge vlan not activated */
15962306a36Sopenharmony_ci		if (!br_vlan_should_use(v))
16062306a36Sopenharmony_ci			continue;
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci		if (!v->tinfo.tunnel_dst)
16362306a36Sopenharmony_ci			continue;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci		if (!vtbegin) {
16662306a36Sopenharmony_ci			goto initvars;
16762306a36Sopenharmony_ci		} else if ((v->vid - vtend->vid) == 1 &&
16862306a36Sopenharmony_ci			    vlan_tunid_inrange(v, vtend)) {
16962306a36Sopenharmony_ci			vtend = v;
17062306a36Sopenharmony_ci			continue;
17162306a36Sopenharmony_ci		} else {
17262306a36Sopenharmony_ci			err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend);
17362306a36Sopenharmony_ci			if (err)
17462306a36Sopenharmony_ci				return err;
17562306a36Sopenharmony_ci		}
17662306a36Sopenharmony_ciinitvars:
17762306a36Sopenharmony_ci		vtbegin = v;
17862306a36Sopenharmony_ci		vtend = v;
17962306a36Sopenharmony_ci	}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (vtbegin) {
18262306a36Sopenharmony_ci		err = br_fill_vlan_tinfo_range(skb, vtbegin, vtend);
18362306a36Sopenharmony_ci		if (err)
18462306a36Sopenharmony_ci			return err;
18562306a36Sopenharmony_ci	}
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	return 0;
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_cistatic const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1] = {
19162306a36Sopenharmony_ci	[IFLA_BRIDGE_VLAN_TUNNEL_UNSPEC] = {
19262306a36Sopenharmony_ci		.strict_start_type = IFLA_BRIDGE_VLAN_TUNNEL_FLAGS + 1
19362306a36Sopenharmony_ci	},
19462306a36Sopenharmony_ci	[IFLA_BRIDGE_VLAN_TUNNEL_ID] = { .type = NLA_U32 },
19562306a36Sopenharmony_ci	[IFLA_BRIDGE_VLAN_TUNNEL_VID] = { .type = NLA_U16 },
19662306a36Sopenharmony_ci	[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 },
19762306a36Sopenharmony_ci};
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ciint br_vlan_tunnel_info(const struct net_bridge_port *p, int cmd,
20062306a36Sopenharmony_ci			u16 vid, u32 tun_id, bool *changed)
20162306a36Sopenharmony_ci{
20262306a36Sopenharmony_ci	int err = 0;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (!p)
20562306a36Sopenharmony_ci		return -EINVAL;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	switch (cmd) {
20862306a36Sopenharmony_ci	case RTM_SETLINK:
20962306a36Sopenharmony_ci		err = nbp_vlan_tunnel_info_add(p, vid, tun_id);
21062306a36Sopenharmony_ci		if (!err)
21162306a36Sopenharmony_ci			*changed = true;
21262306a36Sopenharmony_ci		break;
21362306a36Sopenharmony_ci	case RTM_DELLINK:
21462306a36Sopenharmony_ci		if (!nbp_vlan_tunnel_info_delete(p, vid))
21562306a36Sopenharmony_ci			*changed = true;
21662306a36Sopenharmony_ci		break;
21762306a36Sopenharmony_ci	}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_ci	return err;
22062306a36Sopenharmony_ci}
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ciint br_parse_vlan_tunnel_info(struct nlattr *attr,
22362306a36Sopenharmony_ci			      struct vtunnel_info *tinfo)
22462306a36Sopenharmony_ci{
22562306a36Sopenharmony_ci	struct nlattr *tb[IFLA_BRIDGE_VLAN_TUNNEL_MAX + 1];
22662306a36Sopenharmony_ci	u32 tun_id;
22762306a36Sopenharmony_ci	u16 vid, flags = 0;
22862306a36Sopenharmony_ci	int err;
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	memset(tinfo, 0, sizeof(*tinfo));
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci	err = nla_parse_nested_deprecated(tb, IFLA_BRIDGE_VLAN_TUNNEL_MAX,
23362306a36Sopenharmony_ci					  attr, vlan_tunnel_policy, NULL);
23462306a36Sopenharmony_ci	if (err < 0)
23562306a36Sopenharmony_ci		return err;
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_ci	if (!tb[IFLA_BRIDGE_VLAN_TUNNEL_ID] ||
23862306a36Sopenharmony_ci	    !tb[IFLA_BRIDGE_VLAN_TUNNEL_VID])
23962306a36Sopenharmony_ci		return -EINVAL;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	tun_id = nla_get_u32(tb[IFLA_BRIDGE_VLAN_TUNNEL_ID]);
24262306a36Sopenharmony_ci	vid = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_VID]);
24362306a36Sopenharmony_ci	if (vid >= VLAN_VID_MASK)
24462306a36Sopenharmony_ci		return -ERANGE;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	if (tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS])
24762306a36Sopenharmony_ci		flags = nla_get_u16(tb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	tinfo->tunid = tun_id;
25062306a36Sopenharmony_ci	tinfo->vid = vid;
25162306a36Sopenharmony_ci	tinfo->flags = flags;
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci	return 0;
25462306a36Sopenharmony_ci}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci/* send a notification if v_curr can't enter the range and start a new one */
25762306a36Sopenharmony_cistatic void __vlan_tunnel_handle_range(const struct net_bridge_port *p,
25862306a36Sopenharmony_ci				       struct net_bridge_vlan **v_start,
25962306a36Sopenharmony_ci				       struct net_bridge_vlan **v_end,
26062306a36Sopenharmony_ci				       int v_curr, bool curr_change)
26162306a36Sopenharmony_ci{
26262306a36Sopenharmony_ci	struct net_bridge_vlan_group *vg;
26362306a36Sopenharmony_ci	struct net_bridge_vlan *v;
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	vg = nbp_vlan_group(p);
26662306a36Sopenharmony_ci	if (!vg)
26762306a36Sopenharmony_ci		return;
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	v = br_vlan_find(vg, v_curr);
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if (!*v_start)
27262306a36Sopenharmony_ci		goto out_init;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	if (v && curr_change && br_vlan_can_enter_range(v, *v_end)) {
27562306a36Sopenharmony_ci		*v_end = v;
27662306a36Sopenharmony_ci		return;
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	br_vlan_notify(p->br, p, (*v_start)->vid, (*v_end)->vid, RTM_NEWVLAN);
28062306a36Sopenharmony_ciout_init:
28162306a36Sopenharmony_ci	/* we start a range only if there are any changes to notify about */
28262306a36Sopenharmony_ci	*v_start = curr_change ? v : NULL;
28362306a36Sopenharmony_ci	*v_end = *v_start;
28462306a36Sopenharmony_ci}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ciint br_process_vlan_tunnel_info(const struct net_bridge *br,
28762306a36Sopenharmony_ci				const struct net_bridge_port *p, int cmd,
28862306a36Sopenharmony_ci				struct vtunnel_info *tinfo_curr,
28962306a36Sopenharmony_ci				struct vtunnel_info *tinfo_last,
29062306a36Sopenharmony_ci				bool *changed)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	int err;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
29562306a36Sopenharmony_ci		if (tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN)
29662306a36Sopenharmony_ci			return -EINVAL;
29762306a36Sopenharmony_ci		memcpy(tinfo_last, tinfo_curr, sizeof(struct vtunnel_info));
29862306a36Sopenharmony_ci	} else if (tinfo_curr->flags & BRIDGE_VLAN_INFO_RANGE_END) {
29962306a36Sopenharmony_ci		struct net_bridge_vlan *v_start = NULL, *v_end = NULL;
30062306a36Sopenharmony_ci		int t, v;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci		if (!(tinfo_last->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN))
30362306a36Sopenharmony_ci			return -EINVAL;
30462306a36Sopenharmony_ci		if ((tinfo_curr->vid - tinfo_last->vid) !=
30562306a36Sopenharmony_ci		    (tinfo_curr->tunid - tinfo_last->tunid))
30662306a36Sopenharmony_ci			return -EINVAL;
30762306a36Sopenharmony_ci		t = tinfo_last->tunid;
30862306a36Sopenharmony_ci		for (v = tinfo_last->vid; v <= tinfo_curr->vid; v++) {
30962306a36Sopenharmony_ci			bool curr_change = false;
31062306a36Sopenharmony_ci
31162306a36Sopenharmony_ci			err = br_vlan_tunnel_info(p, cmd, v, t, &curr_change);
31262306a36Sopenharmony_ci			if (err)
31362306a36Sopenharmony_ci				break;
31462306a36Sopenharmony_ci			t++;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci			if (curr_change)
31762306a36Sopenharmony_ci				*changed = curr_change;
31862306a36Sopenharmony_ci			 __vlan_tunnel_handle_range(p, &v_start, &v_end, v,
31962306a36Sopenharmony_ci						    curr_change);
32062306a36Sopenharmony_ci		}
32162306a36Sopenharmony_ci		if (v_start && v_end)
32262306a36Sopenharmony_ci			br_vlan_notify(br, p, v_start->vid, v_end->vid,
32362306a36Sopenharmony_ci				       RTM_NEWVLAN);
32462306a36Sopenharmony_ci		if (err)
32562306a36Sopenharmony_ci			return err;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci		memset(tinfo_last, 0, sizeof(struct vtunnel_info));
32862306a36Sopenharmony_ci		memset(tinfo_curr, 0, sizeof(struct vtunnel_info));
32962306a36Sopenharmony_ci	} else {
33062306a36Sopenharmony_ci		if (tinfo_last->flags)
33162306a36Sopenharmony_ci			return -EINVAL;
33262306a36Sopenharmony_ci		err = br_vlan_tunnel_info(p, cmd, tinfo_curr->vid,
33362306a36Sopenharmony_ci					  tinfo_curr->tunid, changed);
33462306a36Sopenharmony_ci		if (err)
33562306a36Sopenharmony_ci			return err;
33662306a36Sopenharmony_ci		br_vlan_notify(br, p, tinfo_curr->vid, 0, RTM_NEWVLAN);
33762306a36Sopenharmony_ci		memset(tinfo_last, 0, sizeof(struct vtunnel_info));
33862306a36Sopenharmony_ci		memset(tinfo_curr, 0, sizeof(struct vtunnel_info));
33962306a36Sopenharmony_ci	}
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_ci	return 0;
34262306a36Sopenharmony_ci}
343