diff options
author | Felix Fietkau <nbd@openwrt.org> | 2011-02-19 15:21:52 +0100 |
---|---|---|
committer | Felix Fietkau <nbd@openwrt.org> | 2011-03-27 01:42:33 +0100 |
commit | 5d1fff7af6f77c9bf0d46572c7af563cd9fc55b3 (patch) | |
tree | 76d080e6b59f9235cd47b0e047f662c1f0a6ce00 /bridge.c |
Initial import
Diffstat (limited to 'bridge.c')
-rw-r--r-- | bridge.c | 343 |
1 files changed, 343 insertions, 0 deletions
diff --git a/bridge.c b/bridge.c new file mode 100644 index 0000000..2aa0e68 --- /dev/null +++ b/bridge.c @@ -0,0 +1,343 @@ +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <assert.h> +#include <errno.h> + +#include "netifd.h" +#include "system.h" + +struct bridge_state { + struct device dev; + device_state_cb set_state; + + bool active; + + struct list_head members; + int n_present; +}; + +struct bridge_member { + struct list_head list; + struct bridge_state *bst; + struct device_user dev; + bool present; +}; + +static int +bridge_disable_member(struct bridge_member *bm) +{ + struct bridge_state *bst = bm->bst; + + if (!bm->present) + return 0; + + system_bridge_delif(&bst->dev, bm->dev.dev); + release_device(bm->dev.dev); + + return 0; +} + +static int +bridge_enable_member(struct bridge_member *bm) +{ + struct bridge_state *bst = bm->bst; + int ret; + + if (!bm->present) + return 0; + + ret = claim_device(bm->dev.dev); + if (ret < 0) + goto error; + + ret = system_bridge_addif(&bst->dev, bm->dev.dev); + if (ret < 0) + goto error; + + return 0; + +error: + bm->present = false; + bst->n_present--; + return ret; +} + +static void +bridge_member_cb(struct device_user *dev, enum device_event ev) +{ + struct bridge_member *bm = container_of(dev, struct bridge_member, dev); + struct bridge_state *bst = bm->bst; + + switch (ev) { + case DEV_EVENT_ADD: + assert(!bm->present); + + bm->present = true; + bst->n_present++; + + if (bst->dev.active) + bridge_enable_member(bm); + else if (bst->n_present == 1) + set_device_present(&bst->dev, true); + + break; + case DEV_EVENT_REMOVE: + if (!bm->present) + return; + + if (bst->dev.active) + bridge_disable_member(bm); + + bm->present = false; + bm->bst->n_present--; + if (bst->n_present == 0) + set_device_present(&bst->dev, false); + + break; + default: + return; + } +} + +static int +bridge_set_down(struct bridge_state *bst) +{ + struct bridge_member *bm; + + bst->set_state(&bst->dev, false); + + list_for_each_entry(bm, &bst->members, list) + bridge_disable_member(bm); + + system_bridge_delbr(&bst->dev); + + return 0; +} + +static int +bridge_set_up(struct bridge_state *bst) +{ + struct bridge_member *bm; + int ret; + + if (!bst->n_present) + return -ENOENT; + + ret = system_bridge_addbr(&bst->dev); + if (ret < 0) + goto out; + + list_for_each_entry(bm, &bst->members, list) + bridge_enable_member(bm); + + if (!bst->n_present) { + /* initialization of all member interfaces failed */ + system_bridge_delbr(&bst->dev); + set_device_present(&bst->dev, false); + return -ENOENT; + } + + ret = bst->set_state(&bst->dev, true); + if (ret < 0) + bridge_set_down(bst); + +out: + return ret; +} + +static int +bridge_set_state(struct device *dev, bool up) +{ + struct bridge_state *bst; + + bst = container_of(dev, struct bridge_state, dev); + + if (up) + return bridge_set_up(bst); + else + return bridge_set_down(bst); +} + +static struct bridge_member * +bridge_create_member(struct bridge_state *bst, struct device *dev) +{ + struct bridge_member *bm; + + bm = calloc(1, sizeof(*bm)); + bm->bst = bst; + bm->dev.cb = bridge_member_cb; + add_device_user(&bm->dev, dev); + + list_add(&bm->list, &bst->members); + + if (bst->dev.active) + bridge_enable_member(bm); + + return bm; +} + +static void +bridge_free_member(struct bridge_member *bm) +{ + if (bm->present) { + bridge_member_cb(&bm->dev, DEV_EVENT_REMOVE); + bm->bst->n_present--; + if (bm->bst->dev.active) + bridge_disable_member(bm); + } + + list_del(&bm->list); + remove_device_user(&bm->dev); + free(bm); +} + +static void +bridge_add_member(struct bridge_state *bst, const char *name) +{ + struct device *dev; + + dev = get_device(name, true); + if (!dev) + return; + + bridge_create_member(bst, dev); +} + +static int +bridge_hotplug_add(struct device *dev, struct device *member) +{ + struct bridge_state *bst = container_of(dev, struct bridge_state, dev); + + bridge_create_member(bst, member); + + return 0; +} + +static int +bridge_hotplug_del(struct device *dev, struct device *member) +{ + struct bridge_state *bst = container_of(dev, struct bridge_state, dev); + struct bridge_member *bm; + + list_for_each_entry(bm, &bst->members, list) { + if (bm->dev.dev != member) + continue; + + bridge_free_member(bm); + return 0; + } + + return -ENOENT; +} + +static const struct device_hotplug_ops bridge_ops = { + .add = bridge_hotplug_add, + .del = bridge_hotplug_del +}; + +static void +bridge_parse_config(struct bridge_state *bst, struct uci_section *s) +{ + struct uci_element *e; + struct uci_option *o; + char buf[IFNAMSIZ + 1]; + char *p, *end; + int len; + + o = uci_lookup_option(uci_ctx, s, "ifname"); + if (!o) + return; + + if (o->type == UCI_TYPE_LIST) { + uci_foreach_element(&o->v.list, e) + bridge_add_member(bst, e->name); + } else { + p = o->v.string; + do { + if (!*p) + break; + + if (*p == ' ') + continue; + + end = strchr(p, ' '); + if (!end) { + bridge_add_member(bst, p); + break; + } + + len = end - p; + if (len <= IFNAMSIZ) { + memcpy(buf, p, len); + buf[len] = 0; + bridge_add_member(bst, buf); + } + p = end; + } while (p++); + } +} + +static void +bridge_free(struct device *dev) +{ + struct bridge_state *bst; + struct bridge_member *bm; + + bst = container_of(dev, struct bridge_state, dev); + while (!list_empty(&bst->members)) { + bm = list_first_entry(&bst->members, struct bridge_member, list); + bridge_free_member(bm); + } + free(bst); +} + +struct device * +bridge_create(const char *name, struct uci_section *s) +{ + static const struct device_type bridge_type = { + .name = "Bridge", + .free = bridge_free, + }; + struct bridge_state *bst; + struct device *dev; + + dev = get_device(name, false); + if (dev) + return NULL; + + bst = calloc(1, sizeof(*bst)); + if (!bst) + return NULL; + + init_device(&bst->dev, &bridge_type, name); + + bst->set_state = bst->dev.set_state; + bst->dev.set_state = bridge_set_state; + + bst->dev.hotplug_ops = &bridge_ops; + + INIT_LIST_HEAD(&bst->members); + + if (s) + bridge_parse_config(bst, s); + + return &bst->dev; +} + +int +interface_attach_bridge(struct interface *iface, struct uci_section *s) +{ + struct device *dev; + char brname[IFNAMSIZ]; + + snprintf(brname, IFNAMSIZ - 1, "br-%s", iface->name); + brname[IFNAMSIZ - 1] = 0; + + dev = bridge_create(brname, s); + if (!dev) + return -1; + + add_device_user(&iface->main_dev, dev); + return 0; +} |