From 59217785704fca27d2c7a19e279d27c384a452cd Mon Sep 17 00:00:00 2001 From: Gioacchino Mazzurco Date: Tue, 10 Jun 2014 19:29:13 +0200 Subject: [PATCH] Add vlan 802.1q/802.1ad support as netifd devices At moment netifd supports just 802.1q vlan, you can configure them using a concise but "hacky" syntax using an interface config section, with this patch netifd acquire the capability of configuring 802.1ad and 802.1q vlan using config device sections, so you can define a vlan device plus interface with something like this: config device 'test' option type '8021ad' option name 'test' option ifname 'eth0' option vid '1000' config interface 'testif' option ifname 'test' option proto 'none' option auto '1' old syntax for 802.1q keeps working so no retrocompatibility problems, to keep retrocompatibility means also that user must not use name/ifname like eth0.2 for devices declared with the new style because this would trigger the "old style" when interface config section is parsed Signed-off-by: Gioacchino Mazzurco --- CMakeLists.txt | 2 +- config.c | 4 + device.h | 1 + system-dummy.c | 10 ++ system-linux.c | 77 +++++++++++++++ system.h | 13 +++ vlandev.c | 255 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 vlandev.c 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) diff --git a/config.c b/config.c index 1d04efd..515e646 100644 --- a/config.c +++ b/config.c @@ -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) diff --git a/device.h b/device.h index 6fceaa1..58dcb18 100644 --- a/device.h +++ b/device.h @@ -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 * Copyright (C) 2013 Jo-Philipp Wich * Copyright (C) 2013 Steven Barth + * Copyright (C) 2014 Gioacchino Mazzurco * * 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 #include #include +#include #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) { diff --git a/system.h b/system.h index a8f12ac..76eee23 100644 --- a/system.h +++ b/system.h @@ -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 + * + * 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 + +#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, +}; -- 2.30.2