18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci// Copyright (c) 2020, Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
38c2ecf20Sopenharmony_ci#include <linux/kernel.h>
48c2ecf20Sopenharmony_ci#include <linux/netdevice.h>
58c2ecf20Sopenharmony_ci#include <linux/rtnetlink.h>
68c2ecf20Sopenharmony_ci#include <linux/slab.h>
78c2ecf20Sopenharmony_ci#include <net/ip_tunnels.h>
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci#include "br_private.h"
108c2ecf20Sopenharmony_ci#include "br_private_tunnel.h"
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_cistatic bool __vlan_tun_put(struct sk_buff *skb, const struct net_bridge_vlan *v)
138c2ecf20Sopenharmony_ci{
148c2ecf20Sopenharmony_ci	__be32 tid = tunnel_id_to_key32(v->tinfo.tunnel_id);
158c2ecf20Sopenharmony_ci	struct nlattr *nest;
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci	if (!v->tinfo.tunnel_dst)
188c2ecf20Sopenharmony_ci		return true;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY_TUNNEL_INFO);
218c2ecf20Sopenharmony_ci	if (!nest)
228c2ecf20Sopenharmony_ci		return false;
238c2ecf20Sopenharmony_ci	if (nla_put_u32(skb, BRIDGE_VLANDB_TINFO_ID, be32_to_cpu(tid))) {
248c2ecf20Sopenharmony_ci		nla_nest_cancel(skb, nest);
258c2ecf20Sopenharmony_ci		return false;
268c2ecf20Sopenharmony_ci	}
278c2ecf20Sopenharmony_ci	nla_nest_end(skb, nest);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci	return true;
308c2ecf20Sopenharmony_ci}
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_cistatic bool __vlan_tun_can_enter_range(const struct net_bridge_vlan *v_curr,
338c2ecf20Sopenharmony_ci				       const struct net_bridge_vlan *range_end)
348c2ecf20Sopenharmony_ci{
358c2ecf20Sopenharmony_ci	return (!v_curr->tinfo.tunnel_dst && !range_end->tinfo.tunnel_dst) ||
368c2ecf20Sopenharmony_ci	       vlan_tunid_inrange(v_curr, range_end);
378c2ecf20Sopenharmony_ci}
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci/* check if the options' state of v_curr allow it to enter the range */
408c2ecf20Sopenharmony_cibool br_vlan_opts_eq_range(const struct net_bridge_vlan *v_curr,
418c2ecf20Sopenharmony_ci			   const struct net_bridge_vlan *range_end)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	return v_curr->state == range_end->state &&
448c2ecf20Sopenharmony_ci	       __vlan_tun_can_enter_range(v_curr, range_end);
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cibool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	return !nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE,
508c2ecf20Sopenharmony_ci			   br_vlan_get_state(v)) &&
518c2ecf20Sopenharmony_ci	       __vlan_tun_put(skb, v);
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cisize_t br_vlan_opts_nl_size(void)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	return nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_STATE */
578c2ecf20Sopenharmony_ci	       + nla_total_size(0) /* BRIDGE_VLANDB_ENTRY_TUNNEL_INFO */
588c2ecf20Sopenharmony_ci	       + nla_total_size(sizeof(u32)); /* BRIDGE_VLANDB_TINFO_ID */
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
628c2ecf20Sopenharmony_ci				struct net_bridge_vlan *v,
638c2ecf20Sopenharmony_ci				u8 state,
648c2ecf20Sopenharmony_ci				bool *changed,
658c2ecf20Sopenharmony_ci				struct netlink_ext_ack *extack)
668c2ecf20Sopenharmony_ci{
678c2ecf20Sopenharmony_ci	struct net_bridge *br;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	ASSERT_RTNL();
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	if (state > BR_STATE_BLOCKING) {
728c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state");
738c2ecf20Sopenharmony_ci		return -EINVAL;
748c2ecf20Sopenharmony_ci	}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci	if (br_vlan_is_brentry(v))
778c2ecf20Sopenharmony_ci		br = v->br;
788c2ecf20Sopenharmony_ci	else
798c2ecf20Sopenharmony_ci		br = v->port->br;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (br->stp_enabled == BR_KERNEL_STP) {
828c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP");
838c2ecf20Sopenharmony_ci		return -EBUSY;
848c2ecf20Sopenharmony_ci	}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	if (v->state == state)
878c2ecf20Sopenharmony_ci		return 0;
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci	if (v->vid == br_get_pvid(vg))
908c2ecf20Sopenharmony_ci		br_vlan_set_pvid_state(vg, state);
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	br_vlan_set_state(v, state);
938c2ecf20Sopenharmony_ci	*changed = true;
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	return 0;
968c2ecf20Sopenharmony_ci}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic const struct nla_policy br_vlandb_tinfo_pol[BRIDGE_VLANDB_TINFO_MAX + 1] = {
998c2ecf20Sopenharmony_ci	[BRIDGE_VLANDB_TINFO_ID]	= { .type = NLA_U32 },
1008c2ecf20Sopenharmony_ci	[BRIDGE_VLANDB_TINFO_CMD]	= { .type = NLA_U32 },
1018c2ecf20Sopenharmony_ci};
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_cistatic int br_vlan_modify_tunnel(const struct net_bridge_port *p,
1048c2ecf20Sopenharmony_ci				 struct net_bridge_vlan *v,
1058c2ecf20Sopenharmony_ci				 struct nlattr **tb,
1068c2ecf20Sopenharmony_ci				 bool *changed,
1078c2ecf20Sopenharmony_ci				 struct netlink_ext_ack *extack)
1088c2ecf20Sopenharmony_ci{
1098c2ecf20Sopenharmony_ci	struct nlattr *tun_tb[BRIDGE_VLANDB_TINFO_MAX + 1], *attr;
1108c2ecf20Sopenharmony_ci	struct bridge_vlan_info *vinfo;
1118c2ecf20Sopenharmony_ci	u32 tun_id = 0;
1128c2ecf20Sopenharmony_ci	int cmd, err;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	if (!p) {
1158c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Can't modify tunnel mapping of non-port vlans");
1168c2ecf20Sopenharmony_ci		return -EINVAL;
1178c2ecf20Sopenharmony_ci	}
1188c2ecf20Sopenharmony_ci	if (!(p->flags & BR_VLAN_TUNNEL)) {
1198c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Port doesn't have tunnel flag set");
1208c2ecf20Sopenharmony_ci		return -EINVAL;
1218c2ecf20Sopenharmony_ci	}
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	attr = tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO];
1248c2ecf20Sopenharmony_ci	err = nla_parse_nested(tun_tb, BRIDGE_VLANDB_TINFO_MAX, attr,
1258c2ecf20Sopenharmony_ci			       br_vlandb_tinfo_pol, extack);
1268c2ecf20Sopenharmony_ci	if (err)
1278c2ecf20Sopenharmony_ci		return err;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	if (!tun_tb[BRIDGE_VLANDB_TINFO_CMD]) {
1308c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Missing tunnel command attribute");
1318c2ecf20Sopenharmony_ci		return -ENOENT;
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci	cmd = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_CMD]);
1348c2ecf20Sopenharmony_ci	switch (cmd) {
1358c2ecf20Sopenharmony_ci	case RTM_SETLINK:
1368c2ecf20Sopenharmony_ci		if (!tun_tb[BRIDGE_VLANDB_TINFO_ID]) {
1378c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Missing tunnel id attribute");
1388c2ecf20Sopenharmony_ci			return -ENOENT;
1398c2ecf20Sopenharmony_ci		}
1408c2ecf20Sopenharmony_ci		/* when working on vlan ranges this is the starting tunnel id */
1418c2ecf20Sopenharmony_ci		tun_id = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_ID]);
1428c2ecf20Sopenharmony_ci		/* vlan info attr is guaranteed by br_vlan_rtm_process_one */
1438c2ecf20Sopenharmony_ci		vinfo = nla_data(tb[BRIDGE_VLANDB_ENTRY_INFO]);
1448c2ecf20Sopenharmony_ci		/* tunnel ids are mapped to each vlan in increasing order,
1458c2ecf20Sopenharmony_ci		 * the starting vlan is in BRIDGE_VLANDB_ENTRY_INFO and v is the
1468c2ecf20Sopenharmony_ci		 * current vlan, so we compute: tun_id + v - vinfo->vid
1478c2ecf20Sopenharmony_ci		 */
1488c2ecf20Sopenharmony_ci		tun_id += v->vid - vinfo->vid;
1498c2ecf20Sopenharmony_ci		break;
1508c2ecf20Sopenharmony_ci	case RTM_DELLINK:
1518c2ecf20Sopenharmony_ci		break;
1528c2ecf20Sopenharmony_ci	default:
1538c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Unsupported tunnel command");
1548c2ecf20Sopenharmony_ci		return -EINVAL;
1558c2ecf20Sopenharmony_ci	}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	return br_vlan_tunnel_info(p, cmd, v->vid, tun_id, changed);
1588c2ecf20Sopenharmony_ci}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_cistatic int br_vlan_process_one_opts(const struct net_bridge *br,
1618c2ecf20Sopenharmony_ci				    const struct net_bridge_port *p,
1628c2ecf20Sopenharmony_ci				    struct net_bridge_vlan_group *vg,
1638c2ecf20Sopenharmony_ci				    struct net_bridge_vlan *v,
1648c2ecf20Sopenharmony_ci				    struct nlattr **tb,
1658c2ecf20Sopenharmony_ci				    bool *changed,
1668c2ecf20Sopenharmony_ci				    struct netlink_ext_ack *extack)
1678c2ecf20Sopenharmony_ci{
1688c2ecf20Sopenharmony_ci	int err;
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	*changed = false;
1718c2ecf20Sopenharmony_ci	if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
1728c2ecf20Sopenharmony_ci		u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci		err = br_vlan_modify_state(vg, v, state, changed, extack);
1758c2ecf20Sopenharmony_ci		if (err)
1768c2ecf20Sopenharmony_ci			return err;
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci	if (tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO]) {
1798c2ecf20Sopenharmony_ci		err = br_vlan_modify_tunnel(p, v, tb, changed, extack);
1808c2ecf20Sopenharmony_ci		if (err)
1818c2ecf20Sopenharmony_ci			return err;
1828c2ecf20Sopenharmony_ci	}
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	return 0;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ciint br_vlan_process_options(const struct net_bridge *br,
1888c2ecf20Sopenharmony_ci			    const struct net_bridge_port *p,
1898c2ecf20Sopenharmony_ci			    struct net_bridge_vlan *range_start,
1908c2ecf20Sopenharmony_ci			    struct net_bridge_vlan *range_end,
1918c2ecf20Sopenharmony_ci			    struct nlattr **tb,
1928c2ecf20Sopenharmony_ci			    struct netlink_ext_ack *extack)
1938c2ecf20Sopenharmony_ci{
1948c2ecf20Sopenharmony_ci	struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL;
1958c2ecf20Sopenharmony_ci	struct net_bridge_vlan_group *vg;
1968c2ecf20Sopenharmony_ci	int vid, err = 0;
1978c2ecf20Sopenharmony_ci	u16 pvid;
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	if (p)
2008c2ecf20Sopenharmony_ci		vg = nbp_vlan_group(p);
2018c2ecf20Sopenharmony_ci	else
2028c2ecf20Sopenharmony_ci		vg = br_vlan_group(br);
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	if (!range_start || !br_vlan_should_use(range_start)) {
2058c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options");
2068c2ecf20Sopenharmony_ci		return -ENOENT;
2078c2ecf20Sopenharmony_ci	}
2088c2ecf20Sopenharmony_ci	if (!range_end || !br_vlan_should_use(range_end)) {
2098c2ecf20Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options");
2108c2ecf20Sopenharmony_ci		return -ENOENT;
2118c2ecf20Sopenharmony_ci	}
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci	pvid = br_get_pvid(vg);
2148c2ecf20Sopenharmony_ci	for (vid = range_start->vid; vid <= range_end->vid; vid++) {
2158c2ecf20Sopenharmony_ci		bool changed = false;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci		v = br_vlan_find(vg, vid);
2188c2ecf20Sopenharmony_ci		if (!v || !br_vlan_should_use(v)) {
2198c2ecf20Sopenharmony_ci			NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options");
2208c2ecf20Sopenharmony_ci			err = -ENOENT;
2218c2ecf20Sopenharmony_ci			break;
2228c2ecf20Sopenharmony_ci		}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci		err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
2258c2ecf20Sopenharmony_ci					       extack);
2268c2ecf20Sopenharmony_ci		if (err)
2278c2ecf20Sopenharmony_ci			break;
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ci		if (changed) {
2308c2ecf20Sopenharmony_ci			/* vlan options changed, check for range */
2318c2ecf20Sopenharmony_ci			if (!curr_start) {
2328c2ecf20Sopenharmony_ci				curr_start = v;
2338c2ecf20Sopenharmony_ci				curr_end = v;
2348c2ecf20Sopenharmony_ci				continue;
2358c2ecf20Sopenharmony_ci			}
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci			if (v->vid == pvid ||
2388c2ecf20Sopenharmony_ci			    !br_vlan_can_enter_range(v, curr_end)) {
2398c2ecf20Sopenharmony_ci				br_vlan_notify(br, p, curr_start->vid,
2408c2ecf20Sopenharmony_ci					       curr_end->vid, RTM_NEWVLAN);
2418c2ecf20Sopenharmony_ci				curr_start = v;
2428c2ecf20Sopenharmony_ci			}
2438c2ecf20Sopenharmony_ci			curr_end = v;
2448c2ecf20Sopenharmony_ci		} else {
2458c2ecf20Sopenharmony_ci			/* nothing changed and nothing to notify yet */
2468c2ecf20Sopenharmony_ci			if (!curr_start)
2478c2ecf20Sopenharmony_ci				continue;
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci			br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
2508c2ecf20Sopenharmony_ci				       RTM_NEWVLAN);
2518c2ecf20Sopenharmony_ci			curr_start = NULL;
2528c2ecf20Sopenharmony_ci			curr_end = NULL;
2538c2ecf20Sopenharmony_ci		}
2548c2ecf20Sopenharmony_ci	}
2558c2ecf20Sopenharmony_ci	if (curr_start)
2568c2ecf20Sopenharmony_ci		br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
2578c2ecf20Sopenharmony_ci			       RTM_NEWVLAN);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	return err;
2608c2ecf20Sopenharmony_ci}
261