diff mbox series

[RFC,netifd,1/3] bridge: add support for defining port member vlans via hotplug ops

Message ID 20200807123533.71916-1-nbd@nbd.name
State New
Headers show
Series [RFC,netifd,1/3] bridge: add support for defining port member vlans via hotplug ops | expand

Commit Message

Felix Fietkau Aug. 7, 2020, 12:35 p.m. UTC
Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
 bridge.c    | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 config.c    |   1 +
 device.h    |   4 +-
 interface.c |  10 +++--
 interface.h |   2 +-
 ubus.c      |   5 ++-
 wireless.c  |   4 +-
 7 files changed, 120 insertions(+), 11 deletions(-)
diff mbox series

Patch

diff --git a/bridge.c b/bridge.c
index 14d497298352..92eea9f7e22e 100644
--- a/bridge.c
+++ b/bridge.c
@@ -122,6 +122,11 @@  struct bridge_member {
 	char name[];
 };
 
+struct bridge_vlan_hotplug_port {
+	struct list_head list;
+	struct bridge_vlan_port port;
+};
+
 static void
 bridge_reset_primary(struct bridge_state *bst)
 {
@@ -153,6 +158,7 @@  bridge_reset_primary(struct bridge_state *bst)
 static struct bridge_vlan_port *
 bridge_find_vlan_member_port(struct bridge_member *bm, struct bridge_vlan *vlan)
 {
+	struct bridge_vlan_hotplug_port *port;
 	const char *ifname = bm->dev.dev->ifname;
 	int i;
 
@@ -163,6 +169,13 @@  bridge_find_vlan_member_port(struct bridge_member *bm, struct bridge_vlan *vlan)
 		return &vlan->ports[i];
 	}
 
+	list_for_each_entry(port, &vlan->hotplug_ports, list) {
+		if (strcmp(port->port.ifname, ifname) != 0)
+			continue;
+
+		return &port->port;
+	}
+
 	return NULL;
 }
 
