device: add veth support
authorMatthias Schiffer <mschiffer@universe-factory.net>
Fri, 10 Feb 2017 05:30:17 +0000 (06:30 +0100)
committerFelix Fietkau <nbd@nbd.name>
Sat, 11 Feb 2017 20:48:38 +0000 (21:48 +0100)
The veth config code mostly handles the primary interface of a veth pair,
the secondary interface is not explicitly referenced and will be found as
an unrelated interface after the pair has been created.

This doesn't only allow us to keep the veth code simple (and similar to
existing device handlers), but will also avoid complicating handling
unnecessarily in case the secondary interface is moved into another network
namespace.

Signed-off-by: Matthias Schiffer <mschiffer@universe-factory.net>
CMakeLists.txt
system-dummy.c
system-linux.c
system.h
veth.c [new file with mode: 0644]

index 1f35d2602669e237cdc34b1224d3ef1ce9b0d122..d54b6fa9fd5c16db4c04068ff98c55ed22417f21 100644 (file)
@@ -9,7 +9,7 @@ SET(SOURCES
        main.c utils.c system.c tunnel.c handler.c
        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
+       config.c device.c bridge.c veth.c vlan.c alias.c
        macvlan.c ubus.c vlandev.c wireless.c)
 
 
index 2ea25ac5d6fd4ec491781880168d2432fb0e1a7c..165438cdd51d91639019e8b90767816a1d7b2153 100644 (file)
@@ -281,6 +281,16 @@ int system_macvlan_del(struct device *macvlan)
        return 0;
 }
 
+int system_veth_add(struct device *veth, struct veth_config *cfg)
+{
+       return 0;
+}
+
+int system_veth_del(struct device *veth)
+{
+       return 0;
+}
+
 int system_vlandev_add(struct device *vlandev, struct device *dev, struct vlandev_config *cfg)
 {
        return 0;
index 8a1173cf539dfc05ad6f29e54b4660cd41972a2e..e0cb5bcbe51e44e3c3b90846bf36235fed84d78c 100644 (file)
@@ -38,6 +38,7 @@
 #include <linux/ip6_tunnel.h>
 #include <linux/ethtool.h>
 #include <linux/fib_rules.h>
+#include <linux/veth.h>
 #include <linux/version.h>
 
 #ifndef RTN_FAILED_POLICY
@@ -1131,6 +1132,66 @@ int system_macvlan_del(struct device *macvlan)
        return system_link_del(macvlan->ifname);
 }
 
