Add vlan 802.1q/802.1ad support as netifd devices
authorGioacchino Mazzurco <gio@eigenlab.org>
Tue, 10 Jun 2014 17:29:13 +0000 (19:29 +0200)
committerFelix Fietkau <nbd@openwrt.org>
Wed, 11 Jun 2014 10:22:23 +0000 (12:22 +0200)
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 <gmazzurco89@gmail.com>
CMakeLists.txt
config.c
device.h
system-dummy.c
system-linux.c
system.h
vlandev.c [new file with mode: 0644]

index 65da3cf90e23e64aeea2c1da3748920ac103ccb4..e648b032c7dc118d50ea244b18bece3a7c8be2ed 100644 (file)
@@ -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)
index 1d04efd7d1caf86d67e192c84d6bdd2c24089cc4..515e6467df57e1068eff7914e057f60904667e5a 100644 (file)
--- 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)
index 6fceaa1ca07a4780a2e9ce1fc3d4c7f2e33ba965..58dcb18525d90d42dcb5d29dfd4415bd6d365bb6 100644 (file)
--- 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);
index deb53ff6642c354146b3e707274b3fd262acae55..bb94781f323cb3c645018496b458951a46f42638 100644 (file)
@@ -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;
+}
index 744742279efc719a2f1e666947b08153de0f89da..f4721cc8902cb6e8043c05cb350a659027e2eb4a 100644 (file)
@@ -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)
 {
index a8f12ac9c4ff692ee80c1ff08329ae889ac8586b..76eee23fb522806952233507c8a70446cf031319 100644 (file)
--- 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 (file)
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,
+};