diff options
-rw-r--r-- | CMakeLists.txt | 2 | ||||
-rw-r--r-- | config.c | 4 | ||||
-rw-r--r-- | device.h | 1 | ||||
-rw-r--r-- | system-dummy.c | 10 | ||||
-rw-r--r-- | system-linux.c | 77 | ||||
-rw-r--r-- | system.h | 13 | ||||
-rw-r--r-- | vlandev.c | 255 |
7 files changed, 361 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 65da3cf..e648b03 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ SET(SOURCES interface.c interface-ip.c interface-event.c iprule.c proto.c proto-static.c proto-shell.c config.c device.c bridge.c vlan.c alias.c - macvlan.c ubus.c wireless.c) + macvlan.c ubus.c vlandev.c wireless.c) find_library(json NAMES json-c json) @@ -173,6 +173,10 @@ config_init_devices(void) devtype = &tunnel_device_type; else if (!strcmp(type, "macvlan")) devtype = &macvlan_device_type; + else if (!strcmp(type, "8021ad")) + devtype = &vlandev_device_type; + else if (!strcmp(type, "8021q")) + devtype = &vlandev_device_type; } if (!devtype) @@ -161,6 +161,7 @@ extern const struct device_type simple_device_type; extern const struct device_type bridge_device_type; extern const struct device_type tunnel_device_type; extern const struct device_type macvlan_device_type; +extern const struct device_type vlandev_device_type; void device_lock(void); void device_unlock(void); diff --git a/system-dummy.c b/system-dummy.c index deb53ff..bb94781 100644 --- a/system-dummy.c +++ b/system-dummy.c @@ -254,3 +254,13 @@ int system_macvlan_del(struct device *macvlan) { return 0; } + +int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlandev_config *cfg) +{ + return 0; +} + +int system_vlandev_del(struct device *vlandev) +{ + return 0; +} diff --git a/system-linux.c b/system-linux.c index 7447422..f4721cc 100644 --- a/system-linux.c +++ b/system-linux.c @@ -3,6 +3,7 @@ * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org> * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org> * Copyright (C) 2013 Steven Barth <steven@midlink.org> + * Copyright (C) 2014 Gioacchino Mazzurco <gio@eigenlab.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 @@ -36,6 +37,7 @@ #include <linux/ip6_tunnel.h> #include <linux/ethtool.h> #include <linux/fib_rules.h> +#include <linux/version.h> #ifndef RTN_FAILED_POLICY #define RTN_FAILED_POLICY 12 @@ -802,6 +804,81 @@ int system_vlan_del(struct device *dev) return system_vlan(dev, -1); } +int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlandev_config *cfg) +{ + struct nl_msg *msg; + struct nlattr *linkinfo, *data; + struct ifinfomsg iim = { .ifi_family = AF_INET }; + int ifindex = system_if_resolve(dev); + int rv; + + if (ifindex == 0) + return -ENOENT; + + msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL); + + if (!msg) + return -1; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + nla_put(msg, IFLA_IFNAME, IFNAMSIZ, vlandev->ifname); + nla_put_u32(msg, IFLA_LINK, ifindex); + + if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) + goto nla_put_failure; + + nla_put(msg, IFLA_INFO_KIND, strlen("vlan"), "vlan"); + + + if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) + goto nla_put_failure; + + nla_put_u16(msg, IFLA_VLAN_ID, cfg->vid); + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,0) + nla_put_u16(msg, IFLA_VLAN_PROTOCOL, htons(cfg->proto)); +#else + if(cfg->proto == VLAN_PROTO_8021AD) + netifd_log_message(L_WARNING, "%s Your kernel is older than linux 3.10.0, 802.1ad is not supported defaulting to 802.1q", vlandev->type->name); +#endif + + nla_nest_end(msg, data); + nla_nest_end(msg, linkinfo); + + rv = system_rtnl_call(msg); + if (rv) + D(SYSTEM, "Error adding vlandev '%s' over '%s': %d\n", vlandev->ifname, dev->ifname, rv); + + return rv; + +nla_put_failure: + nlmsg_free(msg); + return -ENOMEM; +} + +int system_vlandev_del(struct device *vlandev) +{ + struct nl_msg *msg; + struct ifinfomsg iim; + + iim.ifi_family = AF_INET; + iim.ifi_index = 0; + + msg = nlmsg_alloc_simple(RTM_DELLINK, 0); + + if (!msg) + return -1; + + nlmsg_append(msg, &iim, sizeof(iim), 0); + + nla_put(msg, IFLA_INFO_KIND, strlen("vlan"), "vlan"); + nla_put(msg, IFLA_IFNAME, sizeof(vlandev->ifname), vlandev->ifname); + + system_rtnl_call(msg); + + return 0; +} + static void system_if_get_settings(struct device *dev, struct device_settings *s) { @@ -68,6 +68,16 @@ struct macvlan_config { unsigned char macaddr[6]; }; +enum vlan_proto { + VLAN_PROTO_8021Q = 0x8100, + VLAN_PROTO_8021AD = 0x88A8 +}; + +struct vlandev_config { + enum vlan_proto proto; + uint16_t vid; +}; + static inline int system_get_addr_family(unsigned int flags) { if ((flags & DEVADDR_FAMILY) == DEVADDR_INET6) @@ -97,6 +107,9 @@ int system_macvlan_del(struct device *macvlan); int system_vlan_add(struct device *dev, int id); int system_vlan_del(struct device *dev); +int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlandev_config *cfg); +int system_vlandev_del(struct device *vlandev); + void system_if_clear_state(struct device *dev); int system_if_up(struct device *dev); int system_if_down(struct device *dev); diff --git a/vlandev.c b/vlandev.c new file mode 100644 index 0000000..36a5c63 --- /dev/null +++ b/vlandev.c @@ -0,0 +1,255 @@ +/* + * netifd - network interface daemon + * Copyright (C) 2014 Gioacchino Mazzurco <gio@eigenlab.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <string.h> + +#include "netifd.h" +#include "device.h" +#include "interface.h" +#include "system.h" + +enum { + VLANDEV_ATTR_TYPE, + VLANDEV_ATTR_IFNAME, + VLANDEV_ATTR_VID, + __VLANDEV_ATTR_MAX +}; + +static const struct blobmsg_policy vlandev_attrs[__VLANDEV_ATTR_MAX] = { + [VLANDEV_ATTR_TYPE] = { "type", BLOBMSG_TYPE_STRING }, + [VLANDEV_ATTR_IFNAME] = { "ifname", BLOBMSG_TYPE_STRING }, + [VLANDEV_ATTR_VID] = { "vid", BLOBMSG_TYPE_INT32 }, +}; + +static const struct uci_blob_param_list vlandev_attr_list = { + .n_params = __VLANDEV_ATTR_MAX, + .params = vlandev_attrs, + + .n_next = 1, + .next = { &device_attr_list }, +}; + +struct vlandev_device { + struct device dev; + struct device_user parent; + + device_state_cb set_state; + + struct blob_attr *config_data; + struct blob_attr *ifname; + struct vlandev_config config; +}; + +static void +vlandev_base_cb(struct device_user *dev, enum device_event ev) +{ + struct vlandev_device *mvdev = container_of(dev, struct vlandev_device, parent); + + switch (ev) { + case DEV_EVENT_ADD: + device_set_present(&mvdev->dev, true); + break; + case DEV_EVENT_REMOVE: + device_set_present(&mvdev->dev, false); + break; + case DEV_EVENT_LINK_UP: + device_set_link(&mvdev->dev, true); + break; + case DEV_EVENT_LINK_DOWN: + device_set_link(&mvdev->dev, false); + break; + default: + return; + } +} + +static int +vlandev_set_down(struct vlandev_device *mvdev) +{ + mvdev->set_state(&mvdev->dev, false); + system_vlandev_del(&mvdev->dev); + device_release(&mvdev->parent); + + return 0; +} + +static int +vlandev_set_up(struct vlandev_device *mvdev) +{ + int ret; + + ret = device_claim(&mvdev->parent); + if (ret < 0) + return ret; + + ret = system_vlandev_add(&mvdev->dev, mvdev->parent.dev, &mvdev->config); + if (ret < 0) + goto release; + + ret = mvdev->set_state(&mvdev->dev, true); + if (ret) + goto delete; + + return 0; + +delete: + system_vlandev_del(&mvdev->dev); +release: + device_release(&mvdev->parent); + return ret; +} + +static int +vlandev_set_state(struct device *dev, bool up) +{ + struct vlandev_device *mvdev; + + D(SYSTEM, "vlandev_set_state(%s, %u)\n", dev->ifname, up); + + mvdev = container_of(dev, struct vlandev_device, dev); + if (up) + return vlandev_set_up(mvdev); + else + return vlandev_set_down(mvdev); +} + +static void +vlandev_free(struct device *dev) +{ + struct vlandev_device *mvdev; + + mvdev = container_of(dev, struct vlandev_device, dev); + device_remove_user(&mvdev->parent); + free(mvdev); +} + +static void +vlandev_dump_info(struct device *dev, struct blob_buf *b) +{ + struct vlandev_device *mvdev; + + mvdev = container_of(dev, struct vlandev_device, dev); + blobmsg_add_string(b, "parent", mvdev->parent.dev->ifname); + system_if_dump_info(dev, b); +} + +static void +vlandev_config_init(struct device *dev) +{ + struct vlandev_device *mvdev; + struct device *basedev = NULL; + + mvdev = container_of(dev, struct vlandev_device, dev); + if (mvdev->ifname) + basedev = device_get(blobmsg_data(mvdev->ifname), true); + + device_add_user(&mvdev->parent, basedev); +} + +static void +vlandev_apply_settings(struct vlandev_device *mvdev, struct blob_attr **tb) +{ + struct vlandev_config *cfg = &mvdev->config; + struct blob_attr *cur; + + cfg->proto = VLAN_PROTO_8021Q; + cfg->vid = 1; + + if ((cur = tb[VLANDEV_ATTR_TYPE])) + { + if(!strcmp(blobmsg_data(cur), "8021ad")) + cfg->proto = VLAN_PROTO_8021AD; + } + + if ((cur = tb[VLANDEV_ATTR_VID])) + cfg->vid = (uint16_t) blobmsg_get_u32(cur); +} + +static enum dev_change_type +vlandev_reload(struct device *dev, struct blob_attr *attr) +{ + struct blob_attr *tb_dev[__DEV_ATTR_MAX]; + struct blob_attr *tb_mv[__VLANDEV_ATTR_MAX]; + enum dev_change_type ret = DEV_CONFIG_APPLIED; + struct vlandev_device *mvdev; + + mvdev = container_of(dev, struct vlandev_device, dev); + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev, + blob_data(attr), blob_len(attr)); + blobmsg_parse(vlandev_attrs, __VLANDEV_ATTR_MAX, tb_mv, + blob_data(attr), blob_len(attr)); + + device_init_settings(dev, tb_dev); + vlandev_apply_settings(mvdev, tb_mv); + mvdev->ifname = tb_mv[VLANDEV_ATTR_IFNAME]; + + if (mvdev->config_data) { + struct blob_attr *otb_dev[__DEV_ATTR_MAX]; + struct blob_attr *otb_mv[__VLANDEV_ATTR_MAX]; + + blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_dev, otb_dev, &device_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + blobmsg_parse(vlandev_attrs, __VLANDEV_ATTR_MAX, otb_mv, + blob_data(mvdev->config_data), blob_len(mvdev->config_data)); + + if (uci_blob_diff(tb_mv, otb_mv, &vlandev_attr_list, NULL)) + ret = DEV_CONFIG_RESTART; + + vlandev_config_init(dev); + } + + mvdev->config_data = attr; + return ret; +} + +static struct device * +vlandev_create(const char *name, struct blob_attr *attr) +{ + struct vlandev_device *mvdev; + struct device *dev = NULL; + + mvdev = calloc(1, sizeof(*mvdev)); + if (!mvdev) + return NULL; + + dev = &mvdev->dev; + device_init(dev, &vlandev_device_type, name); + dev->config_pending = true; + + mvdev->set_state = dev->set_state; + dev->set_state = vlandev_set_state; + + dev->hotplug_ops = NULL; + mvdev->parent.cb = vlandev_base_cb; + + vlandev_reload(dev, attr); + + return dev; +} + +const struct device_type vlandev_device_type = { + .name = "VLANDEV", + .config_params = &vlandev_attr_list, + + .create = vlandev_create, + .config_init = vlandev_config_init, + .reload = vlandev_reload, + .free = vlandev_free, + .dump_info = vlandev_dump_info, +}; |