BRIDGE_ATTR_QUERY_INTERVAL,
BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL,
BRIDGE_ATTR_LAST_MEMBER_INTERVAL,
+ BRIDGE_ATTR_VLAN_FILTERING,
+ BRIDGE_ATTR_HAS_VLANS,
__BRIDGE_ATTR_MAX
};
[BRIDGE_ATTR_QUERY_INTERVAL] = { "query_interval", BLOBMSG_TYPE_INT32 },
[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 */
};
static const struct uci_blob_param_info bridge_attr_info[__BRIDGE_ATTR_MAX] = {
static void bridge_config_init(struct device *dev);
static void bridge_free(struct device *dev);
static void bridge_dump_info(struct device *dev, struct blob_buf *b);
-enum dev_change_type
+static enum dev_change_type
bridge_reload(struct device *dev, struct blob_attr *attr);
static struct device_type bridge_device_type = {
struct blob_attr *ifnames;
bool active;
bool force_active;
+ bool has_vlans;
struct uloop_timeout retry;
struct bridge_member *primary_port;
struct vlist_node node;
struct bridge_state *bst;
struct device_user dev;
+ uint16_t pvid;
bool present;
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)
{
}
}
+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;
+
+ for (i = 0; i < vlan->n_ports; i++) {
+ if (strcmp(vlan->ports[i].ifname, ifname) != 0)
+ continue;
+
+ 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;
+}
+
+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
+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);
+}
+
+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)
+ bridge_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;
+
+ 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)
+ 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);
if (ret < 0)
return ret;
+ if (bst->has_vlans) {
+ /* 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;
}
bridge_enable_member(struct bridge_member *bm)
{
struct bridge_state *bst = bm->bst;
+ struct bridge_vlan *vlan;
int ret;
if (!bm->present)
goto error;
}
+ if (bst->has_vlans) {
+ /* 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);
if (!bm->present)
return;
- if (bm == bst->primary_port)
- bridge_reset_primary(bst);
-
if (bst->dev.active)
bridge_disable_member(bm);
bm->present = false;
bm->bst->n_present--;
+ if (bm == bst->primary_port)
+ bridge_reset_primary(bst);
+
if (bst->config.bridge_empty)
return;
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_lock();
+
device_remove_user(&bm->dev);
/*
device_set_present(dev, true);
}
+ device_unlock();
+
free(bm);
}
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;
strcpy(bm->name, name);
bm->dev.dev = dev;
vlist_add(&bst->members, &bm->node, bm->name);
- // Need to look up the bridge member again as the above
- // created pointer will be freed in case the bridge member
- // already existed
+ /*
+ * Need to look up the bridge member again as the above
+ * created pointer will be freed in case the bridge member
+ * already existed
+ */
bm = vlist_find(&bst->members, name, bm, node);
if (hotplug && bm)
bm->node.version = -1;
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;
}
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);
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, "%s%s%s%s\n", 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_state *bst;
struct bridge_member *bm;
+ struct bridge_vlan *vlan;
void *list;
bst = container_of(dev, struct bridge_state, dev);
}
blobmsg_close_array(b, list);
+
+ 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
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);
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);
}
/* defaults */
cfg->stp = false;
cfg->forward_delay = 2;
- cfg->igmp_snoop = true;
- cfg->multicast_querier = true;
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;
cfg->hash_max = 512;
cfg->bridge_empty = false;
cfg->priority = 0x7FFF;
+ cfg->vlan_filtering = false;
if ((cur = tb[BRIDGE_ATTR_STP]))
cfg->stp = blobmsg_get_bool(cur);
if ((cur = tb[BRIDGE_ATTR_BRIDGE_EMPTY]))
cfg->bridge_empty = blobmsg_get_bool(cur);
+
+ if ((cur = tb[BRIDGE_ATTR_VLAN_FILTERING]))
+ cfg->vlan_filtering = blobmsg_get_bool(cur);
}
-enum dev_change_type
+static enum dev_change_type
bridge_reload(struct device *dev, struct blob_attr *attr)
{
struct blob_attr *tb_dev[__DEV_ATTR_MAX];
}
}
+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_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)
+{
+ struct bridge_state *bst = container_of(tree, struct bridge_state, dev.vlans);
+ struct bridge_vlan *vlan_new = NULL, *vlan_old = NULL;
+
+ if (!bst->has_vlans || !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)) {
+ 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);
+
+ bst->dev.config_pending = true;
+
+out:
+ bridge_vlan_free(vlan_old);
+}
+
static struct device *
bridge_create(const char *name, struct device_type *devtype,
struct blob_attr *attr)
return NULL;
dev = &bst->dev;
- device_init(dev, devtype, name);
+
+ if (device_init(dev, devtype, name) < 0) {
+ device_cleanup(dev);
+ free(bst);
+ return NULL;
+ }
+
dev->config_pending = true;
bst->retry.cb = bridge_retry_members;
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;