Merge branch 'stmmac-100GB-Enterprise-MAC-support'
[linux-block.git] / net / bridge / br_vlan_options.c
CommitLineData
7a53e718
NA
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
8#include "br_private.h"
9
10/* check if the options between two vlans are equal */
11bool br_vlan_opts_eq(const struct net_bridge_vlan *v1,
12 const struct net_bridge_vlan *v2)
13{
a580c76d 14 return v1->state == v2->state;
7a53e718
NA
15}
16
17bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v)
18{
a580c76d
NA
19 return !nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE,
20 br_vlan_get_state(v));
7a53e718
NA
21}
22
23size_t br_vlan_opts_nl_size(void)
24{
a580c76d
NA
25 return nla_total_size(sizeof(u8)); /* BRIDGE_VLANDB_ENTRY_STATE */
26}
27
28static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
29 struct net_bridge_vlan *v,
30 u8 state,
31 bool *changed,
32 struct netlink_ext_ack *extack)
33{
34 struct net_bridge *br;
35
36 ASSERT_RTNL();
37
38 if (state > BR_STATE_BLOCKING) {
39 NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state");
40 return -EINVAL;
41 }
42
43 if (br_vlan_is_brentry(v))
44 br = v->br;
45 else
46 br = v->port->br;
47
48 if (br->stp_enabled == BR_KERNEL_STP) {
49 NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP");
50 return -EBUSY;
51 }
52
53 if (v->state == state)
54 return 0;
55
56 if (v->vid == br_get_pvid(vg))
57 br_vlan_set_pvid_state(vg, state);
58
59 br_vlan_set_state(v, state);
60 *changed = true;
61
7a53e718
NA
62 return 0;
63}
a5d29ae2
NA
64
65static int br_vlan_process_one_opts(const struct net_bridge *br,
66 const struct net_bridge_port *p,
67 struct net_bridge_vlan_group *vg,
68 struct net_bridge_vlan *v,
69 struct nlattr **tb,
70 bool *changed,
71 struct netlink_ext_ack *extack)
72{
a580c76d
NA
73 int err;
74
a5d29ae2 75 *changed = false;
a580c76d
NA
76 if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
77 u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
78
79 err = br_vlan_modify_state(vg, v, state, changed, extack);
80 if (err)
81 return err;
82 }
83
a5d29ae2
NA
84 return 0;
85}
86
87int br_vlan_process_options(const struct net_bridge *br,
88 const struct net_bridge_port *p,
89 struct net_bridge_vlan *range_start,
90 struct net_bridge_vlan *range_end,
91 struct nlattr **tb,
92 struct netlink_ext_ack *extack)
93{
94 struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL;
95 struct net_bridge_vlan_group *vg;
96 int vid, err = 0;
97 u16 pvid;
98
99 if (p)
100 vg = nbp_vlan_group(p);
101 else
102 vg = br_vlan_group(br);
103
104 if (!range_start || !br_vlan_should_use(range_start)) {
105 NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options");
106 return -ENOENT;
107 }
108 if (!range_end || !br_vlan_should_use(range_end)) {
109 NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options");
110 return -ENOENT;
111 }
112
113 pvid = br_get_pvid(vg);
114 for (vid = range_start->vid; vid <= range_end->vid; vid++) {
115 bool changed = false;
116
117 v = br_vlan_find(vg, vid);
118 if (!v || !br_vlan_should_use(v)) {
119 NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options");
120 err = -ENOENT;
121 break;
122 }
123
124 err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
125 extack);
126 if (err)
127 break;
128
129 if (changed) {
130 /* vlan options changed, check for range */
131 if (!curr_start) {
132 curr_start = v;
133 curr_end = v;
134 continue;
135 }
136
137 if (v->vid == pvid ||
138 !br_vlan_can_enter_range(v, curr_end)) {
139 br_vlan_notify(br, p, curr_start->vid,
140 curr_end->vid, RTM_NEWVLAN);
141 curr_start = v;
142 }
143 curr_end = v;
144 } else {
145 /* nothing changed and nothing to notify yet */
146 if (!curr_start)
147 continue;
148
149 br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
150 RTM_NEWVLAN);
151 curr_start = NULL;
152 curr_end = NULL;
153 }
154 }
155 if (curr_start)
156 br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
157 RTM_NEWVLAN);
158
159 return err;
160}