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