BRIDGE_ATTR_QUERY_INTERVAL,
BRIDGE_ATTR_QUERY_RESPONSE_INTERVAL,
BRIDGE_ATTR_LAST_MEMBER_INTERVAL,
+ BRIDGE_ATTR_VLAN_FILTERING,
__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 },
};
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);
-struct device_type bridge_device_type = {
+static struct device_type bridge_device_type = {
.name = "bridge",
.config_params = &bridge_attr_list,
struct vlist_node node;
struct bridge_state *bst;
struct device_user dev;
+ uint16_t pvid;
bool present;
char name[];
};
}
}
+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);
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;
}
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->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);
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 struct bridge_member *
-bridge_create_member(struct bridge_state *bst, struct device *dev, bool hotplug)
+bridge_create_member(struct bridge_state *bst, const char *name,
+ struct device *dev, bool hotplug)
{
struct bridge_member *bm;
- bm = calloc(1, sizeof(*bm) + strlen(dev->ifname) + 1);
+ bm = calloc(1, sizeof(*bm) + strlen(name) + 1);
if (!bm)
return NULL;
bm->bst = bst;
bm->dev.cb = bridge_member_cb;
bm->dev.hotplug = hotplug;
- strcpy(bm->name, dev->ifname);
+ 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
- bm = vlist_find(&bst->members, dev->ifname, bm, node);
+ /*
+ * 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;
if (!dev)
return;
- bridge_create_member(bst, dev, false);
+ bridge_create_member(bst, name, dev, false);
}
static int
{
struct bridge_state *bst = container_of(dev, struct bridge_state, dev);
- bridge_create_member(bst, member, true);
+ bridge_create_member(bst, member->ifname, member, true);
return 0;
}
system_if_dump_info(dev, b);
list = blobmsg_open_array(b, "bridge-members");
- vlist_for_each_element(&bst->members, bm, node)
+ vlist_for_each_element(&bst->members, bm, node) {
+ if (bm->dev.dev->hidden)
+ continue;
+
blobmsg_add_string(b, NULL, bm->dev.dev->ifname);
+ }
blobmsg_close_array(b, list);
}
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->query_interval = 12500;
cfg->query_response_interval = 1000;
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];
blobmsg_parse(bridge_attrs, __BRIDGE_ATTR_MAX, tb_br,
blob_data(attr), blob_len(attr));
+ if (tb_dev[DEV_ATTR_MACADDR])
+ bst->primary_port = NULL;
+
bst->ifnames = tb_br[BRIDGE_ATTR_IFNAME];
device_init_settings(dev, tb_dev);
bridge_apply_settings(bst, tb_br);
}
}
+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)
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;