From 0e8cea0f2acdae3812f9603ee046055acd89d717 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 15 Jul 2020 17:18:20 +0200 Subject: [PATCH] bridge: add support for VLAN filtering VLANs can be defined using bridge-vlan sections, like the following example: config bridge-vlan option device 'switch0' option vlan '1' option ports "lan1 lan2 lan3 lan4:t*" Each member port can be confgured with optional attributes after ':' - t: member port is tagged - *: This is the primary VLAN for the port (PVID) VLAN member interfaces are automatically added as bridge members Signed-off-by: Felix Fietkau --- bridge.c | 217 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- config.c | 122 +++++++++++++++++++++++++++++++ device.c | 15 ++++ device.h | 20 +++++ 4 files changed, 373 insertions(+), 1 deletion(-) diff --git a/bridge.c b/bridge.c index e4ec597..c96dcc7 100644 --- a/bridge.c +++ b/bridge.c @@ -117,6 +117,7 @@ struct bridge_member { struct vlist_node node; struct bridge_state *bst; struct device_user dev; + uint16_t pvid; bool present; char name[]; }; @@ -149,14 +150,150 @@ 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) +{ + const char *ifname = bm->dev.dev->ifname; + int i; + + for (i = 0; i < vlan->n_ports; i++) { + if (strcmp(vlan->ports[i].ifname, ifname) != 0) + continue; + + return &vlan->ports[i]; + } + + return NULL; +} + +static bool +bridge_member_vlan_is_pvid(struct bridge_member *bm, struct bridge_vlan_port *port) +{ + return (!bm->pvid && (port->flags & BRVLAN_F_UNTAGGED)) || + (port->flags & BRVLAN_F_PVID); +} + +static void +__bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan, + struct bridge_vlan_port *port, bool add) +{ + uint16_t flags; + + flags = port->flags; + if (bm->pvid == vlan->vid) + flags |= BRVLAN_F_PVID; + + system_bridge_vlan(port->ifname, vlan->vid, add, flags); +} + +static void +bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan, bool add) +{ + struct bridge_vlan_port *port; + + if (!bm->present) + return; + + port = bridge_find_vlan_member_port(bm, vlan); + if (!port) + return; + + if (bridge_member_vlan_is_pvid(bm, port)) + bm->pvid = vlan->vid; + + __bridge_set_member_vlan(bm, vlan, port, add); +} + +static void +brigde_set_local_vlan(struct bridge_state *bst, struct bridge_vlan *vlan, bool add) +{ + if (!vlan->local && add) + return; + + system_bridge_vlan(bst->dev.ifname, vlan->vid, add, BRVLAN_F_SELF); +} + +static void +bridge_set_local_vlans(struct bridge_state *bst, bool add) +{ + struct bridge_vlan *vlan; + + vlist_for_each_element(&bst->dev.vlans, vlan, node) + brigde_set_local_vlan(bst, vlan, add); +} + +static struct bridge_vlan * +bridge_recalc_member_pvid(struct bridge_member *bm) +{ + struct bridge_state *bst = bm->bst; + struct bridge_vlan_port *port; + struct bridge_vlan *vlan, *ret = NULL; + + vlist_for_each_element(&bst->dev.vlans, vlan, node) { + port = bridge_find_vlan_member_port(bm, vlan); + if (!port) + continue; + + if (!bridge_member_vlan_is_pvid(bm, port)) + continue; + + ret = vlan; + if (port->flags & BRVLAN_F_PVID) + break; + } + + return ret; +} + +static void +bridge_set_vlan_state(struct bridge_state *bst, struct bridge_vlan *vlan, bool add) +{ + struct bridge_member *bm; + struct bridge_vlan *vlan2; + + brigde_set_local_vlan(bst, vlan, add); + + vlist_for_each_element(&bst->members, bm, node) { + struct bridge_vlan_port *port; + int new_pvid = -1; + + port = bridge_find_vlan_member_port(bm, vlan); + if (!port) + continue; + + if (add) { + if (bridge_member_vlan_is_pvid(bm, port)) + bm->pvid = vlan->vid; + } else if (bm->pvid == vlan->vid) { + vlan2 = bridge_recalc_member_pvid(bm); + if (vlan2 && vlan2->vid != vlan->vid) { + bridge_set_member_vlan(bm, vlan2, false); + bridge_set_member_vlan(bm, vlan2, true); + } + new_pvid = vlan2 ? vlan2->vid : 0; + } + + if (!bm->present) + continue; + + __bridge_set_member_vlan(bm, vlan, port, add); + if (new_pvid >= 0) + bm->pvid = new_pvid; + } +} + static int bridge_disable_member(struct bridge_member *bm) { struct bridge_state *bst = bm->bst; + struct bridge_vlan *vlan; if (!bm->present) return 0; + vlist_for_each_element(&bst->dev.vlans, vlan, node) + bridge_set_member_vlan(bm, vlan, false); + system_bridge_delif(&bst->dev, bm->dev.dev); device_release(&bm->dev); @@ -177,6 +314,13 @@ bridge_enable_interface(struct bridge_state *bst) if (ret < 0) return ret; + if (bst->config.vlan_filtering) { + /* delete default VLAN 1 */ + system_bridge_vlan(bst->dev.ifname, 1, false, BRVLAN_F_SELF); + + bridge_set_local_vlans(bst, true); + } + bst->active = true; return 0; } @@ -195,6 +339,7 @@ static int bridge_enable_member(struct bridge_member *bm) { struct bridge_state *bst = bm->bst; + struct bridge_vlan *vlan; int ret; if (!bm->present) @@ -220,6 +365,14 @@ bridge_enable_member(struct bridge_member *bm) goto error; } + if (bst->config.vlan_filtering) { + /* delete default VLAN 1 */ + system_bridge_vlan(bm->dev.dev->ifname, 1, false, 0); + + vlist_for_each_element(&bst->dev.vlans, vlan, node) + bridge_set_member_vlan(bm, vlan, true); + } + device_set_present(&bst->dev, true); device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE); @@ -542,8 +695,9 @@ static void bridge_config_init(struct device *dev) { struct bridge_state *bst; + struct bridge_vlan *vlan; struct blob_attr *cur; - int rem; + int i, rem; bst = container_of(dev, struct bridge_state, dev); @@ -559,6 +713,11 @@ bridge_config_init(struct device *dev) bridge_add_member(bst, blobmsg_data(cur)); } } + + vlist_for_each_element(&bst->dev.vlans, vlan, node) + for (i = 0; i < vlan->n_ports; i++) + bridge_add_member(bst, vlan->ports[i].ifname); + vlist_flush(&bst->members); bridge_check_retry(bst); } @@ -716,6 +875,59 @@ bridge_retry_members(struct uloop_timeout *timeout) } } +static int bridge_avl_cmp_u16(const void *k1, const void *k2, void *ptr) +{ + const uint16_t *i1 = k1, *i2 = k2; + + return *i1 - *i2; +} + +static bool +bridge_vlan_equal(struct bridge_vlan *v1, struct bridge_vlan *v2) +{ + int i; + + if (v1->n_ports != v2->n_ports) + return false; + + for (i = 0; i < v1->n_ports; i++) + if (v1->ports[i].flags != v2->ports[i].flags || + strcmp(v1->ports[i].ifname, v2->ports[i].ifname) != 0) + return false; + + return true; +} + +static void +bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct bridge_state *bst = container_of(tree, struct bridge_state, dev.vlans); + struct bridge_vlan *vlan_new = NULL, *vlan_old = NULL; + + if (!bst->config.vlan_filtering || !bst->active) + goto out; + + if (node_old) + vlan_old = container_of(node_old, struct bridge_vlan, node); + if (node_new) + vlan_new = container_of(node_new, struct bridge_vlan, node); + + if (node_new && node_old && bridge_vlan_equal(vlan_old, vlan_new)) + goto out; + + if (node_old) + bridge_set_vlan_state(bst, vlan_old, false); + + if (node_new) + bridge_set_vlan_state(bst, vlan_new, true); + + bst->dev.config_pending = true; + +out: + free(vlan_old); +} + static struct device * bridge_create(const char *name, struct device_type *devtype, struct blob_attr *attr) @@ -745,6 +957,9 @@ bridge_create(const char *name, struct device_type *devtype, vlist_init(&bst->members, avl_strcmp, bridge_member_update); bst->members.keep_old = true; + + vlist_init(&dev->vlans, bridge_avl_cmp_u16, bridge_vlan_update); + bridge_reload(dev, attr); return dev; diff --git a/config.c b/config.c index b1b4bf3..a9f2651 100644 --- a/config.c +++ b/config.c @@ -212,6 +212,127 @@ config_init_devices(void) } } +static void +config_parse_vlan(struct device *dev, struct uci_section *s) +{ + enum { + BRVLAN_ATTR_VID, + BRVLAN_ATTR_LOCAL, + BRVLAN_ATTR_PORTS, + __BRVLAN_ATTR_MAX, + }; + static const struct blobmsg_policy vlan_attrs[__BRVLAN_ATTR_MAX] = { + [BRVLAN_ATTR_VID] = { "vlan", BLOBMSG_TYPE_INT32 }, + [BRVLAN_ATTR_LOCAL] = { "local", BLOBMSG_TYPE_BOOL }, + [BRVLAN_ATTR_PORTS] = { "ports", BLOBMSG_TYPE_ARRAY }, + }; + static const struct uci_blob_param_info vlan_attr_info[__BRVLAN_ATTR_MAX] = { + [BRVLAN_ATTR_PORTS] = { .type = BLOBMSG_TYPE_STRING }, + }; + static const struct uci_blob_param_list vlan_attr_list = { + .n_params = __BRVLAN_ATTR_MAX, + .params = vlan_attrs, + .info = vlan_attr_info, + }; + struct blob_attr *tb[__BRVLAN_ATTR_MAX]; + struct blob_attr *cur; + struct bridge_vlan_port *port; + struct bridge_vlan *vlan; + unsigned int vid; + const char *val; + char *name_buf; + int name_len = 0; + int n_ports = 0; + int rem; + + val = uci_lookup_option_string(uci_ctx, s, "vlan"); + if (!val) + return; + + blob_buf_init(&b, 0); + uci_to_blob(&b, s, &vlan_attr_list); + blobmsg_parse(vlan_attrs, __BRVLAN_ATTR_MAX, tb, blob_data(b.head), blob_len(b.head)); + + if (!tb[BRVLAN_ATTR_VID]) + return; + + vid = blobmsg_get_u32(tb[BRVLAN_ATTR_VID]); + if (!vid || vid > 4095) + return; + + blobmsg_for_each_attr(cur, tb[BRVLAN_ATTR_PORTS], rem) { + name_len += strlen(blobmsg_get_string(cur)) + 1; + n_ports++; + } + + vlan = calloc(1, sizeof(*vlan) + n_ports * sizeof(*port) + name_len); + if (!vlan) + return; + + vlan->vid = vid; + vlan->local = true; + if (tb[BRVLAN_ATTR_LOCAL]) + vlan->local = blobmsg_get_bool(tb[BRVLAN_ATTR_LOCAL]); + + vlan->n_ports = n_ports; + vlan->ports = port = (struct bridge_vlan_port *)&vlan[1]; + name_buf = (char *)&port[n_ports]; + + blobmsg_for_each_attr(cur, tb[BRVLAN_ATTR_PORTS], rem) { + char *sep; + + port->ifname = name_buf; + port->flags = BRVLAN_F_UNTAGGED; + strcpy(name_buf, blobmsg_get_string(cur)); + + sep = strchr(name_buf, ':'); + if (sep) { + for (*sep = 0, sep++; *sep; sep++) + switch (*sep) { + case '*': + port->flags |= BRVLAN_F_PVID; + break; + case 't': + port->flags &= ~BRVLAN_F_UNTAGGED; + break; + } + } + + name_buf += strlen(name_buf) + 1; + port++; + } + + vlist_add(&dev->vlans, &vlan->node, &vlan->vid); +} + + +static void +config_init_vlans(void) +{ + struct uci_element *e; + struct device *dev; + + device_vlan_update(false); + uci_foreach_element(&uci_network->sections, e) { + struct uci_section *s = uci_to_section(e); + const char *name; + + if (strcmp(s->type, "bridge-vlan") != 0) + continue; + + name = uci_lookup_option_string(uci_ctx, s, "device"); + if (!name) + continue; + + dev = device_get(name, 0); + if (!dev || !dev->vlans.update) + continue; + + config_parse_vlan(dev, s); + } + device_vlan_update(true); +} + static struct uci_package * config_init_package(const char *config) { @@ -495,6 +616,7 @@ config_init_all(void) device_reset_config(); config_init_devices(); config_init_interfaces(); + config_init_vlans(); config_init_ip(); config_init_rules(); config_init_globals(); diff --git a/device.c b/device.c index 70cb6a7..5e3a741 100644 --- a/device.c +++ b/device.c @@ -109,6 +109,21 @@ void device_unlock(void) device_free_unused(NULL); } +void device_vlan_update(bool done) +{ + struct device *dev; + + avl_for_each_element(&devices, dev, avl) { + if (!dev->vlans.update) + continue; + + if (!done) + vlist_update(&dev->vlans); + else + vlist_flush(&dev->vlans); + } +} + static int set_device_state(struct device *dev, bool state) { if (state) { diff --git a/device.h b/device.h index 4fb15a3..b25d267 100644 --- a/device.h +++ b/device.h @@ -22,6 +22,7 @@ struct device; struct device_type; struct device_user; struct device_hotplug_ops; +struct bridge_vlan; struct interface; typedef int (*device_state_cb)(struct device *, bool up); @@ -184,6 +185,8 @@ struct device { struct safe_list users; struct safe_list aliases; + struct vlist_tree vlans; + char ifname[IFNAMSIZ + 1]; int ifindex; @@ -234,6 +237,21 @@ enum bridge_vlan_flags { BRVLAN_F_UNTAGGED = (1 << 2), }; +struct bridge_vlan_port { + const char *ifname; + uint16_t flags; +}; + +struct bridge_vlan { + struct vlist_node node; + + struct bridge_vlan_port *ports; + int n_ports; + + uint16_t vid; + bool local; +}; + extern const struct uci_blob_param_list device_attr_list; extern struct device_type simple_device_type; extern struct device_type tunnel_device_type; @@ -241,6 +259,8 @@ extern struct device_type tunnel_device_type; void device_lock(void); void device_unlock(void); +void device_vlan_update(bool done); + int device_type_add(struct device_type *devtype); struct device_type *device_type_get(const char *tname); struct device *device_create(const char *name, struct device_type *type, -- 2.30.2