From: Felix Fietkau Date: Sat, 19 Feb 2011 14:21:52 +0000 (+0100) Subject: Initial import X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=5d1fff7af6f77c9bf0d46572c7af563cd9fc55b3;p=oweals%2Fnetifd.git Initial import --- 5d1fff7af6f77c9bf0d46572c7af563cd9fc55b3 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..262ee3b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.6) + +PROJECT(netifd C) +ADD_DEFINITIONS(-Os -Wall -Werror --std=gnu99 -g3) + +SET(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") + +IF(DEBUG) + ADD_DEFINITIONS(-DDEBUG -O0) +ENDIF() + +ADD_EXECUTABLE(netifd main.c interface.c config.c device.c bridge.c vlan.c ubus.c system-dummy.c) +TARGET_LINK_LIBRARIES(netifd ubox ubus uci) 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 +#include +#include +#include +#include + +#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; +} diff --git a/config.c b/config.c new file mode 100644 index 0000000..a106db2 --- /dev/null +++ b/config.c @@ -0,0 +1,51 @@ +#include +#include +#include + +#include "netifd.h" + +struct uci_context *uci_ctx; + +static void config_parse_interface(struct uci_section *s) +{ + struct interface *iface; + const char *type; + + DPRINTF("Create interface '%s'\n", s->e.name); + + iface = alloc_interface(s->e.name); + type = uci_lookup_option_string(uci_ctx, s, "type"); + + if (!type) + type = ""; + + if (!strcmp(type, "bridge")) + interface_attach_bridge(iface, s); +} + +void config_init_interfaces(const char *name) +{ + struct uci_context *ctx; + struct uci_package *p = NULL; + struct uci_element *e; + + ctx = uci_alloc_context(); + uci_ctx = ctx; + + uci_set_confdir(ctx, "./config"); + + if (uci_load(ctx, "network", &p)) { + fprintf(stderr, "Failed to load network config\n"); + return; + } + + uci_foreach_element(&p->sections, e) { + struct uci_section *s = uci_to_section(e); + + if (name && strcmp(s->e.name, name) != 0) + continue; + + if (!strcmp(s->type, "interface")) + config_parse_interface(s); + } +} diff --git a/config/network b/config/network new file mode 100644 index 0000000..35276ab --- /dev/null +++ b/config/network @@ -0,0 +1,14 @@ +# Copyright (C) 2006 OpenWrt.org + +config interface loopback + option ifname lo + option proto static + option ipaddr 127.0.0.1 + option netmask 255.0.0.0 + +config interface lan + option ifname 'eth0.1 eth0.2' + option type bridge + option proto static + option ipaddr 192.168.1.1 + option netmask 255.255.255.0 diff --git a/device.c b/device.c new file mode 100644 index 0000000..e1ffba1 --- /dev/null +++ b/device.c @@ -0,0 +1,188 @@ +#include +#include +#include +#include + +#include + +#include "netifd.h" +#include "system.h" + +static struct avl_tree devices; + +static int avl_strcmp(const void *k1, const void *k2, void *ptr) +{ + return strcmp(k1, k2); +} + +static void API_CTOR dev_init(void) +{ + avl_init(&devices, avl_strcmp, false, NULL); +} + +static void free_device(struct device *dev) +{ + cleanup_device(dev); + free(dev); +} + +static void broadcast_device_event(struct device *dev, enum device_event ev) +{ + struct device_user *dep, *tmp; + + list_for_each_entry_safe(dep, tmp, &dev->users, list) { + if (!dep->cb) + continue; + + dep->cb(dep, ev); + } +} + +static int set_device_state(struct device *dev, bool state) +{ + if (state) { + broadcast_device_event(dev, DEV_EVENT_SETUP); + system_if_up(dev); + broadcast_device_event(dev, DEV_EVENT_UP); + } else { + broadcast_device_event(dev, DEV_EVENT_TEARDOWN); + system_if_down(dev); + broadcast_device_event(dev, DEV_EVENT_DOWN); + } + return 0; +} + +int claim_device(struct device *dev) +{ + int ret; + + DPRINTF("claim device %s, new refcount: %d\n", dev->ifname, dev->active + 1); + if (++dev->active != 1) + return 0; + + ret = dev->set_state(dev, true); + if (ret != 0) + dev->active = 0; + + return ret; +} + +void release_device(struct device *dev) +{ + dev->active--; + DPRINTF("release device %s, new refcount: %d\n", dev->ifname, dev->active); + assert(dev->active >= 0); + + if (!dev->active) + dev->set_state(dev, false); +} + +int check_device_state(struct device *dev) +{ + if (!dev->type->check_state) + return 0; + + return dev->type->check_state(dev); +} + +int init_device(struct device *dev, const struct device_type *type, const char *ifname) +{ + int ret; + + assert(dev); + assert(type); + + if (ifname) + strncpy(dev->ifname, ifname, IFNAMSIZ); + + if (!dev->set_state) + dev->set_state = set_device_state; + + fprintf(stderr, "Initialize interface '%s'\n", dev->ifname); + INIT_LIST_HEAD(&dev->users); + dev->avl.key = dev->ifname; + dev->type = type; + + ret = avl_insert(&devices, &dev->avl); + if (ret < 0) + return ret; + + check_device_state(dev); + return 0; +} + +struct device *get_device(const char *name, bool create) +{ + static const struct device_type simple_type = { + .name = "Device", + .check_state = system_if_check, + .free = free_device, + }; + struct device *dev; + + + if (strchr(name, '.')) + return get_vlan_device_chain(name, create); + + dev = avl_find_element(&devices, name, dev, avl); + if (dev) + return dev; + + if (!create) + return NULL; + + dev = calloc(1, sizeof(*dev)); + init_device(dev, &simple_type, name); + + return dev; +} + +void cleanup_device(struct device *dev) +{ + struct device_user *dep, *tmp; + + fprintf(stderr, "Clean up interface '%s'\n", dev->ifname); + list_for_each_entry_safe(dep, tmp, &dev->users, list) { + if (!dep->cb) + continue; + + dep->cb(dep, DEV_EVENT_REMOVE); + } + + avl_delete(&devices, &dev->avl); +} + +void set_device_present(struct device *dev, bool state) +{ + if (dev->present == state) + return; + + DPRINTF("Device '%s' %s present\n", dev->ifname, state ? "is now" : "is no longer" ); + dev->present = state; + broadcast_device_event(dev, state ? DEV_EVENT_ADD : DEV_EVENT_REMOVE); +} + +void add_device_user(struct device_user *dep, struct device *dev) +{ + dep->dev = dev; + list_add(&dep->list, &dev->users); + if (dep->cb && dev->present) { + dep->cb(dep, DEV_EVENT_ADD); + if (dev->active) + dep->cb(dep, DEV_EVENT_UP); + } +} + +void remove_device_user(struct device_user *dep) +{ + struct device *dev = dep->dev; + + list_del(&dep->list); + + if (list_empty(&dev->users)) { + /* all references have gone away, remove this interface */ + dev->type->free(dev); + } + + dep->dev = NULL; +} diff --git a/device.h b/device.h new file mode 100644 index 0000000..9e7d309 --- /dev/null +++ b/device.h @@ -0,0 +1,95 @@ +#ifndef __LL_H +#define __LL_H + +#include + +struct device; +struct device_hotplug_ops; + +typedef int (*device_state_cb)(struct device *, bool up); + +struct device_type { + const char *name; + + int (*check_state)(struct device *); + void (*free)(struct device *); +}; + +/* + * link layer device. typically represents a linux network device. + * can be used to support VLANs as well + */ +struct device { + const struct device_type *type; + + struct avl_node avl; + struct list_head users; + + char ifname[IFNAMSIZ + 1]; + int ifindex; + + bool present; + int active; + + /* set interface up or down */ + device_state_cb set_state; + + const struct device_hotplug_ops *hotplug_ops; +}; + +/* events broadcasted to all users of a device */ +enum device_event { + /* device has been added to the system and can be brought up */ + DEV_EVENT_ADD, + + /* device has been removed */ + DEV_EVENT_REMOVE, + + /* device is being brought up */ + DEV_EVENT_SETUP, + + /* device is being brought down */ + DEV_EVENT_TEARDOWN, + + /* device has been brought up */ + DEV_EVENT_UP, + + /* device has been brought down */ + DEV_EVENT_DOWN, + + /* device has changed its link state to up */ + DEV_EVENT_LINK_UP, + + /* device has changed its link state to down */ + DEV_EVENT_LINK_DOWN, +}; + +/* + * device dependency with callbacks + */ +struct device_user { + struct list_head list; + + struct device *dev; + void (*cb)(struct device_user *, enum device_event); +}; + +struct device_hotplug_ops { + int (*add)(struct device *main, struct device *member); + int (*del)(struct device *main, struct device *member); +}; + +int init_device(struct device *iface, const struct device_type *type, const char *ifname); +void cleanup_device(struct device *iface); +struct device *get_device(const char *name, bool create); +void add_device_user(struct device_user *dep, struct device *iface); +void remove_device_user(struct device_user *dep); + +void set_device_present(struct device *dev, bool state); +int claim_device(struct device *dev); +void release_device(struct device *dev); +int check_device_state(struct device *dev); + +struct device *get_vlan_device_chain(const char *ifname, bool create); + +#endif diff --git a/interface.c b/interface.c new file mode 100644 index 0000000..d45f2d5 --- /dev/null +++ b/interface.c @@ -0,0 +1,169 @@ +#include +#include +#include + +#include "netifd.h" +#include "ubus.h" + +LIST_HEAD(interfaces); + +static int interface_event(struct interface *iface, enum interface_event ev) +{ + if (!iface->state || !iface->state->event) + return 0; + + return iface->state->event(iface, iface->state, ev); +} + +static void +__set_interface_up(struct interface *iface) +{ + if (iface->up) + return; + + if (claim_device(iface->main_dev.dev) < 0) + return; + + if (interface_event(iface, IFEV_UP) < 0) { + release_device(iface->main_dev.dev); + return; + } + + iface->up = true; +} + +static void +__set_interface_down(struct interface *iface) +{ + if (!iface->up) + return; + + iface->up = false; + interface_event(iface, IFEV_DOWN); + release_device(iface->main_dev.dev); +} + +static void +interface_cb(struct device_user *dep, enum device_event ev) +{ + struct interface *iface; + bool new_state; + + iface = container_of(dep, struct interface, main_dev); + switch (ev) { + case DEV_EVENT_ADD: + new_state = true; + break; + case DEV_EVENT_REMOVE: + new_state = false; + break; + default: + return; + } + + if (iface->active == new_state) + return; + + iface->active = new_state; + + if (new_state) { + if (iface->autostart) + __set_interface_up(iface); + } else + __set_interface_down(iface); +} + +struct interface * +alloc_interface(const char *name) +{ + struct interface *iface; + + iface = get_interface(name); + if (iface) + return iface; + + iface = calloc(1, sizeof(*iface)); + iface->main_dev.cb = interface_cb; + iface->l3_iface = &iface->main_dev; + strncpy(iface->name, name, sizeof(iface->name) - 1); + list_add(&iface->list, &interfaces); + netifd_ubus_add_interface(iface); + + return iface; +} + +void +free_interface(struct interface *iface) +{ + netifd_ubus_remove_interface(iface); + list_del(&iface->list); + if (iface->state && iface->state->free) + iface->state->free(iface, iface->state); + free(iface); +} + +struct interface * +get_interface(const char *name) +{ + struct interface *iface; + + list_for_each_entry(iface, &interfaces, list) { + if (!strcmp(iface->name, name)) + return iface; + } + return NULL; +} + +void +interface_remove_link(struct interface *iface, struct device *llif) +{ + struct device *dev = iface->main_dev.dev; + + if (dev && dev->hotplug_ops) { + dev->hotplug_ops->del(dev, llif); + return; + } + + remove_device_user(&iface->main_dev); +} + +int +interface_add_link(struct interface *iface, struct device *llif) +{ + struct device *dev = iface->main_dev.dev; + + if (dev && dev->hotplug_ops) + return dev->hotplug_ops->add(dev, llif); + + if (iface->main_dev.dev) + interface_remove_link(iface, NULL); + + add_device_user(&iface->main_dev, llif); + + return 0; +} + +int +set_interface_up(struct interface *iface) +{ + iface->autostart = true; + + if (iface->up || !iface->active) + return -1; + + __set_interface_up(iface); + return 0; +} + +int +set_interface_down(struct interface *iface) +{ + iface->autostart = false; + + if (!iface->up) + return -1; + + __set_interface_down(iface); + + return 0; +} diff --git a/interface.h b/interface.h new file mode 100644 index 0000000..0fd70d7 --- /dev/null +++ b/interface.h @@ -0,0 +1,63 @@ +#ifndef __NETIFD_INTERFACE_H +#define __NETIFD_INTERFACE_H + +struct interface; +struct interface_proto; +struct interface_proto_state; + +extern struct list_head interfaces; + +enum interface_event { + IFEV_UP, + IFEV_DOWN, +}; + +struct interface_proto_state { + const struct interface_proto *proto; + + int (*event)(struct interface *, struct interface_proto_state *, enum interface_event ev); + void (*free)(struct interface *, struct interface_proto_state *); +}; + +/* + * interface configuration + */ +struct interface { + struct list_head list; + + char name[IFNAMSIZ - 2]; + + /* interface is up and running */ + bool up; + + /* interface can be brought up */ + bool active; + + /* interface will be brought up when available */ + bool autostart; + + /* main interface that the interface is bound to */ + struct device_user main_dev; + + /* interface that layer 3 communication will go through */ + struct device_user *l3_iface; + + /* primary protocol state */ + struct interface_proto_state *state; + + struct ubus_object ubus; +}; + +struct interface *get_interface(const char *name); +struct interface *alloc_interface(const char *name); +void free_interface(struct interface *iface); + +int set_interface_up(struct interface *iface); +int set_interface_down(struct interface *iface); + +int interface_add_link(struct interface *iface, struct device *llif); +void interface_remove_link(struct interface *iface, struct device *llif); + +int interface_attach_bridge(struct interface *iface, struct uci_section *s); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..69aa5cd --- /dev/null +++ b/main.c @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#include "netifd.h" +#include "ubus.h" + +static int usage(const char *progname) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options:\n" + " -s : Path to the ubus socket\n" + "\n", progname); + + return 1; +} + +int main(int argc, char **argv) +{ + const char *socket = NULL; + int ch; + + while ((ch = getopt(argc, argv, "s:")) != -1) { + switch(ch) { + case 's': + socket = optarg; + break; + default: + return usage(argv[0]); + } + } + + if (netifd_ubus_init(NULL) < 0) { + fprintf(stderr, "Failed to connect to ubus\n"); + return 1; + } + + config_init_interfaces(NULL); + + uloop_run(); + + netifd_ubus_done(); + + return 0; +} diff --git a/netifd.h b/netifd.h new file mode 100644 index 0000000..fabf4d9 --- /dev/null +++ b/netifd.h @@ -0,0 +1,29 @@ +#ifndef __NETIFD_H +#define __NETIFD_H + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +#include "device.h" +#include "interface.h" + +#ifdef DEBUG +#define DPRINTF(format, ...) fprintf(stderr, "%s(%d): " format, __func__, __LINE__, ## __VA_ARGS__) +#else +#define DPRINTF(...) do {} while(0) +#endif + +extern struct uci_context *uci_ctx; + +void config_init_interfaces(const char *name); + +#endif diff --git a/system-dummy.c b/system-dummy.c new file mode 100644 index 0000000..e143d8b --- /dev/null +++ b/system-dummy.c @@ -0,0 +1,62 @@ +#include +#include + +#include "netifd.h" + +int system_bridge_addbr(struct device *bridge) +{ + DPRINTF("brctl addbr %s\n", bridge->ifname); + return 0; +} + +int system_bridge_delbr(struct device *bridge) +{ + DPRINTF("brctl delbr %s\n", bridge->ifname); + return 0; +} + +int system_bridge_addif(struct device *bridge, struct device *dev) +{ + DPRINTF("brctl addif %s %s\n", bridge->ifname, dev->ifname); + return 0; +} + +int system_bridge_delif(struct device *bridge, struct device *dev) +{ + DPRINTF("brctl delif %s %s\n", bridge->ifname, dev->ifname); + return 0; +} + +int system_vlan_add(struct device *dev, int id) +{ + DPRINTF("vconfig add %s %d\n", dev->ifname, id); + return 0; +} + +int system_vlan_del(struct device *dev) +{ + DPRINTF("vconfig rem %s\n", dev->ifname); + return 0; +} + +int system_if_up(struct device *dev) +{ + DPRINTF("ifconfig %s up\n", dev->ifname); + return 0; +} + +int system_if_down(struct device *dev) +{ + DPRINTF("ifconfig %s down\n", dev->ifname); + return 0; +} + +int system_if_check(struct device *dev) +{ + dev->ifindex = 0; + + if (!strcmp(dev->ifname, "eth0")) + set_device_present(dev, true); + + return 0; +} diff --git a/system.h b/system.h new file mode 100644 index 0000000..f8428cc --- /dev/null +++ b/system.h @@ -0,0 +1,18 @@ +#ifndef __NETIFD_SYSTEM_H +#define __NETIFD_SYSTEM_H + +#include "device.h" + +int system_bridge_addbr(struct device *bridge); +int system_bridge_delbr(struct device *bridge); +int system_bridge_addif(struct device *bridge, struct device *dev); +int system_bridge_delif(struct device *bridge, struct device *dev); + +int system_vlan_add(struct device *dev, int id); +int system_vlan_del(struct device *dev); + +int system_if_up(struct device *dev); +int system_if_down(struct device *dev); +int system_if_check(struct device *dev); + +#endif diff --git a/ubus.c b/ubus.c new file mode 100644 index 0000000..30db080 --- /dev/null +++ b/ubus.c @@ -0,0 +1,167 @@ +#include + +#include "netifd.h" +#include "ubus.h" + +static struct ubus_context *ctx = NULL; + +/* global object */ + +static const struct ubus_signature main_object_sig[] = { + UBUS_METHOD_START("add_device"), + UBUS_FIELD(STRING, "name"), + UBUS_METHOD_END(), + + UBUS_METHOD_START("del_device"), + UBUS_FIELD(STRING, "name"), + UBUS_METHOD_END(), +}; + +static struct ubus_object_type main_object_type = + UBUS_OBJECT_TYPE("netifd", main_object_sig); + +enum { + DEV_NAME, + DEV_FORCE, + DEV_LAST, +}; + +static const struct blobmsg_policy dev_policy[] = { + [DEV_NAME] = { .name = "name", .type = BLOBMSG_TYPE_STRING }, + [DEV_FORCE] = { .name = "force", .type = BLOBMSG_TYPE_INT8 }, +}; + +static int netifd_handle_device(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct device *dev; + struct blob_attr *tb[DEV_LAST]; + bool add = !strncmp(method, "add", 3); + + blobmsg_parse(dev_policy, ARRAY_SIZE(dev_policy), tb, blob_data(msg), blob_len(msg)); + + if (!tb[DEV_NAME]) + return UBUS_STATUS_INVALID_ARGUMENT; + + dev = get_device(blobmsg_data(tb[DEV_NAME]), false); + if (!dev) + return UBUS_STATUS_NOT_FOUND; + + if (!add || (tb[DEV_FORCE] && blobmsg_get_u8(tb[DEV_FORCE]))) + set_device_present(dev, add); + else + check_device_state(dev); + + return 0; +} + +static struct ubus_method main_object_methods[] = { + { .name = "add_device", .handler = netifd_handle_device }, + { .name = "del_device", .handler = netifd_handle_device }, +}; + +static struct ubus_object main_object = { + .name = "network.interface", + .type = &main_object_type, + .methods = main_object_methods, + .n_methods = ARRAY_SIZE(main_object_methods), +}; + +int netifd_ubus_init(const char *path) +{ + int ret; + + ctx = ubus_connect(path); + if (!ctx) + return -EIO; + + DPRINTF("connected as %08x\n", ctx->local_id); + uloop_init(); + ubus_add_uloop(ctx); + + ret = ubus_add_object(ctx, &main_object); + if (ret != 0) + fprintf(stderr, "Failed to publish object: %s\n", ubus_strerror(ret)); + + return 0; +} + +void netifd_ubus_done(void) +{ + ubus_free(ctx); +} + + +/* per-interface object */ +static const struct ubus_signature iface_object_sig[] = { + UBUS_METHOD_START("up"), + UBUS_METHOD_END(), + + UBUS_METHOD_START("down"), + UBUS_METHOD_END(), +}; + +static struct ubus_object_type iface_object_type = + UBUS_OBJECT_TYPE("netifd", iface_object_sig); + + +static int netifd_handle_up(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + + iface = container_of(obj, struct interface, ubus); + set_interface_up(iface); + + return 0; +} + +static int netifd_handle_down(struct ubus_context *ctx, struct ubus_object *obj, + struct ubus_request_data *req, const char *method, + struct blob_attr *msg) +{ + struct interface *iface; + + iface = container_of(obj, struct interface, ubus); + set_interface_down(iface); + + return 0; +} + +static struct ubus_method iface_object_methods[] = { + { .name = "up", .handler = netifd_handle_up }, + { .name = "down", .handler = netifd_handle_down }, +}; + + +void netifd_ubus_add_interface(struct interface *iface) +{ + struct ubus_object *obj = &iface->ubus; + char *name; + + name = malloc(strlen(main_object.name) + strlen(iface->name) + 2); + if (!name) + return; + + sprintf(name, "%s.%s", main_object.name, iface->name); + obj->name = name; + obj->type = &iface_object_type; + obj->methods = iface_object_methods; + obj->n_methods = ARRAY_SIZE(iface_object_methods); + if (ubus_add_object(ctx, &iface->ubus)) { + DPRINTF("failed to publish ubus object for interface '%s'\n", iface->name); + free(name); + obj->name = NULL; + } +} + +void netifd_ubus_remove_interface(struct interface *iface) +{ + if (!iface->ubus.name) + return; + + ubus_remove_object(ctx, &iface->ubus); + free((void *) iface->ubus.name); +} diff --git a/ubus.h b/ubus.h new file mode 100644 index 0000000..668a34b --- /dev/null +++ b/ubus.h @@ -0,0 +1,9 @@ +#ifndef __NETIFD_UBUS_H +#define __NETIFD_UBUS_H + +int netifd_ubus_init(const char *path); +void netifd_ubus_done(void); +void netifd_ubus_add_interface(struct interface *iface); +void netifd_ubus_remove_interface(struct interface *iface); + +#endif diff --git a/vlan.c b/vlan.c new file mode 100644 index 0000000..3de1121 --- /dev/null +++ b/vlan.c @@ -0,0 +1,156 @@ +#include +#include +#include + +#include "netifd.h" +#include "system.h" + +struct vlan_device { + struct device dev; + struct device_user dep; + + device_state_cb set_state; + int id; +}; + +static void free_vlan_if(struct device *iface) +{ + struct vlan_device *vldev; + + vldev = container_of(iface, struct vlan_device, dev); + remove_device_user(&vldev->dep); + cleanup_device(&vldev->dev); + free(vldev); +} + +static int vlan_set_device_state(struct device *dev, bool up) +{ + struct vlan_device *vldev; + int ret = 0; + + vldev = container_of(dev, struct vlan_device, dev); + if (!up) { + vldev->set_state(dev, false); + system_vlan_del(dev); + release_device(vldev->dep.dev); + return 0; + } + + ret = claim_device(vldev->dep.dev); + if (ret) + return ret; + + system_vlan_add(vldev->dep.dev, vldev->id); + ret = vldev->set_state(dev, true); + if (ret) + release_device(vldev->dep.dev); + + return ret; +} + +static void vlan_dev_cb(struct device_user *dep, enum device_event ev) +{ + struct vlan_device *vldev; + + vldev = container_of(dep, struct vlan_device, dep); + switch(ev) { + case DEV_EVENT_ADD: + set_device_present(&vldev->dev, true); + break; + case DEV_EVENT_REMOVE: + set_device_present(&vldev->dev, false); + break; + default: + break; + } +} + +static struct device *get_vlan_device(struct device *dev, int id, bool create) +{ + static const struct device_type vlan_type = { + .name = "VLAN", + .free = free_vlan_if, + }; + struct vlan_device *vldev; + struct device_user *dep; + + /* look for an existing interface before creating a new one */ + list_for_each_entry(dep, &dev->users, list) { + if (dep->cb != vlan_dev_cb) + continue; + + vldev = container_of(dep, struct vlan_device, dep); + if (vldev->id != id) + continue; + + return &vldev->dev; + } + + if (!create) + return NULL; + + vldev = calloc(1, sizeof(*vldev)); + snprintf(vldev->dev.ifname, IFNAMSIZ, "%s.%d", dev->ifname, id); + + init_device(&vldev->dev, &vlan_type, NULL); + + vldev->set_state = vldev->dev.set_state; + vldev->dev.set_state = vlan_set_device_state; + + vldev->id = id; + + vldev->dep.cb = vlan_dev_cb; + add_device_user(&vldev->dep, dev); + + return &vldev->dev; +} + +static inline char *split_vlan(char *s) +{ + s = strchr(s, '.'); + if (!s) + goto out; + + *s = 0; + s++; + +out: + return s; +} + +struct device *get_vlan_device_chain(const char *ifname, bool create) +{ + struct device *iface = NULL; + char *buf, *s, *next, *err = NULL; + int id; + + buf = strdup(ifname); + if (!buf) + return NULL; + + s = split_vlan(buf); + iface = get_device(buf, create); + if (!iface && !create) + goto error; + + do { + next = split_vlan(s); + id = strtoul(s, &err, 10); + if (err && *err) + goto error; + + iface = get_vlan_device(iface, id, create); + if (!iface) + goto error; + + s = next; + if (!s) + goto out; + } while (1); + +error: + iface = NULL; +out: + free(buf); + return iface; +}