Merge branch 'next' into for-linus
[linux-2.6-microblaze.git] / net / bridge / br_vlan_options.c
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 */
11 bool br_vlan_opts_eq(const struct net_bridge_vlan *v1,
12                      const struct net_bridge_vlan *v2)
13 {
14         return v1->state == v2->state;
15 }
16
17 bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v)
18 {
19         return !nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE,
20                            br_vlan_get_state(v));
21 }
22
23 size_t br_vlan_opts_nl_size(void)
24 {
25         return nla_total_size(sizeof(u8)); /* BRIDGE_VLANDB_ENTRY_STATE */
26 }
27
28 static 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
62         return 0;
63 }
64
65 static 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 {
73         int err;
74
75         *changed = false;
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
84         return 0;
85 }
86
87 int 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 }