network: add support for specifying a host gateway
authorFelix Fietkau <nbd@nbd.name>
Wed, 24 Aug 2022 12:02:48 +0000 (14:02 +0200)
committerFelix Fietkau <nbd@nbd.name>
Wed, 24 Aug 2022 12:02:49 +0000 (14:02 +0200)
A host will only use its gateway as a peer, and connections from
other hosts will be routed through the gateway host

Signed-off-by: Felix Fietkau <nbd@nbd.name>
host.c
host.h
scripts/unet-cli
wg-linux.c
wg-user.c

diff --git a/host.c b/host.c
index bef8863292cfca764343a48c0f6dc7e0006abe26..996dbcf5982de7a0bfeff9c86eca38f920297c3b 100644 (file)
--- a/host.c
+++ b/host.c
@@ -93,6 +93,7 @@ network_host_create(struct network *net, struct blob_attr *attr)
                NETWORK_HOST_SUBNET,
                NETWORK_HOST_PORT,
                NETWORK_HOST_ENDPOINT,
+               NETWORK_HOST_GATEWAY,
                __NETWORK_HOST_MAX
        };
        static const struct blobmsg_policy policy[__NETWORK_HOST_MAX] = {
@@ -102,6 +103,7 @@ network_host_create(struct network *net, struct blob_attr *attr)
                [NETWORK_HOST_SUBNET] = { "subnet", BLOBMSG_TYPE_ARRAY },
                [NETWORK_HOST_PORT] = { "port", BLOBMSG_TYPE_INT32 },
                [NETWORK_HOST_ENDPOINT] = { "endpoint", BLOBMSG_TYPE_STRING },
+               [NETWORK_HOST_GATEWAY] = { "gateway", BLOBMSG_TYPE_STRING },
        };
        struct blob_attr *tb[__NETWORK_HOST_MAX];
        struct blob_attr *cur, *ipaddr, *subnet;
@@ -109,8 +111,8 @@ network_host_create(struct network *net, struct blob_attr *attr)
        struct network_host *host;
        struct network_peer *peer;
        int ipaddr_len, subnet_len;
-       const char *name, *endpoint;
-       char *name_buf, *endpoint_buf;
+       const char *name, *endpoint, *gateway;
+       char *name_buf, *endpoint_buf, *gateway_buf;
        int rem;
 
        blobmsg_parse(policy, __NETWORK_HOST_MAX, tb, blobmsg_data(attr), blobmsg_len(attr));
@@ -133,6 +135,11 @@ network_host_create(struct network *net, struct blob_attr *attr)
        else
                endpoint = NULL;
 
+       if ((cur = tb[NETWORK_HOST_GATEWAY]) != NULL)
+               gateway = blobmsg_get_string(cur);
+       else
+               gateway = NULL;
+
        if (b64_decode(blobmsg_get_string(tb[NETWORK_HOST_KEY]), key,
                       sizeof(key)) != sizeof(key))
                return;
@@ -146,7 +153,8 @@ network_host_create(struct network *net, struct blob_attr *attr)
                        &name_buf, strlen(name) + 1,
                        &ipaddr, ipaddr_len,
                        &subnet, subnet_len,
-                       &endpoint_buf, endpoint ? strlen(endpoint) + 1 : 0);
+                       &endpoint_buf, endpoint ? strlen(endpoint) + 1 : 0,
+                       &gateway_buf, gateway ? strlen(endpoint) + 1 : 0);
        peer = &host->peer;
        if ((cur = tb[NETWORK_HOST_IPADDR]) != NULL && ipaddr_len)
                peer->ipaddr = memcpy(ipaddr, cur, ipaddr_len);
@@ -158,6 +166,8 @@ network_host_create(struct network *net, struct blob_attr *attr)
                peer->port = net->net_config.port;
        if (endpoint)
                peer->endpoint = strcpy(endpoint_buf, endpoint);
+       if (gateway)
+               host->gateway = strcpy(gateway_buf, gateway);
        memcpy(peer->key, key, sizeof(key));
        host->node.key = strcpy(name_buf, name);
 
