service: add vxlan tunnel support
authorFelix Fietkau <nbd@nbd.name>
Wed, 15 Jun 2022 13:12:25 +0000 (15:12 +0200)
committerFelix Fietkau <nbd@nbd.name>
Wed, 15 Jun 2022 15:01:32 +0000 (17:01 +0200)
Signed-off-by: Felix Fietkau <nbd@nbd.name>
CMakeLists.txt
examples/net0.json
network.c
network.h
service.c
service.h
unetd.h
utils.h
vxlan.c [new file with mode: 0644]
wg.c

index 02fadbdb57a463cdde33909e80a7cda90266878e..73c4cfa55300003f9ba884a31f800e246425e071 100644 (file)
@@ -17,7 +17,7 @@ FIND_LIBRARY(libjson NAMES json-c json)
 OPTION(UBUS_SUPPORT "enable ubus support" ON)
 IF(CMAKE_SYSTEM_NAME STREQUAL "Linux")
        FIND_LIBRARY(nl nl-tiny)
-       SET(SOURCES ${SOURCES} wg-linux.c)
+       SET(SOURCES ${SOURCES} wg-linux.c vxlan.c)
 ELSE()
        SET(nl "")
 ENDIF()
index 567d508b736930784c770f6f688af8bf238c2189..1b969873c77fbeefa3ef7b4757f5f0c29a3c0b2d 100644 (file)
@@ -31,6 +31,8 @@
        "services": {
                "l2-tunnel": {
                        "type": "vxlan",
+                       "config": {
+                       },
                        "members": [ "master", "@ap" ]
                },
                "usteer": {
index 9e9485c7a58a166ddfa5980be543ae96133ba5b2..e4229540cc877eeabb659942cc11335e57d95045 100644 (file)
--- a/network.c
+++ b/network.c
@@ -3,7 +3,10 @@
  * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
  */
 #include <arpa/inet.h>
+#include <sys/types.h>
+#include <sys/socket.h>
 #include <sys/wait.h>
+#include <net/if.h>
 #include <libubox/avl-cmp.h>
 #include <libubox/utils.h>
 #include <libubox/blobmsg_json.h>
@@ -48,6 +51,7 @@ const struct blobmsg_policy network_policy[__NETWORK_ATTR_MAX] = {
        [NETWORK_ATTR_KEEPALIVE] = { "keepalive", BLOBMSG_TYPE_INT32 },
        [NETWORK_ATTR_DOMAIN] = { "domain", BLOBMSG_TYPE_STRING },
        [NETWORK_ATTR_UPDATE_CMD] = { "update-cmd", BLOBMSG_TYPE_STRING },
+       [NETWORK_ATTR_TUNNELS] = { "tunnels", BLOBMSG_TYPE_TABLE },
 };
 
 AVL_TREE(networks, avl_strcmp, false, NULL);
@@ -327,6 +331,12 @@ static int network_setup(struct network *net)
                return -1;
        }
 
+       net->ifindex = if_nametoindex(network_name(net));
+       if (!net->ifindex) {
+               fprintf(stderr, "Could not get ifindex for network %s\n", network_name(net));
+               return -1;
+       }
+
        return 0;
 }
 
@@ -405,6 +415,9 @@ network_set_config(struct network *net, struct blob_attr *config)
        if ((cur = tb[NETWORK_ATTR_DOMAIN]) != NULL)
                net->config.domain = blobmsg_get_string(cur);
 
+       if ((cur = tb[NETWORK_ATTR_TUNNELS]) != NULL)
+               net->config.tunnels = cur;
+
        if ((cur = tb[NETWORK_ATTR_KEY]) == NULL)
                goto invalid;
 
index da0a28182916495e64bbb2290d9435841f0f4939..ea52be3cc69cab0e9bfe84bfe96d05d02c6d8ba8 100644 (file)
--- a/network.h
+++ b/network.h
@@ -33,6 +33,7 @@ struct network {
                const char *interface;
                const char *update_cmd;
                const char *domain;
+               struct blob_attr *tunnels;
                struct blob_attr *net_data;
        } config;
 
