bridge: allow adding/removing VLANs to configured member ports via hotplug
[project/netifd.git] / vlan.c
1 /*
2 * netifd - network interface daemon
3 * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2
7 * as published by the Free Software Foundation
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14 #include <string.h>
15 #include <stdlib.h>
16 #include <stdio.h>
17
18 #include "netifd.h"
19 #include "system.h"
20
21 static struct blob_buf b;
22
23 struct vlan_device {
24 struct device dev;
25 struct device_user dep;
26
27 device_state_cb set_state;
28 int id;
29 };
30
31 static void free_vlan_if(struct device *iface)
32 {
33 struct vlan_device *vldev;
34
35 vldev = container_of(iface, struct vlan_device, dev);
36 device_remove_user(&vldev->dep);
37 device_cleanup(&vldev->dev);
38 free(vldev);
39 }
40
41 static int
42 __vlan_hotplug_op(struct device *dev, struct device *member, struct blob_attr *vlan, bool add)
43 {
44 struct vlan_device *vldev = container_of(dev, struct vlan_device, dev);
45 void *a;
46
47 dev = vldev->dep.dev;
48 if (!dev || !dev->hotplug_ops)
49 return UBUS_STATUS_NOT_SUPPORTED;
50
51 blob_buf_init(&b, 0);
52 a = blobmsg_open_array(&b, "vlans");
53 blobmsg_printf(&b, NULL, "%d", vldev->id);
54 blobmsg_close_array(&b, a);
55
56 if (add)
57 return dev->hotplug_ops->add(dev, member, blobmsg_data(b.head));
58 else
59 return dev->hotplug_ops->del(dev, member, blobmsg_data(b.head));
60 }
61
62 static int
63 vlan_hotplug_add(struct device *dev, struct device *member, struct blob_attr *vlan)
64 {
65 return __vlan_hotplug_op(dev, member, vlan, true);
66 }
67
68 static int
69 vlan_hotplug_del(struct device *dev, struct device *member, struct blob_attr *vlan)
70 {
71 return __vlan_hotplug_op(dev, member, vlan, false);
72 }
73
74 static int
75 vlan_hotplug_prepare(struct device *dev, struct device **bridge_dev)
76 {
77 struct vlan_device *vldev = container_of(dev, struct vlan_device, dev);
78
79 dev = vldev->dep.dev;
80 if (!dev || !dev->hotplug_ops)
81 return UBUS_STATUS_NOT_SUPPORTED;
82
83 return dev->hotplug_ops->prepare(dev, bridge_dev);
84 }
85
86 static void vlan_hotplug_check(struct vlan_device *vldev, struct device *dev)
87 {
88 static const struct device_hotplug_ops hotplug_ops = {
89 .prepare = vlan_hotplug_prepare,
90 .add = vlan_hotplug_add,
91 .del = vlan_hotplug_del
92 };
93
94 if (!dev || !dev->hotplug_ops || avl_is_empty(&dev->vlans.avl)) {
95 vldev->dev.hotplug_ops = NULL;
96 return;
97 }
98
99 vldev->dev.hotplug_ops = &hotplug_ops;
100 }
101
102
103 static int vlan_set_device_state(struct device *dev, bool up)
104 {
105 struct vlan_device *vldev;
106 int ret = 0;
107
108 vldev = container_of(dev, struct vlan_device, dev);
109 if (!up) {
110 vldev->set_state(dev, false);
111 system_vlan_del(dev);
112 device_release(&vldev->dep);
113 return 0;
114 }
115
116 ret = device_claim(&vldev->dep);
117 if (ret < 0)
118 return ret;
119
120 system_vlan_add(vldev->dep.dev, vldev->id);
121 ret = vldev->set_state(dev, true);
122 if (ret)
123 device_release(&vldev->dep);
124
125 return ret;
126 }
127
128 static void vlan_dev_cb(struct device_user *dep, enum device_event ev)
129 {
130 char name[IFNAMSIZ + 1];
131 struct vlan_device *vldev;
132
133 vldev = container_of(dep, struct vlan_device, dep);
134 switch(ev) {
135 case DEV_EVENT_ADD:
136 device_set_present(&vldev->dev, true);
137 break;
138 case DEV_EVENT_REMOVE:
139 device_set_present(&vldev->dev, false);
140 break;
141 case DEV_EVENT_UPDATE_IFNAME:
142 vlan_hotplug_check(vldev, dep->dev);
143 vldev->dev.hidden = dep->dev->hidden;
144 if (snprintf(name, sizeof(name), "%s.%d", dep->dev->ifname,
145 vldev->id) >= sizeof(name) - 1 ||
146 device_set_ifname(&vldev->dev, name))
147 free_vlan_if(&vldev->dev);
148 break;
149 case DEV_EVENT_TOPO_CHANGE:
150 /* Propagate topo changes */
151 device_broadcast_event(&vldev->dev, DEV_EVENT_TOPO_CHANGE);
152 break;
153 default:
154 break;
155 }
156 }
157
158 static void
159 vlan_config_init(struct device *dev)
160 {
161 struct vlan_device *vldev;
162
163 vldev = container_of(dev, struct vlan_device, dev);
164 vlan_hotplug_check(vldev, vldev->dep.dev);
165 }
166
167 static struct device *get_vlan_device(struct device *dev, char *id_str, bool create)
168 {
169 static struct device_type vlan_type = {
170 .name = "VLAN",
171 .config_params = &device_attr_list,
172 .config_init = vlan_config_init,
173 .free = free_vlan_if,
174 };
175 struct vlan_device *vldev;
176 struct device_user *dep;
177 char name[IFNAMSIZ + 1];
178 char *err = NULL;
179 int id, *alias_id;
180
181 id = strtoul(id_str, &err, 10);
182 if (err && *err) {
183 alias_id = kvlist_get(&dev->vlan_aliases, id_str);
184 if (!alias_id)
185 return NULL;
186
187 id = *alias_id;
188 }
189
190 /* look for an existing interface before creating a new one */
191 list_for_each_entry(dep, &dev->users.list, list.list) {
192 if (dep->cb != vlan_dev_cb)
193 continue;
194
195 vldev = container_of(dep, struct vlan_device, dep);
196 if (vldev->id != id)
197 continue;
198
199 return &vldev->dev;
200 }
201
202 if (!create)
203 return NULL;
204
205 if (snprintf(name, sizeof(name), "%s.%d", dev->ifname, id) >= sizeof(name) - 1)
206 return NULL;
207
208 D(DEVICE, "Create vlan device '%s'\n", name);
209
210 vldev = calloc(1, sizeof(*vldev));
211 if (!vldev)
212 return NULL;
213
214 vldev->id = id;
215 vldev->dev.hidden = dev->hidden;
216 strcpy(vldev->dev.ifname, name);
217
218 if (device_init(&vldev->dev, &vlan_type, NULL) < 0)
219 goto error;
220
221 vldev->dev.default_config = true;
222 vldev->dev.config_pending = true;
223
224 vldev->set_state = vldev->dev.set_state;
225 vldev->dev.set_state = vlan_set_device_state;
226
227 vldev->dep.cb = vlan_dev_cb;
228 vlan_hotplug_check(vldev, vldev->dep.dev);
229 device_add_user(&vldev->dep, dev);
230
231 return &vldev->dev;
232
233 error:
234 device_cleanup(&vldev->dev);
235 free(vldev);
236 return NULL;
237 }
238
239 static char *split_vlan(char *s)
240 {
241 s = strchr(s, '.');
242 if (!s)
243 goto out;
244
245 *s = 0;
246 s++;
247
248 out:
249 return s;
250 }
251
252 struct device *get_vlan_device_chain(const char *ifname, bool create)
253 {
254 struct device *dev = NULL;
255 char *buf, *s, *next;
256
257 buf = strdup(ifname);
258 if (!buf)
259 return NULL;
260
261 s = split_vlan(buf);
262 dev = device_get(buf, create);
263 if (!dev)
264 goto error;
265
266 do {
267 next = split_vlan(s);
268 dev = get_vlan_device(dev, s, create);
269 if (!dev)
270 goto error;
271
272 s = next;
273 if (!s)
274 goto out;
275 } while (1);
276
277 error:
278 dev = NULL;
279 out:
280 free(buf);
281 return dev;
282 }