@@ -415,9 +428,25 @@  bridge_remove_member(struct bridge_member *bm)
 static void
 bridge_free_member(struct bridge_member *bm)
 {
+	struct bridge_state *bst = bm->bst;
 	struct device *dev = bm->dev.dev;
+	const char *ifname = dev->ifname;
+	struct bridge_vlan *vlan;
 
 	bridge_remove_member(bm);
+
+	vlist_for_each_element(&bst->dev.vlans, vlan, node) {
+		struct bridge_vlan_hotplug_port *port, *tmp;
+
+		list_for_each_entry_safe(port, tmp, &vlan->hotplug_ports, list) {
+			if (strcmp(port->port.ifname, ifname) != 0)
+				continue;
+
+			list_del(&port->list);
+			free(port);
+		}
+	}
+
 	device_remove_user(&bm->dev);
 
 	/*
@@ -616,11 +645,66 @@  bridge_add_member(struct bridge_state *bst, const char *name)
 	bridge_create_member(bst, name, dev, false);
 }
 
+static void
+bridge_hotplug_create_member_vlans(struct bridge_state *bst, struct blob_attr *vlans, const char *ifname)
+{
+	struct bridge_vlan *vlan;
+	struct blob_attr *cur;
+	int rem;
+
+	if (!vlans)
+		return;
+
+	blobmsg_for_each_attr(cur, vlans, rem) {
+		struct bridge_vlan_hotplug_port *port;
+		uint16_t flags = BRVLAN_F_UNTAGGED;
+		char *name_buf;
+		unsigned int vid;
+		char *end;
+
+		if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+			continue;
+
+		vid = strtoul(blobmsg_get_string(cur), &end, 0);
+		if (!vid || vid > 4095)
+			continue;
+
+		vlan = vlist_find(&bst->dev.vlans, &vid, vlan, node);
+		if (!vlan)
+			continue;
+
+		if (end && *end) {
+			if (*end != ':')
+				continue;
+
+			for (end++; *end; end++) {
+				switch (*end) {
+				case 't':
+					flags &= ~BRVLAN_F_UNTAGGED;
+					break;
+				case '*':
+					flags |= BRVLAN_F_PVID;
+					break;
+				}
+			}
+		}
+
+		port = calloc_a(sizeof(*port), &name_buf, strlen(ifname) + 1);
+		if (!port)
+			continue;
+
+		port->port.flags = flags;
+		port->port.ifname = strcpy(name_buf, ifname);
+		list_add_tail(&port->list, &vlan->hotplug_ports);
+	}
+}
+
 static int
-bridge_hotplug_add(struct device *dev, struct device *member)
+bridge_hotplug_add(struct device *dev, struct device *member, struct blob_attr *vlan)
 {
 	struct bridge_state *bst = container_of(dev, struct bridge_state, dev);
 
+	bridge_hotplug_create_member_vlans(bst, vlan, member->ifname);
 	bridge_create_member(bst, member->ifname, member, true);
 
 	return 0;
@@ -899,6 +983,20 @@  bridge_vlan_equal(struct bridge_vlan *v1, struct bridge_vlan *v2)
 	return true;
 }
 
+static void
+bridge_vlan_free(struct bridge_vlan *vlan)
+{
+	struct bridge_vlan_hotplug_port *port, *tmp;
+
+	if (!vlan)
+		return;
+
+	list_for_each_entry_safe(port, tmp, &vlan->hotplug_ports, list)
+		free(port);
+
+	free(vlan);
+}
+
 static void
 bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new,
 		   struct vlist_node *node_old)
@@ -920,13 +1018,16 @@  bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new,
 	if (node_old)
 		bridge_set_vlan_state(bst, vlan_old, false);
 
+	if (node_old && node_new)
+		list_splice_init(&vlan_old->hotplug_ports, &vlan_new->hotplug_ports);
+
 	if (node_new)
 		bridge_set_vlan_state(bst, vlan_new, true);
 
 	bst->dev.config_pending = true;
 
 out:
-	free(vlan_old);
+	bridge_vlan_free(vlan_old);
 }
 
 static struct device *
diff --git a/config.c b/config.c
index 526a20f0d1ab..3546787e46f5 100644
--- a/config.c
+++ b/config.c
@@ -323,6 +323,7 @@  config_parse_vlan(struct device *dev, struct uci_section *s)
 
 	vlan->n_ports = n_ports;
 	vlan->ports = port = (struct bridge_vlan_port *)&vlan[1];
+	INIT_LIST_HEAD(&vlan->hotplug_ports);
 	name_buf = (char *)&port[n_ports];
 
 	blobmsg_for_each_attr(cur, tb[BRVLAN_ATTR_PORTS], rem) {
diff --git a/device.h b/device.h
index b25d2675e2fa..617a2724b1fd 100644
--- a/device.h
+++ b/device.h
@@ -227,7 +227,7 @@  struct device {
 
 struct device_hotplug_ops {
 	int (*prepare)(struct device *dev);
-	int (*add)(struct device *main, struct device *member);
+	int (*add)(struct device *main, struct device *member, struct blob_attr *vlan);
 	int (*del)(struct device *main, struct device *member);
 };
 
@@ -248,6 +248,8 @@  struct bridge_vlan {
 	struct bridge_vlan_port *ports;
 	int n_ports;
 
+	struct list_head hotplug_ports;
+
 	uint16_t vid;
 	bool local;
 };
diff --git a/interface.c b/interface.c
index 782174f3b365..c53c091c3232 100644
--- a/interface.c
+++ b/interface.c
@@ -1038,7 +1038,8 @@  interface_remove_link(struct interface *iface, struct device *dev)
 }
 
 static int
-interface_add_link(struct interface *iface, struct device *dev, bool link_ext)
+interface_add_link(struct interface *iface, struct device *dev,
+		   struct blob_attr *vlan, bool link_ext)
 {
 	struct device *mdev = iface->main_dev.dev;
 
@@ -1050,7 +1051,7 @@  interface_add_link(struct interface *iface, struct device *dev, bool link_ext)
 
 	if (mdev) {
 		if (mdev->hotplug_ops)
-			return mdev->hotplug_ops->add(mdev, dev);
+			return mdev->hotplug_ops->add(mdev, dev, vlan);
 		else
 			return UBUS_STATUS_NOT_SUPPORTED;
 	}
@@ -1064,7 +1065,8 @@  interface_add_link(struct interface *iface, struct device *dev, bool link_ext)
 }
 
 int
-interface_handle_link(struct interface *iface, const char *name, bool add, bool link_ext)
+interface_handle_link(struct interface *iface, const char *name,
+		      struct blob_attr *vlan, bool add, bool link_ext)
 {
 	struct device *dev;
 	int ret;
@@ -1081,7 +1083,7 @@  interface_handle_link(struct interface *iface, const char *name, bool add, bool
 		interface_set_device_config(iface, dev);
 		device_set_present(dev, true);
 
-		ret = interface_add_link(iface, dev, link_ext);
+		ret = interface_add_link(iface, dev, vlan, link_ext);
 	} else {
 		ret = interface_remove_link(iface, dev);
 	}
diff --git a/interface.h b/interface.h
index 3d58aa3dee40..9c136b63e377 100644
--- a/interface.h
+++ b/interface.h
@@ -196,7 +196,7 @@  void interface_set_l3_dev(struct interface *iface, struct device *dev);
 void interface_add_user(struct interface_user *dep, struct interface *iface);
 void interface_remove_user(struct interface_user *dep);
 
-int interface_handle_link(struct interface *iface, const char *name, bool add, bool link_ext);
+int interface_handle_link(struct interface *iface, const char *name, struct blob_attr *vlan, bool add, bool link_ext);
 
 void interface_add_error(struct interface *iface, const char *subsystem,
 			 const char *code, const char **data, int n_data);
diff --git a/ubus.c b/ubus.c
index 15c826b427d6..4f167641e3a1 100644
--- a/ubus.c
+++ b/ubus.c
@@ -903,12 +903,14 @@  netifd_handle_dump(struct ubus_context *ctx, struct ubus_object *obj,
 enum {
 	DEV_LINK_NAME,
 	DEV_LINK_EXT,
+	DEV_LINK_VLAN,
 	__DEV_LINK_MAX,
 };
 
 static const struct blobmsg_policy dev_link_policy[__DEV_LINK_MAX] = {
 	[DEV_LINK_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING },
 	[DEV_LINK_EXT] = { .name = "link-ext", .type = BLOBMSG_TYPE_BOOL },
+	[DEV_LINK_VLAN] = { .name = "vlan", .type = BLOBMSG_TYPE_ARRAY },
 };
 
 static int
@@ -933,7 +935,8 @@  netifd_iface_handle_device(struct ubus_context *ctx, struct ubus_object *obj,
 	if (cur)
 		link_ext = blobmsg_get_bool(cur);
 
-	return interface_handle_link(iface, blobmsg_data(tb[DEV_LINK_NAME]), add, link_ext);
+	return interface_handle_link(iface, blobmsg_data(tb[DEV_LINK_NAME]),
+				     tb[DEV_LINK_VLAN], add, link_ext);
 }
 
 
diff --git a/wireless.c b/wireless.c
index efb799204d76..6bf6b585bf73 100644
--- a/wireless.c
+++ b/wireless.c
@@ -319,7 +319,7 @@  static void wireless_interface_handle_link(struct wireless_interface *vif, bool
 		if (!iface)
 			continue;
 
-		interface_handle_link(iface, vif->ifname, up, true);
+		interface_handle_link(iface, vif->ifname, NULL, up, true);
 	}
 }
 
@@ -349,7 +349,7 @@  static void wireless_vlan_handle_link(struct wireless_vlan *vlan, bool up)
 		if (!iface)
 			continue;
 
-		interface_handle_link(iface, vlan->ifname, up, true);
+		interface_handle_link(iface, vlan->ifname, NULL, up, true);
 	}
 }