X-Git-Url: http://git.openwrt.org/?a=blobdiff_plain;f=bridge.c;h=2128ec75a0fb9bcb47b502a11973734523531517;hb=HEAD;hp=c96dcc7bd0ab2c02fbfb2c19a527aef4ed501127;hpb=0e8cea0f2acdae3812f9603ee046055acd89d717;p=project%2Fnetifd.git diff --git a/bridge.c b/bridge.c index c96dcc7..2128ec7 100644 --- a/bridge.c +++ b/bridge.c @@ -21,9 +21,10 @@ #include "device.h" #include "interface.h" #include "system.h" +#include "ubus.h" enum { - BRIDGE_ATTR_IFNAME, + BRIDGE_ATTR_PORTS, BRIDGE_ATTR_STP, BRIDGE_ATTR_FORWARD_DELAY, BRIDGE_ATTR_PRIORITY, @@ -39,11 +40,14 @@ enum { BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL, BRIDGE_ATTR_LAST_MEMBER_INTERVAL, BRIDGE_ATTR_VLAN_FILTERING, + BRIDGE_ATTR_HAS_VLANS, + BRIDGE_ATTR_STP_KERNEL, + BRIDGE_ATTR_STP_PROTO, __BRIDGE_ATTR_MAX }; static const struct blobmsg_policy bridge_attrs[__BRIDGE_ATTR_MAX] = { - [BRIDGE_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_ARRAY }, + [BRIDGE_ATTR_PORTS] = { "ports", BLOBMSG_TYPE_ARRAY }, [BRIDGE_ATTR_STP] = { "stp", BLOBMSG_TYPE_BOOL }, [BRIDGE_ATTR_FORWARD_DELAY] = { "forward_delay", BLOBMSG_TYPE_INT32 }, [BRIDGE_ATTR_PRIORITY] = { "priority", BLOBMSG_TYPE_INT32 }, @@ -59,10 +63,13 @@ static const struct blobmsg_policy bridge_attrs[__BRIDGE_ATTR_MAX] = { [BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL] = { "query_response_interval", BLOBMSG_TYPE_INT32 }, [BRIDGE_ATTR_LAST_MEMBER_INTERVAL] = { "last_member_interval", BLOBMSG_TYPE_INT32 }, [BRIDGE_ATTR_VLAN_FILTERING] = { "vlan_filtering", BLOBMSG_TYPE_BOOL }, + [BRIDGE_ATTR_HAS_VLANS] = { "__has_vlans", BLOBMSG_TYPE_BOOL }, /* internal */ + [BRIDGE_ATTR_STP_KERNEL] = { "stp_kernel", BLOBMSG_TYPE_BOOL }, + [BRIDGE_ATTR_STP_PROTO] = { "stp_proto", BLOBMSG_TYPE_STRING }, }; static const struct uci_blob_param_info bridge_attr_info[__BRIDGE_ATTR_MAX] = { - [BRIDGE_ATTR_IFNAME] = { .type = BLOBMSG_TYPE_STRING }, + [BRIDGE_ATTR_PORTS] = { .type = BLOBMSG_TYPE_STRING }, }; static const struct uci_blob_param_list bridge_attr_list = { @@ -74,10 +81,13 @@ static const struct uci_blob_param_list bridge_attr_list = { .next = { &device_attr_list }, }; +static struct blob_buf b; static struct device *bridge_create(const char *name, struct device_type *devtype, struct blob_attr *attr); static void bridge_config_init(struct device *dev); +static void bridge_dev_vlan_update(struct device *dev); static void bridge_free(struct device *dev); +static void bridge_stp_init(struct device *dev); static void bridge_dump_info(struct device *dev, struct blob_buf *b); static enum dev_change_type bridge_reload(struct device *dev, struct blob_attr *attr); @@ -91,9 +101,11 @@ static struct device_type bridge_device_type = { .create = bridge_create, .config_init = bridge_config_init, + .vlan_update = bridge_dev_vlan_update, .reload = bridge_reload, .free = bridge_free, .dump_info = bridge_dump_info, + .stp_init = bridge_stp_init, }; struct bridge_state { @@ -102,9 +114,10 @@ struct bridge_state { struct blob_attr *config_data; struct bridge_config config; - struct blob_attr *ifnames; + struct blob_attr *ports; bool active; bool force_active; + bool has_vlans; struct uloop_timeout retry; struct bridge_member *primary_port; @@ -117,8 +130,12 @@ struct bridge_member { struct vlist_node node; struct bridge_state *bst; struct device_user dev; + struct uloop_timeout check_timer; + struct device_vlan_range *extra_vlan; + int n_extra_vlan; uint16_t pvid; bool present; + bool active; char name[]; }; @@ -153,6 +170,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 +181,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; } @@ -183,7 +208,7 @@ __bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan, if (bm->pvid == vlan->vid) flags |= BRVLAN_F_PVID; - system_bridge_vlan(port->ifname, vlan->vid, add, flags); + system_bridge_vlan(port->ifname, vlan->vid, -1, add, flags); } static void @@ -198,19 +223,21 @@ bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan, bool if (!port) return; - if (bridge_member_vlan_is_pvid(bm, port)) + if (!add && bm->pvid == vlan->vid) + bm->pvid = 0; + else if (add && 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) +bridge_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); + system_bridge_vlan(bst->dev.ifname, vlan->vid, -1, add, BRVLAN_F_SELF); } static void @@ -219,7 +246,7 @@ 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); + bridge_set_local_vlan(bst, vlan, add); } static struct bridge_vlan * @@ -250,12 +277,12 @@ bridge_set_vlan_state(struct bridge_state *bst, struct bridge_vlan *vlan, bool a { struct bridge_member *bm; struct bridge_vlan *vlan2; + bool clear_pvid = false; - brigde_set_local_vlan(bst, vlan, add); + bridge_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) @@ -268,63 +295,105 @@ bridge_set_vlan_state(struct bridge_state *bst, struct bridge_vlan *vlan, bool a vlan2 = bridge_recalc_member_pvid(bm); if (vlan2 && vlan2->vid != vlan->vid) { bridge_set_member_vlan(bm, vlan2, false); + bm->pvid = vlan2->vid; bridge_set_member_vlan(bm, vlan2, true); + } else if (!vlan2) { + clear_pvid = true; } - new_pvid = vlan2 ? vlan2->vid : 0; } - if (!bm->present) - continue; + if (bm->present) + __bridge_set_member_vlan(bm, vlan, port, add); - __bridge_set_member_vlan(bm, vlan, port, add); - if (new_pvid >= 0) - bm->pvid = new_pvid; + if (clear_pvid) + bm->pvid = 0; } } static int -bridge_disable_member(struct bridge_member *bm) +bridge_disable_member(struct bridge_member *bm, bool keep_dev) { struct bridge_state *bst = bm->bst; struct bridge_vlan *vlan; - if (!bm->present) + if (!bm->present || !bm->active) return 0; + bm->active = false; 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); + if (!keep_dev) + device_release(&bm->dev); device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE); return 0; } +static void bridge_stp_notify(struct bridge_state *bst) +{ + struct bridge_config *cfg = &bst->config; + + if (!cfg->stp || cfg->stp_kernel) + return; + + blob_buf_init(&b, 0); + blobmsg_add_string(&b, "name", bst->dev.ifname); + if (cfg->stp_proto) + blobmsg_add_string(&b, "proto", cfg->stp_proto); + blobmsg_add_u32(&b, "forward_delay", cfg->forward_delay); + blobmsg_add_u32(&b, "hello_time", cfg->hello_time); + blobmsg_add_u32(&b, "max_age", cfg->max_age); + if (cfg->flags & BRIDGE_OPT_AGEING_TIME) + blobmsg_add_u32(&b, "ageing_time", cfg->ageing_time); + netifd_ubus_device_notify("stp_init", b.head, 1000); +} + static int bridge_enable_interface(struct bridge_state *bst) { - int ret; + struct device *dev = &bst->dev; + int i, ret; if (bst->active) return 0; - ret = system_bridge_addbr(&bst->dev, &bst->config); + bridge_stp_notify(bst); + ret = system_bridge_addbr(dev, &bst->config); if (ret < 0) return ret; - if (bst->config.vlan_filtering) { + if (bst->has_vlans) { /* delete default VLAN 1 */ - system_bridge_vlan(bst->dev.ifname, 1, false, BRVLAN_F_SELF); + system_bridge_vlan(bst->dev.ifname, 1, -1, false, BRVLAN_F_SELF); bridge_set_local_vlans(bst, true); } + for (i = 0; i < dev->n_extra_vlan; i++) + system_bridge_vlan(dev->ifname, dev->extra_vlan[i].start, + dev->extra_vlan[i].end, true, BRVLAN_F_SELF); + bst->active = true; return 0; } +static void +bridge_stp_init(struct device *dev) +{ + struct bridge_state *bst; + + bst = container_of(dev, struct bridge_state, dev); + if (!bst->config.stp || !bst->active) + return; + + bridge_stp_notify(bst); + system_bridge_set_stp_state(&bst->dev, false); + system_bridge_set_stp_state(&bst->dev, true); +} + static void bridge_disable_interface(struct bridge_state *bst) { @@ -335,11 +404,196 @@ bridge_disable_interface(struct bridge_state *bst) bst->active = false; } +static struct bridge_vlan * +bridge_hotplug_get_vlan(struct bridge_state *bst, uint16_t vid, bool create) +{ + struct bridge_vlan *vlan; + + vlan = vlist_find(&bst->dev.vlans, &vid, vlan, node); + if (vlan || !create) + return vlan; + + vlan = calloc(1, sizeof(*vlan)); + vlan->vid = vid; + vlan->local = true; + INIT_LIST_HEAD(&vlan->hotplug_ports); + vlist_add(&bst->dev.vlans, &vlan->node, &vlan->vid); + vlan->node.version = -1; + + return vlan; +} + +static struct bridge_vlan_hotplug_port * +bridge_hotplug_get_vlan_port(struct bridge_vlan *vlan, const char *ifname) +{ + struct bridge_vlan_hotplug_port *port; + + list_for_each_entry(port, &vlan->hotplug_ports, list) + if (!strcmp(port->port.ifname, ifname)) + return port; + + return NULL; +} + +static void +bridge_hotplug_set_member_vlans(struct bridge_state *bst, struct blob_attr *vlans, + struct bridge_member *bm, bool add, bool untracked) +{ + const char *ifname = bm->name; + struct device_vlan_range *r; + struct bridge_vlan *vlan; + struct blob_attr *cur; + int n_vlans; + size_t rem; + + if (!vlans) + return; + + if (add) { + bm->n_extra_vlan = 0; + n_vlans = blobmsg_check_array(vlans, BLOBMSG_TYPE_STRING); + if (n_vlans < 1) + return; + + bm->extra_vlan = realloc(bm->extra_vlan, n_vlans * sizeof(*bm->extra_vlan)); + } + + blobmsg_for_each_attr(cur, vlans, rem) { + struct bridge_vlan_hotplug_port *port; + unsigned int vid, vid_end; + uint16_t flags = 0; + char *name_buf; + char *end; + + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + continue; + + vid = strtoul(blobmsg_get_string(cur), &end, 0); + vid_end = vid; + if (!vid || vid > 4095) + continue; + + if (*end == '-') { + vid_end = strtoul(end + 1, &end, 0); + if (vid_end < vid) + continue; + } + + if (end && *end) { + if (*end != ':') + continue; + + for (end++; *end; end++) { + switch (*end) { + case 'u': + flags |= BRVLAN_F_UNTAGGED; + fallthrough; + case '*': + flags |= BRVLAN_F_PVID; + break; + } + } + } + + vlan = bridge_hotplug_get_vlan(bst, vid, !!flags); + if (!vlan || vid_end > vid || untracked) { + if (add) { + if (!untracked) { + r = &bm->extra_vlan[bm->n_extra_vlan++]; + r->start = vid; + r->end = vid_end; + } + if (bm->active) + system_bridge_vlan(ifname, vid, vid_end, true, flags); + } else if (bm->active) { + system_bridge_vlan(ifname, vid, vid_end, false, 0); + } + continue; + } + + if (vlan->pending) { + vlan->pending = false; + bridge_set_vlan_state(bst, vlan, true); + } + + port = bridge_hotplug_get_vlan_port(vlan, ifname); + if (!add) { + if (!port) + continue; + + __bridge_set_member_vlan(bm, vlan, &port->port, false); + list_del(&port->list); + free(port); + continue; + } + + if (port) { + if (port->port.flags == flags) + continue; + + __bridge_set_member_vlan(bm, vlan, &port->port, false); + port->port.flags = flags; + __bridge_set_member_vlan(bm, vlan, &port->port, true); + continue; + } + + 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); + + if (!bm) + continue; + + __bridge_set_member_vlan(bm, vlan, &port->port, true); + } +} + + +static void +bridge_member_add_extra_vlans(struct bridge_member *bm) +{ + struct device *dev = bm->dev.dev; + int i; + + for (i = 0; i < dev->n_extra_vlan; i++) + system_bridge_vlan(dev->ifname, dev->extra_vlan[i].start, + dev->extra_vlan[i].end, true, 0); + for (i = 0; i < bm->n_extra_vlan; i++) + system_bridge_vlan(dev->ifname, bm->extra_vlan[i].start, + bm->extra_vlan[i].end, true, 0); +} + +static void +bridge_member_enable_vlans(struct bridge_member *bm) +{ + struct bridge_state *bst = bm->bst; + struct device *dev = bm->dev.dev; + struct bridge_vlan *vlan; + + if (dev->settings.auth) { + bridge_hotplug_set_member_vlans(bst, dev->config_auth_vlans, bm, + !dev->auth_status, true); + bridge_hotplug_set_member_vlans(bst, dev->auth_vlans, bm, + dev->auth_status, true); + } + + if (dev->settings.auth && !dev->auth_status) + return; + + bridge_member_add_extra_vlans(bm); + vlist_for_each_element(&bst->dev.vlans, vlan, node) + bridge_set_member_vlan(bm, vlan, true); +} + static int bridge_enable_member(struct bridge_member *bm) { struct bridge_state *bst = bm->bst; - struct bridge_vlan *vlan; + struct device *dev; int ret; if (!bm->present) @@ -359,22 +613,30 @@ bridge_enable_member(struct bridge_member *bm) if (ret < 0) goto error; - ret = system_bridge_addif(&bst->dev, bm->dev.dev); - if (ret < 0) { - D(DEVICE, "Bridge device %s could not be added\n", bm->dev.dev->ifname); - goto error; + dev = bm->dev.dev; + if (dev->settings.auth && !bst->has_vlans && !dev->auth_status) + return -1; + + if (!bm->active) { + ret = system_bridge_addif(&bst->dev, bm->dev.dev); + if (ret < 0) { + D(DEVICE, "Bridge device %s could not be added", bm->dev.dev->ifname); + goto error; + } + + bm->active = true; } - if (bst->config.vlan_filtering) { + if (bst->has_vlans) { /* delete default VLAN 1 */ - system_bridge_vlan(bm->dev.dev->ifname, 1, false, 0); + system_bridge_vlan(bm->dev.dev->ifname, 1, -1, false, 0); - vlist_for_each_element(&bst->dev.vlans, vlan, node) - bridge_set_member_vlan(bm, vlan, true); + bridge_member_enable_vlans(bm); } device_set_present(&bst->dev, true); - device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE); + if (!dev->settings.auth || dev->auth_status) + device_broadcast_event(&bst->dev, DEV_EVENT_TOPO_CHANGE); return 0; @@ -396,7 +658,7 @@ bridge_remove_member(struct bridge_member *bm) return; if (bst->dev.active) - bridge_disable_member(bm); + bridge_disable_member(bm, false); bm->present = false; bm->bst->n_present--; @@ -415,10 +677,37 @@ 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); + +restart: + vlist_for_each_element(&bst->dev.vlans, vlan, node) { + struct bridge_vlan_hotplug_port *port, *tmp; + bool free_port = false; + + 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); + free_port = true; + } + + if (!free_port || !list_empty(&vlan->hotplug_ports) || + vlan->n_ports || vlan->node.version != -1) + continue; + + vlist_delete(&bst->dev.vlans, &vlan->node); + goto restart; + } + device_remove_user(&bm->dev); + uloop_timeout_cancel(&bm->check_timer); /* * When reloading the config and moving a device from one bridge to @@ -445,10 +734,27 @@ bridge_check_retry(struct bridge_state *bst) } static void -bridge_member_cb(struct device_user *dev, enum device_event ev) +bridge_member_check_cb(struct uloop_timeout *t) { - struct bridge_member *bm = container_of(dev, struct bridge_member, dev); + struct bridge_member *bm; + struct bridge_state *bst; + + bm = container_of(t, struct bridge_member, check_timer); + bst = bm->bst; + + if (system_bridge_vlan_check(&bst->dev, bm->dev.dev->ifname) <= 0) + return; + + bridge_disable_member(bm, true); + bridge_enable_member(bm); +} + +static void +bridge_member_cb(struct device_user *dep, enum device_event ev) +{ + struct bridge_member *bm = container_of(dep, struct bridge_member, dev); struct bridge_state *bst = bm->bst; + struct device *dev = dep->dev; switch (ev) { case DEV_EVENT_ADD: @@ -459,19 +765,39 @@ bridge_member_cb(struct device_user *dev, enum device_event ev) if (bst->n_present == 1) device_set_present(&bst->dev, true); - if (bst->dev.active && !bridge_enable_member(bm)) { - /* - * Adding a bridge member can overwrite the bridge mtu - * in the kernel, apply the bridge settings in case the - * bridge mtu is set - */ - system_if_apply_settings(&bst->dev, &bst->dev.settings, - DEV_OPT_MTU | DEV_OPT_MTU6); - } + fallthrough; + case DEV_EVENT_AUTH_UP: + if (!bst->dev.active) + break; + + if (bridge_enable_member(bm)) + break; + + /* + * Adding a bridge member can overwrite the bridge mtu + * in the kernel, apply the bridge settings in case the + * bridge mtu is set + */ + system_if_apply_settings(&bst->dev, &bst->dev.settings, + DEV_OPT_MTU | DEV_OPT_MTU6); + break; + case DEV_EVENT_LINK_UP: + if (!bst->has_vlans) + break; + + if (dev->settings.auth) + bridge_enable_member(bm); + uloop_timeout_set(&bm->check_timer, 1000); + break; + case DEV_EVENT_LINK_DOWN: + if (!dev->settings.auth) + break; + + bridge_disable_member(bm, true); break; case DEV_EVENT_REMOVE: - if (dev->hotplug) { + if (dep->hotplug && !dev->sys_present) { vlist_delete(&bst->members, &bm->node); return; } @@ -493,7 +819,7 @@ bridge_set_down(struct bridge_state *bst) bst->set_state(&bst->dev, false); vlist_for_each_element(&bst->members, bm, node) - bridge_disable_member(bm); + bridge_disable_member(bm, false); bridge_disable_interface(bst); @@ -506,6 +832,7 @@ bridge_set_up(struct bridge_state *bst) struct bridge_member *bm; int ret; + bst->has_vlans = !avl_is_empty(&bst->dev.vlans.avl); if (!bst->n_present) { if (!bst->force_active) return -ENOENT; @@ -549,8 +876,8 @@ bridge_set_state(struct device *dev, bool up) } static struct bridge_member * -bridge_create_member(struct bridge_state *bst, const char *name, - struct device *dev, bool hotplug) +bridge_alloc_member(struct bridge_state *bst, const char *name, + struct device *dev, bool hotplug) { struct bridge_member *bm; @@ -561,8 +888,18 @@ bridge_create_member(struct bridge_state *bst, const char *name, bm->bst = bst; bm->dev.cb = bridge_member_cb; bm->dev.hotplug = hotplug; + bm->check_timer.cb = bridge_member_check_cb; strcpy(bm->name, name); bm->dev.dev = dev; + + return bm; +} + +static void bridge_insert_member(struct bridge_member *bm, const char *name) +{ + struct bridge_state *bst = bm->bst; + bool hotplug = bm->dev.hotplug; + vlist_add(&bst->members, &bm->node, bm->name); /* * Need to look up the bridge member again as the above @@ -572,8 +909,17 @@ bridge_create_member(struct bridge_state *bst, const char *name, bm = vlist_find(&bst->members, name, bm, node); if (hotplug && bm) bm->node.version = -1; +} - return bm; +static void +bridge_create_member(struct bridge_state *bst, const char *name, + struct device *dev, bool hotplug) +{ + struct bridge_member *bm; + + bm = bridge_alloc_member(bst, name, dev, hotplug); + if (bm) + bridge_insert_member(bm, name); } static void @@ -617,17 +963,26 @@ bridge_add_member(struct bridge_state *bst, const char *name) } 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); + struct bridge_member *bm; + bool new_entry = false; - bridge_create_member(bst, member->ifname, member, true); + bm = vlist_find(&bst->members, member->ifname, bm, node); + if (!bm) { + new_entry = true; + bm = bridge_alloc_member(bst, member->ifname, member, true); + } + bridge_hotplug_set_member_vlans(bst, vlan, bm, true, false); + if (new_entry) + bridge_insert_member(bm, member->ifname); return 0; } static int -bridge_hotplug_del(struct device *dev, struct device *member) +bridge_hotplug_del(struct device *dev, struct device *member, struct blob_attr *vlan) { struct bridge_state *bst = container_of(dev, struct bridge_state, dev); struct bridge_member *bm; @@ -636,15 +991,22 @@ bridge_hotplug_del(struct device *dev, struct device *member) if (!bm) return UBUS_STATUS_NOT_FOUND; + bridge_hotplug_set_member_vlans(bst, vlan, bm, false, false); + if (!bm->dev.hotplug) + return 0; + vlist_delete(&bst->members, &bm->node); return 0; } static int -bridge_hotplug_prepare(struct device *dev) +bridge_hotplug_prepare(struct device *dev, struct device **bridge_dev) { struct bridge_state *bst; + if (bridge_dev) + *bridge_dev = dev; + bst = container_of(dev, struct bridge_state, dev); bst->force_active = true; device_set_present(&bst->dev, true); @@ -665,18 +1027,61 @@ bridge_free(struct device *dev) bst = container_of(dev, struct bridge_state, dev); vlist_flush_all(&bst->members); + vlist_flush_all(&dev->vlans); + kvlist_free(&dev->vlan_aliases); free(bst->config_data); free(bst); } +static void +bridge_dump_port(struct blob_buf *b, struct bridge_vlan_port *port) +{ + bool tagged = !(port->flags & BRVLAN_F_UNTAGGED); + bool pvid = (port->flags & BRVLAN_F_PVID); + + blobmsg_printf(b, NULL, "%s%s%s%s", port->ifname, + tagged || pvid ? ":" : "", + tagged ? "t" : "", + pvid ? "*" : ""); +} + +static void +bridge_dump_vlan(struct blob_buf *b, struct bridge_vlan *vlan) +{ + struct bridge_vlan_hotplug_port *port; + void *c, *p; + int i; + + c = blobmsg_open_table(b, NULL); + + blobmsg_add_u32(b, "id", vlan->vid); + blobmsg_add_u8(b, "local", vlan->local); + + p = blobmsg_open_array(b, "ports"); + + for (i = 0; i < vlan->n_ports; i++) + bridge_dump_port(b, &vlan->ports[i]); + + list_for_each_entry(port, &vlan->hotplug_ports, list) + bridge_dump_port(b, &port->port); + + blobmsg_close_array(b, p); + + blobmsg_close_table(b, c); +} + static void bridge_dump_info(struct device *dev, struct blob_buf *b) { + struct bridge_config *cfg; struct bridge_state *bst; struct bridge_member *bm; + struct bridge_vlan *vlan; void *list; + void *c; bst = container_of(dev, struct bridge_state, dev); + cfg = &bst->config; system_if_dump_info(dev, b); list = blobmsg_open_array(b, "bridge-members"); @@ -689,6 +1094,39 @@ bridge_dump_info(struct device *dev, struct blob_buf *b) } blobmsg_close_array(b, list); + + c = blobmsg_open_table(b, "bridge-attributes"); + + blobmsg_add_u8(b, "stp", cfg->stp); + blobmsg_add_u32(b, "forward_delay", cfg->forward_delay); + blobmsg_add_u32(b, "priority", cfg->priority); + blobmsg_add_u32(b, "ageing_time", cfg->ageing_time); + blobmsg_add_u32(b, "hello_time", cfg->hello_time); + blobmsg_add_u32(b, "max_age", cfg->max_age); + blobmsg_add_u8(b, "igmp_snooping", cfg->igmp_snoop); + blobmsg_add_u8(b, "bridge_empty", cfg->bridge_empty); + blobmsg_add_u8(b, "multicast_querier", cfg->multicast_querier); + blobmsg_add_u32(b, "hash_max", cfg->hash_max); + blobmsg_add_u32(b, "robustness", cfg->robustness); + blobmsg_add_u32(b, "query_interval", cfg->query_interval); + blobmsg_add_u32(b, "query_response_interval", cfg->query_response_interval); + blobmsg_add_u32(b, "last_member_interval", cfg->last_member_interval); + blobmsg_add_u8(b, "vlan_filtering", cfg->vlan_filtering); + blobmsg_add_u8(b, "stp_kernel", cfg->stp_kernel); + if (cfg->stp_proto) + blobmsg_add_string(b, "stp_proto", cfg->stp_proto); + + blobmsg_close_table(b, c); + + if (avl_is_empty(&dev->vlans.avl)) + return; + + list = blobmsg_open_array(b, "bridge-vlans"); + + vlist_for_each_element(&bst->dev.vlans, vlan, node) + bridge_dump_vlan(b, vlan); + + blobmsg_close_array(b, list); } static void @@ -697,7 +1135,8 @@ bridge_config_init(struct device *dev) struct bridge_state *bst; struct bridge_vlan *vlan; struct blob_attr *cur; - int i, rem; + size_t rem; + int i; bst = container_of(dev, struct bridge_state, dev); @@ -708,8 +1147,8 @@ bridge_config_init(struct device *dev) bst->n_failed = 0; vlist_update(&bst->members); - if (bst->ifnames) { - blobmsg_for_each_attr(cur, bst->ifnames, rem) { + if (bst->ports) { + blobmsg_for_each_attr(cur, bst->ports, rem) { bridge_add_member(bst, blobmsg_data(cur)); } } @@ -729,9 +1168,12 @@ bridge_apply_settings(struct bridge_state *bst, struct blob_attr **tb) struct blob_attr *cur; /* defaults */ + memset(cfg, 0, sizeof(*cfg)); cfg->stp = false; - cfg->forward_delay = 2; + cfg->stp_kernel = false; cfg->robustness = 2; + cfg->igmp_snoop = false; + cfg->multicast_querier = false; cfg->query_interval = 12500; cfg->query_response_interval = 1000; cfg->last_member_interval = 100; @@ -740,9 +1182,19 @@ bridge_apply_settings(struct bridge_state *bst, struct blob_attr **tb) cfg->priority = 0x7FFF; cfg->vlan_filtering = false; + cfg->forward_delay = 8; + cfg->max_age = 10; + cfg->hello_time = 1; + if ((cur = tb[BRIDGE_ATTR_STP])) cfg->stp = blobmsg_get_bool(cur); + if ((cur = tb[BRIDGE_ATTR_STP_KERNEL])) + cfg->stp = blobmsg_get_bool(cur); + + if ((cur = tb[BRIDGE_ATTR_STP_PROTO])) + cfg->stp_proto = blobmsg_get_string(cur); + if ((cur = tb[BRIDGE_ATTR_FORWARD_DELAY])) cfg->forward_delay = blobmsg_get_u32(cur); @@ -783,15 +1235,11 @@ bridge_apply_settings(struct bridge_state *bst, struct blob_attr **tb) cfg->flags |= BRIDGE_OPT_AGEING_TIME; } - if ((cur = tb[BRIDGE_ATTR_HELLO_TIME])) { + if ((cur = tb[BRIDGE_ATTR_HELLO_TIME])) cfg->hello_time = blobmsg_get_u32(cur); - cfg->flags |= BRIDGE_OPT_HELLO_TIME; - } - if ((cur = tb[BRIDGE_ATTR_MAX_AGE])) { + if ((cur = tb[BRIDGE_ATTR_MAX_AGE])) cfg->max_age = blobmsg_get_u32(cur); - cfg->flags |= BRIDGE_OPT_MAX_AGE; - } if ((cur = tb[BRIDGE_ATTR_BRIDGE_EMPTY])) cfg->bridge_empty = blobmsg_get_bool(cur); @@ -806,11 +1254,11 @@ bridge_reload(struct device *dev, struct blob_attr *attr) struct blob_attr *tb_dev[__DEV_ATTR_MAX]; struct blob_attr *tb_br[__BRIDGE_ATTR_MAX]; enum dev_change_type ret = DEV_CONFIG_APPLIED; - unsigned long diff; struct bridge_state *bst; + unsigned long diff[2] = {}; - BUILD_BUG_ON(sizeof(diff) < __BRIDGE_ATTR_MAX / 8); - BUILD_BUG_ON(sizeof(diff) < __DEV_ATTR_MAX / 8); + BUILD_BUG_ON(sizeof(diff) < __BRIDGE_ATTR_MAX / BITS_PER_LONG); + BUILD_BUG_ON(sizeof(diff) < __DEV_ATTR_MAX / BITS_PER_LONG); bst = container_of(dev, struct bridge_state, dev); attr = blob_memdup(attr); @@ -823,7 +1271,7 @@ bridge_reload(struct device *dev, struct blob_attr *attr) if (tb_dev[DEV_ATTR_MACADDR]) bst->primary_port = NULL; - bst->ifnames = tb_br[BRIDGE_ATTR_IFNAME]; + bst->ports = tb_br[BRIDGE_ATTR_PORTS]; device_init_settings(dev, tb_dev); bridge_apply_settings(bst, tb_br); @@ -834,18 +1282,23 @@ bridge_reload(struct device *dev, struct blob_attr *attr) blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev, blob_data(bst->config_data), blob_len(bst->config_data)); - diff = 0; - uci_blob_diff(tb_dev, otb_dev, &device_attr_list, &diff); - if (diff) - ret = DEV_CONFIG_RESTART; + uci_blob_diff(tb_dev, otb_dev, &device_attr_list, diff); + if (diff[0] | diff[1]) { + ret = DEV_CONFIG_RESTART; + D(DEVICE, "Bridge %s device attributes have changed, diff=[%lx %lx]", + dev->ifname, diff[1], diff[0]); + } blobmsg_parse(bridge_attrs, __BRIDGE_ATTR_MAX, otb_br, blob_data(bst->config_data), blob_len(bst->config_data)); - diff = 0; - uci_blob_diff(tb_br, otb_br, &bridge_attr_list, &diff); - if (diff & ~(1 << BRIDGE_ATTR_IFNAME)) - ret = DEV_CONFIG_RESTART; + diff[0] = diff[1] = 0; + uci_blob_diff(tb_br, otb_br, &bridge_attr_list, diff); + if (diff[0] & ~(1 << BRIDGE_ATTR_PORTS)) { + ret = DEV_CONFIG_RESTART; + D(DEVICE, "Bridge %s attributes have changed, diff=[%lx %lx]", + dev->ifname, diff[1], diff[0]); + } bridge_config_init(dev); } @@ -898,6 +1351,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) @@ -905,27 +1372,46 @@ bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new, 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)) + if (!bst->has_vlans || !bst->active) + goto out; + + if (node_new && node_old && bridge_vlan_equal(vlan_old, vlan_new)) { + list_splice_init(&vlan_old->hotplug_ports, &vlan_new->hotplug_ports); goto out; + } 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); + vlan_new->pending = true; +out: bst->dev.config_pending = true; + bridge_vlan_free(vlan_old); +} -out: - free(vlan_old); +static void +bridge_dev_vlan_update(struct device *dev) +{ + struct bridge_state *bst = container_of(dev, struct bridge_state, dev); + struct bridge_vlan *vlan; + + vlist_for_each_element(&dev->vlans, vlan, node) { + if (!vlan->pending) + continue; + + vlan->pending = false; + bridge_set_vlan_state(bst, vlan, true); + } } static struct device *