+int system_veth_add(struct device *veth, struct veth_config *cfg)
+{
+       struct nl_msg *msg;
+       struct ifinfomsg empty_iim = {};
+       struct nlattr *linkinfo, *data, *veth_info;
+       int rv;
+
+       msg = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL);
+
+       if (!msg)
+               return -1;
+
+       nlmsg_append(msg, &empty_iim, sizeof(empty_iim), 0);
+
+       if (cfg->flags & VETH_OPT_MACADDR)
+               nla_put(msg, IFLA_ADDRESS, sizeof(cfg->macaddr), cfg->macaddr);
+       nla_put_string(msg, IFLA_IFNAME, veth->ifname);
+
+       if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO)))
+               goto nla_put_failure;
+
+       nla_put_string(msg, IFLA_INFO_KIND, "veth");
+
+       if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
+               goto nla_put_failure;
+
+       if (!(veth_info = nla_nest_start(msg, VETH_INFO_PEER)))
+               goto nla_put_failure;
+
+       nlmsg_append(msg, &empty_iim, sizeof(empty_iim), 0);
+
+       if (cfg->flags & VETH_OPT_PEER_NAME)
+               nla_put_string(msg, IFLA_IFNAME, cfg->peer_name);
+       if (cfg->flags & VETH_OPT_PEER_MACADDR)
+               nla_put(msg, IFLA_ADDRESS, sizeof(cfg->peer_macaddr), cfg->peer_macaddr);
+
+       nla_nest_end(msg, veth_info);
+       nla_nest_end(msg, data);
+       nla_nest_end(msg, linkinfo);
+
+       rv = system_rtnl_call(msg);
+       if (rv) {
+               if (cfg->flags & VETH_OPT_PEER_NAME)
+                       D(SYSTEM, "Error adding veth '%s' with peer '%s': %d\n", veth->ifname, cfg->peer_name, rv);
+               else
+                       D(SYSTEM, "Error adding veth '%s': %d\n", veth->ifname, rv);
+       }
+
+       return rv;
+
+nla_put_failure:
+       nlmsg_free(msg);
+       return -ENOMEM;
+}
+
+int system_veth_del(struct device *veth)
+{
+       return system_link_del(veth->ifname);
+}
+
 static int system_vlan(struct device *dev, int id)
 {
        struct vlan_ioctl_args ifr = {
index 9c40e4c28cd22f0ef54fd206fb9c5d40d96aaaf4..6077b95140647fef67c11da6b1379b5f976f7273 100644 (file)
--- a/system.h
+++ b/system.h
@@ -14,6 +14,7 @@
 #ifndef __NETIFD_SYSTEM_H
 #define __NETIFD_SYSTEM_H
 
+#include <net/if.h>
 #include <sys/time.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
@@ -82,6 +83,20 @@ struct macvlan_config {
        unsigned char macaddr[6];
 };
 
+enum veth_opt {
+       VETH_OPT_MACADDR = (1 << 0),
+       VETH_OPT_PEER_NAME = (1 << 1),
+       VETH_OPT_PEER_MACADDR = (1 << 2),
+};
+
+struct veth_config {
+       enum veth_opt flags;
+
+       unsigned char macaddr[6];
+       char peer_name[IFNAMSIZ];
+       unsigned char peer_macaddr[6];
+};
+
 enum vlan_proto {
        VLAN_PROTO_8021Q = 0x8100,
        VLAN_PROTO_8021AD = 0x88A8
@@ -118,6 +133,9 @@ int system_bridge_delif(struct device *bridge, struct device *dev);
 int system_macvlan_add(struct device *macvlan, struct device *dev, struct macvlan_config *cfg);
 int system_macvlan_del(struct device *macvlan);
 
+int system_veth_add(struct device *veth, struct veth_config *cfg);
+int system_veth_del(struct device *veth);
+
 int system_vlan_add(struct device *dev, int id);
 int system_vlan_del(struct device *dev);
 
diff --git a/veth.c b/veth.c
new file mode 100644 (file)
index 0000000..e109f27
--- /dev/null
+++ b/veth.c
@@ -0,0 +1,247 @@
+/*
+ * netifd - network interface daemon
+ * Copyright (C) 2012 Felix Fietkau <nbd@openwrt.org>
+ * Copyright (C) 2013 Jo-Philipp Wich <jow@openwrt.org>
+ * Copyright (C) 2017 Matthias Schiffer <mschiffer@universe-factory.net>
+ *
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+#include <net/ethernet.h>
+
+#ifdef linux
+#include <netinet/ether.h>
+#endif
+
+#include "netifd.h"
+#include "device.h"
+#include "interface.h"
+#include "system.h"
+
+enum {
+       VETH_ATTR_MACADDR,
+       VETH_ATTR_PEER_NAME,
+       VETH_ATTR_PEER_MACADDR,
+       __VETH_ATTR_MAX
+};
+
+static const struct blobmsg_policy veth_attrs[__VETH_ATTR_MAX] = {
+       [VETH_ATTR_MACADDR] = { "macaddr", BLOBMSG_TYPE_STRING },
+       [VETH_ATTR_PEER_NAME]  = { "peer_name", BLOBMSG_TYPE_STRING },
+       [VETH_ATTR_PEER_MACADDR] = { "peer_macaddr", BLOBMSG_TYPE_STRING },
+};
+
+static const struct uci_blob_param_list veth_attr_list = {
+       .n_params = __VETH_ATTR_MAX,
+       .params = veth_attrs,
+
+       .n_next = 1,
+       .next = { &device_attr_list },
+};
+
+struct veth {
+       struct device dev;
+
+       device_state_cb set_state;
+
+       struct blob_attr *config_data;
+       struct veth_config config;
+};
+
+static int
+veth_set_down(struct veth *veth)
+{
+       veth->set_state(&veth->dev, false);
+       system_veth_del(&veth->dev);
+
+       return 0;
+}
+
+static int
+veth_set_up(struct veth *veth)
+{
+       int ret;
+
+       ret = system_veth_add(&veth->dev, &veth->config);
+       if (ret < 0)
+               return ret;
+
+       ret = veth->set_state(&veth->dev, true);
+       if (ret)
+               goto delete;
+
+       return 0;
+
+delete:
+       system_veth_del(&veth->dev);
+       return ret;
+}
+
+static int
+veth_set_state(struct device *dev, bool up)
+{
+       struct veth *veth;
+
+       D(SYSTEM, "veth_set_state(%s, %u)\n", dev->ifname, up);
+
+       veth = container_of(dev, struct veth, dev);
+       if (up)
+               return veth_set_up(veth);
+       else
+               return veth_set_down(veth);
+}
+
+static void
+veth_free(struct device *dev)
+{
+       struct veth *veth;
+
+       veth = container_of(dev, struct veth, dev);
+       free(veth->config_data);
+       free(veth);
+}
+
+static void
+veth_dump_info(struct device *dev, struct blob_buf *b)
+{
+       struct veth *veth;
+
+       veth = container_of(dev, struct veth, dev);
+       if (veth->config.flags & VETH_OPT_PEER_NAME)
+               blobmsg_add_string(b, "peer", veth->config.peer_name);
+       system_if_dump_info(dev, b);
+}
+
+static void
+veth_config_init(struct device *dev)
+{
+       device_set_present(dev, true);
+}
+
+static void
+veth_apply_settings(struct veth *veth, struct blob_attr **tb)
+{
+       struct veth_config *cfg = &veth->config;
+       struct blob_attr *cur;
+       struct ether_addr *ea;
+
+       cfg->flags = 0;
+
+       if ((cur = tb[VETH_ATTR_MACADDR]))
+       {
+               ea = ether_aton(blobmsg_data(cur));
+               if (ea) {
+                       memcpy(cfg->macaddr, ea, 6);
+                       cfg->flags |= VETH_OPT_MACADDR;
+               }
+       }
+
+       if ((cur = tb[VETH_ATTR_PEER_NAME]))
+       {
+               strncpy(cfg->peer_name, blobmsg_get_string(cur), sizeof(cfg->peer_name)-1);
+               cfg->flags |= VETH_OPT_PEER_NAME;
+       }
+
+       if ((cur = tb[VETH_ATTR_PEER_MACADDR]))
+       {
+               ea = ether_aton(blobmsg_data(cur));
+               if (ea) {
+                       memcpy(cfg->peer_macaddr, ea, 6);
+                       cfg->flags |= VETH_OPT_PEER_MACADDR;
+               }
+       }
+}
+
+static enum dev_change_type
+veth_reload(struct device *dev, struct blob_attr *attr)
+{
+       struct blob_attr *tb_dev[__DEV_ATTR_MAX];
+       struct blob_attr *tb_mv[__VETH_ATTR_MAX];
+       enum dev_change_type ret = DEV_CONFIG_APPLIED;
+       struct veth *veth;
+
+       veth = container_of(dev, struct veth, dev);
+       attr = blob_memdup(attr);
+
+       blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, tb_dev,
+               blob_data(attr), blob_len(attr));
+       blobmsg_parse(veth_attrs, __VETH_ATTR_MAX, tb_mv,
+               blob_data(attr), blob_len(attr));
+
+       device_init_settings(dev, tb_dev);
+       veth_apply_settings(veth, tb_mv);
+
+       if (veth->config_data) {
+               struct blob_attr *otb_dev[__DEV_ATTR_MAX];
+               struct blob_attr *otb_mv[__VETH_ATTR_MAX];
+
+               blobmsg_parse(device_attr_list.params, __DEV_ATTR_MAX, otb_dev,
+                       blob_data(veth->config_data), blob_len(veth->config_data));
+
+               if (uci_blob_diff(tb_dev, otb_dev, &device_attr_list, NULL))
+                   ret = DEV_CONFIG_RESTART;
+
+               blobmsg_parse(veth_attrs, __VETH_ATTR_MAX, otb_mv,
+                       blob_data(veth->config_data), blob_len(veth->config_data));
+
+               if (uci_blob_diff(tb_mv, otb_mv, &veth_attr_list, NULL))
+                   ret = DEV_CONFIG_RESTART;
+
+               veth_config_init(dev);
+       }
+
+       free(veth->config_data);
+       veth->config_data = attr;
+       return ret;
+}
+
+static struct device *
+veth_create(const char *name, struct device_type *devtype,
+       struct blob_attr *attr)
+{
+       struct veth *veth;
+       struct device *dev = NULL;
+
+       veth = calloc(1, sizeof(*veth));
+       if (!veth)
+               return NULL;
+
+       dev = &veth->dev;
+       device_init(dev, devtype, name);
+       dev->config_pending = true;
+
+       veth->set_state = dev->set_state;
+       dev->set_state = veth_set_state;
+
+       dev->hotplug_ops = NULL;
+
+       veth_reload(dev, attr);
+
+       return dev;
+}
+
+static struct device_type veth_device_type = {
+       .name = "veth",
+       .config_params = &veth_attr_list,
+       .create = veth_create,
+       .config_init = veth_config_init,
+       .reload = veth_reload,
+       .free = veth_free,
+       .dump_info = veth_dump_info,
+};
+
+static void __init veth_device_type_init(void)
+{
+       device_type_add(&veth_device_type);
+}