1 // SPDX-License-Identifier: GPL-2.0-only 2 // Copyright (c) 2020, Nikolay Aleksandrov <nikolay@cumulusnetworks.com> 3 #include <linux/kernel.h> 4 #include <linux/netdevice.h> 5 #include <linux/rtnetlink.h> 6 #include <linux/slab.h> 7 #include <net/ip_tunnels.h> 8 9 #include "br_private.h" 10 #include "br_private_tunnel.h" 11 12 static bool __vlan_tun_put(struct sk_buff *skb, const struct net_bridge_vlan *v) 13 { 14 __be32 tid = tunnel_id_to_key32(v->tinfo.tunnel_id); 15 16 if (!v->tinfo.tunnel_dst) 17 return true; 18 19 return !nla_put_u32(skb, BRIDGE_VLANDB_ENTRY_TUNNEL_ID, 20 be32_to_cpu(tid)); 21 } 22 23 static bool __vlan_tun_can_enter_range(const struct net_bridge_vlan *v_curr, 24 const struct net_bridge_vlan *range_end) 25 { 26 return (!v_curr->tinfo.tunnel_dst && !range_end->tinfo.tunnel_dst) || 27 vlan_tunid_inrange(v_curr, range_end); 28 } 29 30 /* check if the options' state of v_curr allow it to enter the range */ 31 bool br_vlan_opts_eq_range(const struct net_bridge_vlan *v_curr, 32 const struct net_bridge_vlan *range_end) 33 { 34 return v_curr->state == range_end->state && 35 __vlan_tun_can_enter_range(v_curr, range_end); 36 } 37 38 bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v) 39 { 40 return !nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE, 41 br_vlan_get_state(v)) && 42 __vlan_tun_put(skb, v); 43 } 44 45 size_t br_vlan_opts_nl_size(void) 46 { 47 return nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_STATE */ 48 + nla_total_size(sizeof(u32)); /* BRIDGE_VLANDB_ENTRY_TUNNEL_ID */ 49 } 50 51 static int br_vlan_modify_state(struct net_bridge_vlan_group *vg, 52 struct net_bridge_vlan *v, 53 u8 state, 54 bool *changed, 55 struct netlink_ext_ack *extack) 56 { 57 struct net_bridge *br; 58 59 ASSERT_RTNL(); 60 61 if (state > BR_STATE_BLOCKING) { 62 NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state"); 63 return -EINVAL; 64 } 65 66 if (br_vlan_is_brentry(v)) 67 br = v->br; 68 else 69 br = v->port->br; 70 71 if (br->stp_enabled == BR_KERNEL_STP) { 72 NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP"); 73 return -EBUSY; 74 } 75 76 if (v->state == state) 77 return 0; 78 79 if (v->vid == br_get_pvid(vg)) 80 br_vlan_set_pvid_state(vg, state); 81 82 br_vlan_set_state(v, state); 83 *changed = true; 84 85 return 0; 86 } 87 88 static int br_vlan_process_one_opts(const struct net_bridge *br, 89 const struct net_bridge_port *p, 90 struct net_bridge_vlan_group *vg, 91 struct net_bridge_vlan *v, 92 struct nlattr **tb, 93 bool *changed, 94 struct netlink_ext_ack *extack) 95 { 96 int err; 97 98 *changed = false; 99 if (tb[BRIDGE_VLANDB_ENTRY_STATE]) { 100 u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]); 101 102 err = br_vlan_modify_state(vg, v, state, changed, extack); 103 if (err) 104 return err; 105 } 106 107 return 0; 108 } 109 110 int br_vlan_process_options(const struct net_bridge *br, 111 const struct net_bridge_port *p, 112 struct net_bridge_vlan *range_start, 113 struct net_bridge_vlan *range_end, 114 struct nlattr **tb, 115 struct netlink_ext_ack *extack) 116 { 117 struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL; 118 struct net_bridge_vlan_group *vg; 119 int vid, err = 0; 120 u16 pvid; 121 122 if (p) 123 vg = nbp_vlan_group(p); 124 else 125 vg = br_vlan_group(br); 126 127 if (!range_start || !br_vlan_should_use(range_start)) { 128 NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options"); 129 return -ENOENT; 130 } 131 if (!range_end || !br_vlan_should_use(range_end)) { 132 NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options"); 133 return -ENOENT; 134 } 135 136 pvid = br_get_pvid(vg); 137 for (vid = range_start->vid; vid <= range_end->vid; vid++) { 138 bool changed = false; 139 140 v = br_vlan_find(vg, vid); 141 if (!v || !br_vlan_should_use(v)) { 142 NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options"); 143 err = -ENOENT; 144 break; 145 } 146 147 err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed, 148 extack); 149 if (err) 150 break; 151 152 if (changed) { 153 /* vlan options changed, check for range */ 154 if (!curr_start) { 155 curr_start = v; 156 curr_end = v; 157 continue; 158 } 159 160 if (v->vid == pvid || 161 !br_vlan_can_enter_range(v, curr_end)) { 162 br_vlan_notify(br, p, curr_start->vid, 163 curr_end->vid, RTM_NEWVLAN); 164 curr_start = v; 165 } 166 curr_end = v; 167 } else { 168 /* nothing changed and nothing to notify yet */ 169 if (!curr_start) 170 continue; 171 172 br_vlan_notify(br, p, curr_start->vid, curr_end->vid, 173 RTM_NEWVLAN); 174 curr_start = NULL; 175 curr_end = NULL; 176 } 177 } 178 if (curr_start) 179 br_vlan_notify(br, p, curr_start->vid, curr_end->vid, 180 RTM_NEWVLAN); 181 182 return err; 183 } 184