system-linux: add VXLAN support
[project/netifd.git] / system-linux.c
index 8a1173c..8888047 100644 (file)
@@ -4,6 +4,7 @@
  * 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>
+ * 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
@@ -25,6 +26,7 @@
 #include <net/if_arp.h>
 
 #include <arpa/inet.h>
+#include <netinet/ether.h>
 #include <netinet/in.h>
 
 #include <linux/rtnetlink.h>
@@ -38,6 +40,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
@@ -315,6 +318,11 @@ static void system_set_neigh6gcstaletime(struct device *dev, const char *val)
        system_set_dev_sysctl("/proc/sys/net/ipv6/neigh/%s/gc_stale_time", dev->ifname, val);
 }
 
+static void system_set_neigh4locktime(struct device *dev, const char *val)
+{
+       system_set_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/locktime", dev->ifname, val);
+}
+
 static void system_set_dadtransmits(struct device *dev, const char *val)
 {
        system_set_dev_sysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits", dev->ifname, val);
@@ -483,6 +491,12 @@ static int system_get_neigh6gcstaletime(struct device *dev, char *buf, const siz
                        dev->ifname, buf, buf_sz);
 }
 
+static int system_get_neigh4locktime(struct device *dev, char *buf, const size_t buf_sz)
+{
+       return system_get_dev_sysctl("/proc/sys/net/ipv4/neigh/%s/locktime",
+                       dev->ifname, buf, buf_sz);
+}
+
 static int system_get_dadtransmits(struct device *dev, char *buf, const size_t buf_sz)
 {
        return system_get_dev_sysctl("/proc/sys/net/ipv6/conf/%s/dad_transmits",
@@ -1131,6 +1145,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 = {
@@ -1284,6 +1358,11 @@ system_if_get_settings(struct device *dev, struct device_settings *s)
                s->flags |= DEV_OPT_NEIGHREACHABLETIME;
        }
 
+       if (!system_get_neigh4locktime(dev, buf, sizeof(buf))) {
+               s->neigh4locktime = strtoul(buf, NULL, 0);
+               s->flags |= DEV_OPT_NEIGHLOCKTIME;
+       }
+
        if (!system_get_neigh4gcstaletime(dev, buf, sizeof(buf))) {
                s->neigh4gcstaletime = strtoul(buf, NULL, 0);
                s->flags |= DEV_OPT_NEIGHGCSTALETIME;
@@ -1393,6 +1472,10 @@ system_if_apply_settings(struct device *dev, struct device_settings *s, unsigned
                snprintf(buf, sizeof(buf), "%d", s->neigh6reachabletime);
                system_set_neigh6reachabletime(dev, buf);
        }
+       if (s->flags & DEV_OPT_NEIGHLOCKTIME & apply_mask) {
+               snprintf(buf, sizeof(buf), "%d", s->neigh4locktime);
+               system_set_neigh4locktime(dev, buf);
+       }
        if (s->flags & DEV_OPT_NEIGHGCSTALETIME & apply_mask) {
                snprintf(buf, sizeof(buf), "%d", s->neigh4gcstaletime);
                system_set_neigh4gcstaletime(dev, buf);
@@ -2459,6 +2542,155 @@ failure:
 }
 #endif
 
+#ifdef IFLA_VXLAN_MAX
+static int system_add_vxlan(const char *name, const unsigned int link, struct blob_attr **tb, bool v6)
+{
+       struct blob_attr *tb_data[__VXLAN_DATA_ATTR_MAX];
+       struct nl_msg *msg;
+       struct nlattr *linkinfo, *data;
+       struct ifinfomsg iim = { .ifi_family = AF_UNSPEC, };
+       struct blob_attr *cur;
+       int ret = 0;
+
+       if ((cur = tb[TUNNEL_ATTR_DATA]))
+               blobmsg_parse(vxlan_data_attr_list.params, __VXLAN_DATA_ATTR_MAX, tb_data,
+                       blobmsg_data(cur), blobmsg_len(cur));
+       else
+               return -EINVAL;
+
+       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_string(msg, IFLA_IFNAME, name);
+
+       if ((cur = tb_data[VXLAN_DATA_ATTR_MACADDR])) {
+               struct ether_addr *ea = ether_aton(blobmsg_get_string(cur));
+               if (!ea) {
+                       ret = -EINVAL;
+                       goto failure;
+               }
+
+               nla_put(msg, IFLA_ADDRESS, ETH_ALEN, ea);
+       }
+
+       if ((cur = tb[TUNNEL_ATTR_MTU])) {
+               uint32_t mtu = blobmsg_get_u32(cur);
+               nla_put_u32(msg, IFLA_MTU, mtu);
+       }
+
+       if (!(linkinfo = nla_nest_start(msg, IFLA_LINKINFO))) {
+               ret = -ENOMEM;
+               goto failure;
+       }
+
+       nla_put_string(msg, IFLA_INFO_KIND, "vxlan");
+
+       if (!(data = nla_nest_start(msg, IFLA_INFO_DATA))) {
+               ret = -ENOMEM;
+               goto failure;
+       }
+
+       if (link)
+               nla_put_u32(msg, IFLA_VXLAN_LINK, link);
+
+       if ((cur = tb_data[VXLAN_DATA_ATTR_ID])) {
+               uint32_t id = blobmsg_get_u32(cur);
+               if (id >= (1u << 24) - 1) {
+                       ret = -EINVAL;
+                       goto failure;
+               }
+
+               nla_put_u32(msg, IFLA_VXLAN_ID, id);
+       }
+
+       if (v6) {
+               struct in6_addr in6buf;
+               if ((cur = tb[TUNNEL_ATTR_LOCAL])) {
+                       if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) {
+                               ret = -EINVAL;
+                               goto failure;
+                       }
+                       nla_put(msg, IFLA_VXLAN_LOCAL6, sizeof(in6buf), &in6buf);
+               }
+
+               if ((cur = tb[TUNNEL_ATTR_REMOTE])) {
+                       if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) {
+                               ret = -EINVAL;
+                               goto failure;
+                       }
+                       nla_put(msg, IFLA_VXLAN_GROUP6, sizeof(in6buf), &in6buf);
+               }
+       } else {
+               struct in_addr inbuf;
+
+               if ((cur = tb[TUNNEL_ATTR_LOCAL])) {
+                       if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) {
+                               ret = -EINVAL;
+                               goto failure;
+                       }
+                       nla_put(msg, IFLA_VXLAN_LOCAL, sizeof(inbuf), &inbuf);
+               }
+
+               if ((cur = tb[TUNNEL_ATTR_REMOTE])) {
+                       if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) {
+                               ret = -EINVAL;
+                               goto failure;
+                       }
+                       nla_put(msg, IFLA_VXLAN_GROUP, sizeof(inbuf), &inbuf);
+               }
+       }
+
+       uint32_t port = 4789;
+       if ((cur = tb_data[VXLAN_DATA_ATTR_PORT])) {
+               port = blobmsg_get_u32(cur);
+               if (port < 1 || port > 65535) {
+                       ret = -EINVAL;
+                       goto failure;
+               }
+       }
+       nla_put_u16(msg, IFLA_VXLAN_PORT, htons(port));
+
+       if ((cur = tb[TUNNEL_ATTR_TOS])) {
+               char *str = blobmsg_get_string(cur);
+               unsigned tos = 1;
+
+               if (strcmp(str, "inherit")) {
+                       if (!system_tos_aton(str, &tos))
+                               return -EINVAL;
+               }
+
+               nla_put_u8(msg, IFLA_VXLAN_TOS, tos);
+       }
+
+       if ((cur = tb[TUNNEL_ATTR_TTL])) {
+               uint32_t ttl = blobmsg_get_u32(cur);
+               if (ttl < 1 || ttl > 255) {
+                       ret = -EINVAL;
+                       goto failure;
+               }
+
+               nla_put_u8(msg, IFLA_VXLAN_TTL, ttl);
+       }
+
+       nla_nest_end(msg, data);
+       nla_nest_end(msg, linkinfo);
+
+       ret = system_rtnl_call(msg);
+       if (ret)
+               D(SYSTEM, "Error adding vxlan '%s': %d\n", name, ret);
+
+       return ret;
+
+failure:
+       nlmsg_free(msg);
+       return ret;
+}
+#endif
+
 static int system_add_proto_tunnel(const char *name, const uint8_t proto, const unsigned int link, struct blob_attr **tb)
 {
        struct blob_attr *cur;
@@ -2528,7 +2760,8 @@ static int __system_del_ip_tunnel(const char *name, struct blob_attr **tb)
 
        if (!strcmp(str, "greip") || !strcmp(str, "gretapip") ||
            !strcmp(str, "greip6") || !strcmp(str, "gretapip6") ||
-           !strcmp(str, "vtiip") || !strcmp(str, "vtiip6"))
+           !strcmp(str, "vtiip") || !strcmp(str, "vtiip6") ||
+           !strcmp(str, "vxlan") || !strcmp(str, "vxlan6"))
                return system_link_del(name);
        else
                return tunnel_ioctl(name, SIOCDELTUNNEL, NULL);
@@ -2752,6 +2985,12 @@ failure:
        } else if (!strcmp(str, "vtiip6")) {
                return system_add_vti_tunnel(name, "vti6", link, tb, true);
 #endif
+#ifdef IFLA_VXLAN_MAX
+       } else if(!strcmp(str, "vxlan")) {
+               return system_add_vxlan(name, link, tb, false);
+       } else if(!strcmp(str, "vxlan6")) {
+               return system_add_vxlan(name, link, tb, true);
+#endif
 #endif
        } else if (!strcmp(str, "ipip")) {
                return system_add_proto_tunnel(name, IPPROTO_IPIP, link, tb);