@@ -45,6 +46,7 @@ struct network {
                bool local_host_changed;
        } net_config;
 
+       int ifindex;
        struct network_host *prev_local_host;
        struct avl_tree hosts;
        struct vlist_tree peers;
@@ -67,6 +69,7 @@ enum {
        NETWORK_ATTR_UPDATE_CMD,
        NETWORK_ATTR_KEEPALIVE,
        NETWORK_ATTR_DOMAIN,
+       NETWORK_ATTR_TUNNELS,
        __NETWORK_ATTR_MAX,
 };
 
index f7daacdd1445a0ac26ac9b8c47dcc4efeef7c6cb..29b41a20a684c6e417761e5be7a1478b78a569e1 100644 (file)
--- a/service.c
+++ b/service.c
@@ -104,6 +104,10 @@ service_add(struct network *net, struct blob_attr *data)
                s->type = strcpy(type_buf, type);
        if (config)
                s->config = memcpy(config_buf, config, blob_pad_len(config));
+#ifdef linux
+       if (type && !strcmp(type, "vxlan"))
+               s->ops = &vxlan_ops;
+#endif
 
        service_parse_members(net, s, tb[SERVICE_ATTR_MEMBERS]);
        vlist_add(&net->services, &s->node, name_buf);
@@ -122,11 +126,37 @@ static void
 service_update(struct vlist_tree *tree, struct vlist_node *node_new,
               struct vlist_node *node_old)
 {
-       struct network_service *s_old;
+       struct network *net = container_of(tree, struct network, services);
+       struct network_service *s_old, *s_new;
 
+       s_new = container_of_safe(node_new, struct network_service, node);
        s_old = container_of_safe(node_old, struct network_service, node);
-       if (s_old)
-               free(s_old);
+
+       if (s_new && s_old && s_new->ops && s_new->ops == s_old->ops) {
+               s_new->ops->init(net, s_new, s_old);
+               goto out;
+       }
+
+       if (s_new && s_new->ops)
+               s_new->ops->init(net, s_new, NULL);
+
+       if (s_old && s_old->ops)
+               s_old->ops->free(net, s_old);
+
+out:
+       free(s_old);
+}
+
+void network_services_peer_update(struct network *net, struct network_peer *peer)
+{
+       struct network_service *s;
+
+       vlist_for_each_element(&net->services, s, node) {
+               if (!s->ops || !s->ops->peer_update)
+                       continue;
+
+               s->ops->peer_update(net, s, peer);
+       }
 }
 
 void network_services_init(struct network *net)
index b85fbe671faaa73eb6ec8dafde4a900974fe0ff8..09041d837e6aa4184ba6c77489ad4cd21e2bf8b0 100644 (file)
--- a/service.h
+++ b/service.h
@@ -5,19 +5,47 @@
 #ifndef __UNETD_SERVICE_H
 #define __UNETD_SERVICE_H
 
+struct vxlan_tunnel;
+struct service_ops;
+
 struct network_service {
        struct vlist_node node;
 
        struct blob_attr *config;
+
        const char *type;
 
+       const struct service_ops *ops;
+       union {
+               struct vxlan_tunnel *vxlan;
+               void *priv;
+       };
+
        int n_members;
        struct network_host *members[];
 };
 
+struct service_ops {
+       void (*init)(struct network *net,
+                    struct network_service *s_new,
+                    struct network_service *s_old);
+       void (*peer_update)(struct network *net, struct network_service *s,
+                           struct network_peer *peer);
+       void (*free)(struct network *net, struct network_service *s);
+};
+
+extern const struct service_ops vxlan_ops;
+
+static inline const char *
+network_service_name(struct network_service *s)
+{
+       return s->node.avl.key;
+}
+
 void network_services_init(struct network *net);
 void network_services_free(struct network *net);
 void network_services_add(struct network *net, struct blob_attr *data);