@@ -202,17 +212,27 @@ void network_hosts_update_start(struct network *net)
 
 void network_hosts_update_done(struct network *net)
 {
-       struct network_host *host, *tmp;
+       struct network_host *local, *host, *tmp;
+       const char *local_name;
 
-       if (!net->net_config.local_host)
+       local = net->net_config.local_host;
+       if (!local)
                goto out;
 
+       local_name = network_host_name(local);
+
        if (net->net_config.local_host_changed)
-               wg_init_local(net, &net->net_config.local_host->peer);
+               wg_init_local(net, &local->peer);
 
-       avl_for_each_element(&net->hosts, host, node)
-               if (host != net->net_config.local_host)
-                       vlist_add(&net->peers, &host->peer.node, host->peer.key);
+       avl_for_each_element(&net->hosts, host, node) {
+               if (host == local)
+                       continue;
+               if (host->gateway && strcmp(host->gateway, local_name) != 0)
+                       continue;
+               if (local->gateway && strcmp(local->gateway, network_host_name(host)) != 0)
+                       continue;
+               vlist_add(&net->peers, &host->peer.node, host->peer.key);
+       }
 
 out:
        vlist_flush(&net->peers);
@@ -242,7 +262,7 @@ network_hosts_connect_cb(struct uloop_timeout *t)
        avl_for_each_element(&net->hosts, host, node) {
                struct network_peer *peer = &host->peer;
 
-               if (host == net->net_config.local_host)
+               if (!network_host_is_peer(host))
                        continue;
 
                if (peer->state.connected)
diff --git a/host.h b/host.h
index 985ac4569e8f4361cbe8a4da7267e85840cf75fa..b802d776c7088c63c0f99b92cacada22b620b13f 100644 (file)
--- a/host.h
+++ b/host.h
@@ -36,6 +36,7 @@ struct network_peer {
 struct network_host {
        struct avl_node node;
 
+       const char *gateway;
        struct network_peer peer;
 };
 
@@ -55,6 +56,11 @@ static inline const char *network_host_name(struct network_host *host)
        return host->node.key;
 }
 
+static inline bool network_host_is_peer(struct network_host *host)
+{
+       return !!host->peer.node.avl.key;
+}
+
 static inline const char *network_peer_name(struct network_peer *peer)
 {
        struct network_host *host;
@@ -66,6 +72,29 @@ static inline const char *network_peer_name(struct network_peer *peer)
        return network_host_name(host);
 }
 
+
+static inline bool
+network_host_uses_peer_route(struct network_host *host, struct network *net,
+                           struct network_peer *peer)
+{
+       if (&host->peer == peer || host == net->net_config.local_host)
+               return false;
+
+       if (net->net_config.local_host->gateway &&
+           !strcmp(net->net_config.local_host->gateway, network_peer_name(peer)))
+               return true;
+
+       if (!host->gateway)
+               return false;
+
+       return !strcmp(host->gateway, network_peer_name(peer));
+}
+
+#define for_each_routed_host(cur_host, net, peer)                      \
+       avl_for_each_element(&(net)->hosts, cur_host, node)             \
+               if (network_host_uses_peer_route(host, net, peer))
+
+
 void network_hosts_update_start(struct network *net);
 void network_hosts_update_done(struct network *net);
 void network_hosts_add(struct network *net, struct blob_attr *hosts);
index 06bb70906ea0e4c333fe2b9e327a06af9b17acc0..c5e702569237f2b35e8a6b0107dc0a22abf291d5 100755 (executable)
@@ -51,6 +51,7 @@ function usage() {
             "  ipaddr=[+|-]<val>[,<val>...]            set/add/remove host ip addresses\n",
             "  subnet=[+|-]<val>[,<val>...]            set/add/remove host announced subnets\n",
             "  endpoint=<val>                          set host endpoint address\n",
+            "  gateway=<name>                          set host gateway (using name of other host)\n",
             " ssh host options (add-ssh-host, set-ssh-host)\n",
             "  auth_key=<key>                          use <key> as public auth key on the remote host\n",
             "  priv_key=<key>                          use <key> as private host key on the remote host (default: generate a new key)\n",
@@ -211,6 +212,7 @@ function set_host(name) {
        set_fields(host, {
                key: "string",
                endpoint: "string",
+               gateway: "string",
                port: "int",
                ipaddr: "array",
                subnet: "array",
index 1dc0c9bb408ab5e586a9e0cc06ad7251011feb91..57b86347808b964f843a08f8465a2b314b85e2a3 100644 (file)
@@ -147,25 +147,12 @@ wg_linux_peer_req_done(struct wg_linux_peer_req *req)
        return wg_genl_call(req->msg);
 }
 
-static int
-wg_linux_peer_update(struct network *net, struct network_peer *peer, enum wg_update_cmd cmd)
+static void
+wg_linux_peer_msg_add_allowed_ip(struct nl_msg *msg, struct network_peer *peer)
 {
-       struct wg_linux_peer_req req;
        struct blob_attr *cur;
-       struct nl_msg *msg;
-       struct nlattr *ips;
        int rem;
 
-       msg = wg_linux_peer_req_init(net, peer, &req);
-
-       if (cmd == WG_PEER_DELETE) {
-               nla_put_u32(msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME);
-               goto out;
-       }
-
-       nla_put_u32(msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS);
-
-       ips = nla_nest_start(msg, WGPEER_A_ALLOWEDIPS);
        wg_linux_msg_add_ip(msg, AF_INET6, &peer->local_addr.in6, 128);
 
        blobmsg_for_each_attr(cur, peer->ipaddr, rem) {
@@ -200,6 +187,31 @@ wg_linux_peer_update(struct network *net, struct network_peer *peer, enum wg_upd
                wg_linux_msg_add_ip(msg, af, &addr, mask);
        }
 
+}
+
+static int
+wg_linux_peer_update(struct network *net, struct network_peer *peer, enum wg_update_cmd cmd)
+{
+       struct wg_linux_peer_req req;
+       struct network_host *host;
+       struct nl_msg *msg;
+       struct nlattr *ips;
+
+       msg = wg_linux_peer_req_init(net, peer, &req);
+
+       if (cmd == WG_PEER_DELETE) {
+               nla_put_u32(msg, WGPEER_A_FLAGS, WGPEER_F_REMOVE_ME);
+               goto out;
+       }
+
+       nla_put_u32(msg, WGPEER_A_FLAGS, WGPEER_F_REPLACE_ALLOWEDIPS);
+
+       ips = nla_nest_start(msg, WGPEER_A_ALLOWEDIPS);
+
+       wg_linux_peer_msg_add_allowed_ip(msg, peer);
+       for_each_routed_host(host, net, peer)
+               wg_linux_peer_msg_add_allowed_ip(msg, &host->peer);
+
        nla_nest_end(msg, ips);
 
 out:
index 3a5004ac7d0c289cb36d1756fa1b2e3630f331c8..a057a10dd6329286bc63ec4290acc9f4c6d2a080 100644 (file)
--- a/wg-user.c
+++ b/wg-user.c
@@ -279,30 +279,15 @@ wg_user_init_local(struct network *net, struct network_peer *peer)
        return wg_req_done(&req);
 }
 
-static int
-wg_user_peer_update(struct network *net, struct network_peer *peer, enum wg_update_cmd cmd)
+static void
+wg_user_peer_req_add_allowed_ip(struct wg_req *req, struct network_peer *peer)
 {
-       struct blob_attr *cur;
-       struct wg_req req;
        char addr[INET6_ADDRSTRLEN];
-       char key[WG_KEY_LEN_HEX];
+       struct blob_attr *cur;
        int rem;
 
-       if (wg_req_init(&req, net, true))
-               return -1;
-
-       key_to_hex(key, peer->key);
-       wg_req_set(&req, "public_key", key);
-
-       if (cmd == WG_PEER_DELETE) {
-               wg_req_set(&req, "remove", "true");
-               goto out;
-       }
-
-       wg_req_set(&req, "replace_allowed_ips", "true");
-
        inet_ntop(AF_INET6, &peer->local_addr.in6, addr, sizeof(addr));
-       wg_req_printf(&req, "allowed_ip", "%s/128", addr);
+       wg_req_printf(req, "allowed_ip", "%s/128", addr);
 
        blobmsg_for_each_attr(cur, peer->ipaddr, rem) {
                const char *str = blobmsg_get_string(cur);
@@ -320,7 +305,7 @@ wg_user_peer_update(struct network *net, struct network_peer *peer, enum wg_upda
                if (inet_pton(af, str, &in6) != 1)
                        continue;
 
-               wg_req_printf(&req, "allowed_ip", "%s/%d", str, mask);
+               wg_req_printf(req, "allowed_ip", "%s/%d", str, mask);
        }
 
        blobmsg_for_each_attr(cur, peer->subnet, rem) {
@@ -335,8 +320,32 @@ wg_user_peer_update(struct network *net, struct network_peer *peer, enum wg_upda
                        continue;
 
                inet_ntop(af, &addr, buf, sizeof(buf));
-               wg_req_printf(&req, "allowed_ip", "%s/%d", buf, mask);
+               wg_req_printf(req, "allowed_ip", "%s/%d", buf, mask);
        }
+}
+
+static int
+wg_user_peer_update(struct network *net, struct network_peer *peer, enum wg_update_cmd cmd)
+{
+       struct network_host *host;
+       struct wg_req req;
+       char key[WG_KEY_LEN_HEX];
+
+       if (wg_req_init(&req, net, true))
+               return -1;
+
+       key_to_hex(key, peer->key);
+       wg_req_set(&req, "public_key", key);
+
+       if (cmd == WG_PEER_DELETE) {
+               wg_req_set(&req, "remove", "true");
+               goto out;
+       }
+
+       wg_req_set(&req, "replace_allowed_ips", "true");
+       wg_user_peer_req_add_allowed_ip(&req, peer);
+       for_each_routed_host(host, net, peer)
+               wg_user_peer_req_add_allowed_ip(&req, &host->peer);
 
 out:
        return wg_req_done(&req);