diff options
author | Felix Fietkau <nbd@nbd.name> | 2020-07-15 17:18:20 +0200 |
---|---|---|
committer | Felix Fietkau <nbd@nbd.name> | 2020-07-19 10:46:02 +0200 |
commit | 0e8cea0f2acdae3812f9603ee046055acd89d717 (patch) | |
tree | effec0c99fc1bc6e72ce70b39b92d31026a55f54 /bridge.c | |
parent | 82bcb641602579f339e9addafe5f1869134a0cd1 (diff) |
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 <nbd@nbd.name>
Diffstat (limited to 'bridge.c')
-rw-r--r-- | bridge.c | 217 |
1 files changed, 216 insertions, 1 deletions
@@ -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; |