+void network_services_peer_update(struct network *net, struct network_peer *peer);
 
 static inline void network_services_update_start(struct network *net)
 {
diff --git a/unetd.h b/unetd.h
index e7d13604a900e2abf027a27801793f5abf1f0fcf..b6fd43746747e60782d5d0c32ed5c2c1a07eb650 100644 (file)
--- a/unetd.h
+++ b/unetd.h
@@ -32,6 +32,7 @@ extern bool debug;
 #define D_NET(net, format, ...)        D("network %s " format, network_name(net), ##__VA_ARGS__)
 #define D_HOST(net, host, format, ...) D_NET(net, "host %s " format, network_host_name(host), ##__VA_ARGS__)
 #define D_PEER(net, peer, format, ...) D_NET(net, "host %s " format, network_peer_name(peer), ##__VA_ARGS__)
+#define D_SERVICE(net, service, format, ...) D_NET(net, "service %s " format, network_service_name(service), ##__VA_ARGS__)
 
 
 void unetd_write_hosts(void);
diff --git a/utils.h b/utils.h
index eac083eb1491d2aed9805215527910b3f2f27d3e..b0243743e8db6c8e5fb3d253fab9d38d2c615577 100644 (file)
--- a/utils.h
+++ b/utils.h
@@ -55,4 +55,31 @@ int network_get_subnet(int af, union network_addr *addr, int *mask,
                       const char *str);
 int network_get_local_addr(void *local, const union network_endpoint *target);
 
+#define DIV_ROUND_UP(n, d)     (((n) + (d) - 1) / (d))
+
+#define bitmask_size(len)      (4 * DIV_ROUND_UP(len, 32))
+
+static inline bool bitmask_test(uint32_t *mask, unsigned int i)
+{
+       return mask[i / 32] & (1 << (i % 32));
+}
+
+static inline void bitmask_set(uint32_t *mask, unsigned int i)
+{
+       mask[i / 32] |= 1 << (i % 32);
+}
+
+static inline void bitmask_clear(uint32_t *mask, unsigned int i)
+{
+       mask[i / 32] &= ~(1 << (i % 32));
+}
+
+static inline void bitmask_set_val(uint32_t *mask, unsigned int i, bool val)
+{
+       if (val)
+               bitmask_set(mask, i);
+       else
+               bitmask_clear(mask, i);
+}
+
 #endif
diff --git a/vxlan.c b/vxlan.c
new file mode 100644 (file)
index 0000000..38db876
--- /dev/null
+++ b/vxlan.c
@@ -0,0 +1,466 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Felix Fietkau <nbd@nbd.name>
+ */
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netlink/msg.h>
+#include <netlink/attr.h>
+#include <netlink/socket.h>
+#include <netinet/if_ether.h>
+#include <net/if.h>
+#include <linux/rtnetlink.h>
+#include "unetd.h"
+
+struct vxlan_tunnel {
+       struct network *net;
+       struct network_service *s;
+       char ifname[IFNAMSIZ + 1];
+       int ifindex;
+       uint16_t mtu;
+       uint16_t port;
+       uint32_t vni;
+       uint32_t *forward_ports;
+       uint32_t *cur_forward_ports;
+       bool active;
+};
+
+static struct nl_sock *rtnl;
+static bool ignore_errors;
+
+static int
+unetd_nl_error_cb(struct sockaddr_nl *nla, struct nlmsgerr *err,
+                  void *arg)
+{
+       struct nlmsghdr *nlh = (struct nlmsghdr *) err - 1;
+       struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
+       struct nlattr *attrs;
+       int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
+       int len = nlh->nlmsg_len;
+       const char *errstr = "(unknown)";
+
+       if (ignore_errors)
+               return NL_STOP;
+
+       if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
+               return NL_STOP;
+
+       if (!(nlh->nlmsg_flags & NLM_F_CAPPED))
+               ack_len += err->msg.nlmsg_len - sizeof(*nlh);
+
+       attrs = (void *) ((unsigned char *) nlh + ack_len);
+       len -= ack_len;
+
+       nla_parse(tb, NLMSGERR_ATTR_MAX, attrs, len, NULL);
+       if (tb[NLMSGERR_ATTR_MSG])
+               errstr = nla_data(tb[NLMSGERR_ATTR_MSG]);
+
+       D("Netlink error(%d): %s\n", err->error, errstr);
+
+       return NL_STOP;
+}
+
+static struct nl_msg *vxlan_rtnl_msg(const char *ifname, int type, int flags)
+{
+       struct ifinfomsg iim = {
+               .ifi_family = AF_UNSPEC,
+       };
+       struct nl_msg *msg;
+
+       msg = nlmsg_alloc_simple(type, flags | NLM_F_REQUEST);
+       if (!msg)
+               return NULL;
+
+       nlmsg_append(msg, &iim, sizeof(iim), 0);
+       nla_put_string(msg, IFLA_IFNAME, ifname);
+
+       return msg;
+}
+
+static int vxlan_rtnl_call(struct nl_msg *msg)
+{
+       int ret;
+
+       ret = nl_send_auto_complete(rtnl, msg);
+       nlmsg_free(msg);
+
+       if (ret < 0)
+               return ret;
+
+       return nl_wait_for_ack(rtnl);
+}
+
+static int
+vxlan_rtnl_init(void)
+{
+       int fd, opt;
+
+       if (rtnl)
+               return 0;
+
+       rtnl = nl_socket_alloc();
+       if (!rtnl)
+               return -1;
+
+       if (nl_connect(rtnl, NETLINK_ROUTE))
+               goto free;
+
+       nl_socket_disable_seq_check(rtnl);
+       nl_socket_set_buffer_size(rtnl, 65536, 0);
+       nl_cb_err(nl_socket_get_cb(rtnl), NL_CB_CUSTOM, unetd_nl_error_cb, NULL);
+
+       fd = nl_socket_get_fd(rtnl);
+
+       opt = 1;
+       setsockopt(fd, SOL_NETLINK, NETLINK_EXT_ACK, &opt, sizeof(opt));
+
+       opt = 1;
+       setsockopt(fd, SOL_NETLINK, NETLINK_CAP_ACK, &opt, sizeof(opt));
+
+       return 0;
+
+free:
+       nl_socket_free(rtnl);
+       rtnl = NULL;
+       return -1;
+}
+
+static uint32_t
+vxlan_tunnel_id(struct vxlan_tunnel *vt)
+{
+       siphash_key_t key = {};
+       const char *name = network_service_name(vt->s);
+       uint64_t val;
+
+       if (vt->vni != ~0)
+               return vt->vni;
+
+       siphash_to_le64(&val, name, strlen(name), &key);
+
+       return val & 0x00ffffff;
+}
+
+static int
+vxlan_update_host_fdb_entry(struct vxlan_tunnel *vt, struct network_host *host, bool add)
+{
+       struct ndmsg ndmsg = {
+               .ndm_family = PF_BRIDGE,
+               .ndm_state = NUD_NOARP | NUD_PERMANENT,
+               .ndm_flags = NTF_SELF,
+               .ndm_ifindex = vt->ifindex,
+       };
+       unsigned int flags = NLM_F_REQUEST;
+       uint8_t lladdr[ETH_ALEN] = {};
+       struct nl_msg *msg;
+
+       if (add)
+               flags |= NLM_F_CREATE | NLM_F_APPEND;
+
+       msg = nlmsg_alloc_simple(add ? RTM_NEWNEIGH : RTM_DELNEIGH, flags);
+       nlmsg_append(msg, &ndmsg, sizeof(ndmsg), 0);
+       nla_put(msg, NDA_LLADDR, ETH_ALEN, lladdr);
+       nla_put(msg, NDA_DST, sizeof(struct in6_addr), &host->peer.local_addr);
+       nla_put_u32(msg, NDA_IFINDEX, vt->net->ifindex);
+
+       return vxlan_rtnl_call(msg);
+}
+
+static void
+vxlan_update_fdb_hosts(struct vxlan_tunnel *vt)
+{
+       struct network_service *s = vt->s;
+       bool active;
+       int i;
+
+       if (!vt->active)
+               return;
+
+       for (i = 0; i < s->n_members; i++) {
+               if (s->members[i] == vt->net->net_config.local_host)
+                       continue;
+
+               if (vt->forward_ports && !bitmask_test(vt->forward_ports, i))
+                       continue;
+
+               active = s->members[i]->peer.state.connected;
+               if (active == bitmask_test(vt->cur_forward_ports, i))
+                       continue;
+
+               if (!vxlan_update_host_fdb_entry(vt, s->members[i], active))
+                       bitmask_set_val(vt->cur_forward_ports, i, active);
+       }
+}
+
+static void
+vxlan_peer_update(struct network *net, struct network_service *s, struct network_peer *peer)
+{
+       if (!s->vxlan)
+               return;
+
+       vxlan_update_fdb_hosts(s->vxlan);
+}
+
+static void
+vxlan_tunnel_init(struct vxlan_tunnel *vt)
+{
+       struct network_peer *local = &vt->net->net_config.local_host->peer;
+       struct nlattr *linkinfo, *data;
+       struct nl_msg *msg;
+
+       if (vxlan_rtnl_init())
+               return;
+
+       msg = vxlan_rtnl_msg(vt->ifname, RTM_NEWLINK, NLM_F_CREATE | NLM_F_EXCL);
+
+       linkinfo = nla_nest_start(msg, IFLA_LINKINFO);
+       nla_put_string(msg, IFLA_INFO_KIND, "vxlan");
+       nla_put_u32(msg, IFLA_MTU, vt->mtu);
+
+       data = nla_nest_start(msg, IFLA_INFO_DATA);
+       nla_put_u32(msg, IFLA_VXLAN_ID, vxlan_tunnel_id(vt));
+       nla_put(msg, IFLA_VXLAN_LOCAL6, sizeof(struct in6_addr), &local->local_addr);
+       nla_put_u16(msg, IFLA_VXLAN_PORT, vt->port);
+       nla_put_u8(msg, IFLA_VXLAN_LEARNING, 1);
+       nla_nest_end(msg, data);
+
+       nla_nest_end(msg, linkinfo);
+
+       if (vxlan_rtnl_call(msg) < 0)
+               return;
+
+       vt->ifindex = if_nametoindex(vt->ifname);
+       if (!vt->ifindex) {
+               D_SERVICE(vt->net, vt->s, "failed to get ifindex for device %s", vt->ifname);
+               return;
+       }
+
+       vt->active = true;
+       vxlan_update_fdb_hosts(vt);
+}
+
+static void
+vxlan_tunnel_teardown(struct vxlan_tunnel *vt)
+{
+       struct nl_msg *msg;
+
+       if (!rtnl)
+               return;
+
+       vt->active = false;
+       msg = vxlan_rtnl_msg(vt->ifname, RTM_DELLINK, 0);
+       vxlan_rtnl_call(msg);
+}
+
+static const char *
+vxlan_find_ifname(struct network *net, const char *service)
+{
+       struct blob_attr *cur;
+       int rem;
+
+       if (!net->config.tunnels)
+               return NULL;
+
+       blobmsg_for_each_attr(cur, net->config.tunnels, rem) {
+               const char *name;
+
+               if (!blobmsg_check_attr(cur, true) ||
+                   blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+                       continue;
+
+               if (strcmp(blobmsg_get_string(cur), service) != 0)
+                       continue;
+
+               name = blobmsg_name(cur);
+               if (strlen(name) > IFNAMSIZ)
+                       break;
+
+               return name;
+       }
+
+       return NULL;
+}
+
+static void
+__vxlan_mark_forward_host(struct vxlan_tunnel *vt, struct network_host *host)
+{
+       struct network_service *s = vt->s;
+       unsigned int i;
+
+       for (i = 0; i < s->n_members; i++) {
+               if (s->members[i] != host)
+                       continue;
+
+               bitmask_set(vt->forward_ports, i);
+               break;
+       }
+}
+
+static void
+vxlan_mark_forward_host(struct vxlan_tunnel *vt, const char *name)
+{
+       struct network *net = vt->net;
+       struct network_host *host;
+
+       host = avl_find_element(&net->hosts, name, host, node);
+       if (!host)
+               return;
+
+       __vxlan_mark_forward_host(vt, host);
+}
+
+static void
+vxlan_mark_forward_group(struct vxlan_tunnel *vt, const char *name)
+{
+       struct network *net = vt->net;
+       struct network_group *group;
+       int i;
+
+       group = avl_find_element(&net->groups, name, group, node);
+       if (!group)
+               return;
+
+       for (i = 0; i < group->n_members; i++)
+               __vxlan_mark_forward_host(vt, group->members[i]);
+}
+
+static void
+vxlan_init_forward_ports(struct vxlan_tunnel *vt, struct blob_attr *data)
+{
+       unsigned int len = bitmask_size(vt->s->n_members);
+       struct blob_attr *cur;
+       int rem;
+
+       vt->cur_forward_ports = realloc(vt->cur_forward_ports, len);
+       memset(vt->cur_forward_ports, 0, len);
+
+       if (!data || blobmsg_check_array(data, BLOBMSG_TYPE_STRING) <= 0) {
+               free(vt->forward_ports);
+               vt->forward_ports = NULL;
+               return;
+       }
+
+       vt->forward_ports = realloc(vt->forward_ports, len);
+       memset(vt->forward_ports, 0, len);
+       blobmsg_for_each_attr(cur, data, rem) {
+               const char *name = blobmsg_get_string(cur);
+
+               if (name[0] == '@')
+                       vxlan_mark_forward_group(vt, name + 1);
+               else
+                       vxlan_mark_forward_host(vt, name);
+       }
+}
+
+static bool
+vxlan_config_equal(struct network_service *s1, struct network_service *s2)
+{
+       int i;
+
+       if (!blob_attr_equal(s1->config, s2->config))
+               return false;
+
+       if (s1->n_members != s2->n_members)
+               return false;
+
+       for (i = 0; i < s1->n_members; i++)
+               if (memcmp(s1->members[i]->peer.key, s2->members[i]->peer.key,
+                          CURVE25519_KEY_SIZE) != 0)
+                       return false;
+
+       return true;
+}
+
+static void
+vxlan_init(struct network *net, struct network_service *s,
+          struct network_service *s_old)
+{
+       enum {
+               VXCFG_ATTR_FWD_PORTS,
+               VXCFG_ATTR_ID,
+               VXCFG_ATTR_PORT,
+               VXCFG_ATTR_MTU,
+               __VXCFG_ATTR_MAX
+       };
+       static const struct blobmsg_policy policy[__VXCFG_ATTR_MAX] = {
+               [VXCFG_ATTR_FWD_PORTS] = { "forward_ports", BLOBMSG_TYPE_ARRAY },
+               [VXCFG_ATTR_ID] = { "id", BLOBMSG_TYPE_INT32 },
+               [VXCFG_ATTR_PORT] = { "port", BLOBMSG_TYPE_INT32 },
+               [VXCFG_ATTR_MTU] = { "mtu", BLOBMSG_TYPE_INT32 },
+       };
+       struct blob_attr *tb[__VXCFG_ATTR_MAX] = {};
+       struct blob_attr *cur;
+       struct vxlan_tunnel *vt = s->vxlan;
+       const char *name;
+
+       if (s_old) {
+               vt = s_old->vxlan;
+               s_old->vxlan = NULL;
+               if (!vt)
+                       return;
+
+               if (vxlan_config_equal(s, s_old)) {
+                       s->vxlan = vt;
+                       vt->s = s;
+                       return;
+               }
+
+               vxlan_tunnel_teardown(vt);
+               goto init;
+       }
+
+       name = vxlan_find_ifname(net, network_service_name(s));
+       if (!name) {
+               D_SERVICE(net, s, "no configured tunnel ifname");
+               return;
+       }
+
+       vt = calloc(1, sizeof(*s->vxlan));
+       snprintf(vt->ifname, sizeof(vt->ifname), "%s", name);
+       vt->net = net;
+
+init:
+       s->vxlan = vt;
+       vt->s = s;
+       if (s->config)
+               blobmsg_parse(policy, __VXCFG_ATTR_MAX, tb, blobmsg_data(s->config),
+                             blobmsg_len(s->config));
+
+       vxlan_init_forward_ports(vt, tb[VXCFG_ATTR_FWD_PORTS]);
+       if ((cur = tb[VXCFG_ATTR_ID]) != NULL)
+               vt->vni = blobmsg_get_u32(cur) & 0x00ffffff;
+       else
+               vt->vni = ~0;
+
+       if ((cur = tb[VXCFG_ATTR_PORT]) != NULL)
+               vt->port = blobmsg_get_u32(cur);
+       else
+               vt->port = 4789;
+
+       if ((cur = tb[VXCFG_ATTR_MTU]) != NULL)
+               vt->mtu = blobmsg_get_u32(cur);
+       else
+               vt->mtu = 1500;
+
+       vxlan_tunnel_init(vt);
+}
+
+static void
+vxlan_free(struct network *net, struct network_service *s)
+{
+       struct vxlan_tunnel *vt = s->vxlan;
+
+       if (!vt)
+               return;
+
+       vxlan_tunnel_teardown(vt);
+       s->vxlan = NULL;
+       free(vt->forward_ports);
+       free(vt);
+}
+
+const struct service_ops vxlan_ops = {
+       .init = vxlan_init,
+       .free = vxlan_free,
+       .peer_update = vxlan_peer_update,
+};
diff --git a/wg.c b/wg.c
index be8e0cb123a59daca34b92d35781752af2a009aa..ea519906c2a4c2c1432e28686690a07b8f34a896 100644 (file)
--- a/wg.c
+++ b/wg.c
@@ -35,6 +35,16 @@ void wg_cleanup_network(struct network *net)
                net->wg.ops->cleanup(net);
 }
 
+static void
+wg_peer_set_connected(struct network *net, struct network_peer *peer, bool val)
+{
+       if (peer->state.connected == val)
+               return;
+
+       peer->state.connected = val;
+       network_services_peer_update(net, peer);
+}
+
 struct network_peer *wg_peer_update_start(struct network *net, const uint8_t *key)
 {
        struct network_peer *peer;
@@ -46,7 +56,7 @@ struct network_peer *wg_peer_update_start(struct network *net, const uint8_t *ke
        peer->state.handshake = false;
        peer->state.idle++;
        if (peer->state.idle >= 2 * net->net_config.keepalive)
-               peer->state.connected = false;
+               wg_peer_set_connected(net, peer, false);
        if (peer->state.idle > net->net_config.keepalive)
                network_pex_event(net, peer, PEX_EV_PING);
 
@@ -69,9 +79,9 @@ void wg_peer_set_last_handshake(struct network *net, struct network_peer *peer,
        peer->state.last_handshake = sec;
        sec = now - sec;
        if (sec <= net->net_config.keepalive) {
-               peer->state.connected = true;
                if (peer->state.idle > sec)
                        peer->state.idle = sec;
+               wg_peer_set_connected(net, peer, true);
        }
 }
 
@@ -83,7 +93,7 @@ void wg_peer_set_rx_bytes(struct network *net, struct network_peer *peer,
        peer->state.rx_bytes = bytes;
        if (diff > 0) {
                peer->state.idle = 0;
-               peer->state.connected = true;
+               wg_peer_set_connected(net, peer, true);
        }
 }