]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network/wireguard: cleanups for resolving endpoints
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 29 Nov 2021 12:21:21 +0000 (21:21 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Wed, 1 Dec 2021 23:39:32 +0000 (08:39 +0900)
This makes
- drop peers_with_unresolved_endpoint and peers_with_failed_endpoint,
- drop destroy handler for sd_resolve_query, and manage each query by peer,
- add random fluctuation to the timeout for retry handler,
- retry timer event source is now managed by peer,
- use sd_event_source_disable_unref().

src/network/netdev/wireguard.c
src/network/netdev/wireguard.h

index 2a802a3739c8f3197781362560d318f96b0d8ca9..2c60ba197d709c26575b7aa439a3db444ca74d89 100644 (file)
 #include "networkd-util.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "random-util.h"
 #include "resolve-private.h"
 #include "string-util.h"
 #include "strv.h"
 #include "wireguard.h"
 
-static void resolve_endpoints(NetDev *netdev);
+static void wireguard_resolve_endpoints(NetDev *netdev);
+static int peer_resolve_endpoint(WireguardPeer *peer);
 
 static WireguardPeer* wireguard_peer_free(WireguardPeer *peer) {
         WireguardIPmask *mask;
@@ -41,9 +43,6 @@ static WireguardPeer* wireguard_peer_free(WireguardPeer *peer) {
         if (peer->wireguard) {
                 LIST_REMOVE(peers, peer->wireguard->peers, peer);
 
-                set_remove(peer->wireguard->peers_with_unresolved_endpoint, peer);
-                set_remove(peer->wireguard->peers_with_failed_endpoint, peer);
-
                 if (peer->section)
                         hashmap_remove(peer->wireguard->peers_by_section, peer->section);
         }
@@ -60,6 +59,9 @@ static WireguardPeer* wireguard_peer_free(WireguardPeer *peer) {
         free(peer->preshared_key_file);
         explicit_bzero_safe(peer->preshared_key, WG_KEY_LEN);
 
+        sd_event_source_disable_unref(peer->resolve_retry_event_source);
+        sd_resolve_query_unref(peer->resolve_query);
+
         return mfree(peer);
 }
 
@@ -288,7 +290,8 @@ static int wireguard_set_interface(NetDev *netdev) {
         return 0;
 }
 
-static void wireguard_peer_destroy_callback(WireguardPeer *peer) {
+static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) {
+        WireguardPeer *peer = userdata;
         NetDev *netdev;
 
         assert(peer);
@@ -296,134 +299,132 @@ static void wireguard_peer_destroy_callback(WireguardPeer *peer) {
 
         netdev = NETDEV(peer->wireguard);
 
-        if (section_is_invalid(peer->section))
-                wireguard_peer_free(peer);
+        if (!netdev_is_managed(netdev))
+                return 0;
 
-        netdev_unref(netdev);
-}
+        peer->resolve_query = sd_resolve_query_unref(peer->resolve_query);
 
-static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) {
-        NetDev *netdev = userdata;
-        Wireguard *w;
+        (void) peer_resolve_endpoint(peer);
+        return 0;
+}
 
-        assert(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
+static usec_t peer_next_resolve_usec(WireguardPeer *peer) {
+        usec_t usec;
 
-        if (!netdev_is_managed(netdev))
-                return 0;
+        /* Given the number of retries this function will return an exponential increasing amount of
+         * milliseconds to wait starting at 200ms and capped at 25 seconds. */
 
-        assert(set_isempty(w->peers_with_unresolved_endpoint));
-
-        SWAP_TWO(w->peers_with_unresolved_endpoint, w->peers_with_failed_endpoint);
+        assert(peer);
 
-        resolve_endpoints(netdev);
+        usec = (2 << MIN(peer->n_retries, 7U)) * 100 * USEC_PER_MSEC;
 
-        return 0;
+        return random_u64_range(usec / 10) + usec * 9 / 10;
 }
 
-/*
- * Given the number of retries this function will return will an exponential
- * increasing time in milliseconds to wait starting at 200ms and capped at 25 seconds.
- */
-static int exponential_backoff_milliseconds(unsigned n_retries) {
-        return (2 << MIN(n_retries, 7U)) * 100 * USEC_PER_MSEC;
-}
+static int wireguard_peer_resolve_handler(
+              sd_resolve_query *q,
+              int ret,
+              const struct addrinfo *ai,
+              void *userdata) {
 
-static int wireguard_resolve_handler(sd_resolve_query *q,
-                                     int ret,
-                                     const struct addrinfo *ai,
-                                     WireguardPeer *peer) {
+        WireguardPeer *peer = userdata;
         NetDev *netdev;
-        Wireguard *w;
         int r;
 
         assert(peer);
         assert(peer->wireguard);
 
-        w = peer->wireguard;
-        netdev = NETDEV(w);
+        netdev = NETDEV(peer->wireguard);
 
         if (!netdev_is_managed(netdev))
                 return 0;
 
         if (ret != 0) {
-                log_netdev_error(netdev, "Failed to resolve host '%s:%s': %s", peer->endpoint_host, peer->endpoint_port, gai_strerror(ret));
-
-                r = set_ensure_put(&w->peers_with_failed_endpoint, NULL, peer);
-                if (r < 0) {
-                        log_netdev_error(netdev, "Failed to save a peer, dropping the peer: %m");
-                        peer->section->invalid = true;
-                        goto resolve_next;
-                }
+                log_netdev_warning(netdev, "Failed to resolve host '%s:%s', ignoring: %s",
+                                   peer->endpoint_host, peer->endpoint_port, gai_strerror(ret));
+                peer->n_retries++;
 
         } else if ((ai->ai_family == AF_INET && ai->ai_addrlen == sizeof(struct sockaddr_in)) ||
-                   (ai->ai_family == AF_INET6 && ai->ai_addrlen == sizeof(struct sockaddr_in6)))
+                   (ai->ai_family == AF_INET6 && ai->ai_addrlen == sizeof(struct sockaddr_in6))) {
+
                 memcpy(&peer->endpoint, ai->ai_addr, ai->ai_addrlen);
-        else
-                log_netdev_error(netdev, "Neither IPv4 nor IPv6 address found for peer endpoint %s:%s, ignoring the address.",
-                                 peer->endpoint_host, peer->endpoint_port);
+                (void) wireguard_set_interface(netdev);
+                peer->n_retries = 0;
 
-resolve_next:
-        if (!set_isempty(w->peers_with_unresolved_endpoint)) {
-                resolve_endpoints(netdev);
-                return 0;
+        } else {
+                log_netdev_warning(netdev, "Neither IPv4 nor IPv6 address found for peer endpoint %s:%s, ignoring the endpoint.",
+                                   peer->endpoint_host, peer->endpoint_port);
+                peer->n_retries++;
         }
 
-        (void) wireguard_set_interface(netdev);
-
-        if (!set_isempty(w->peers_with_failed_endpoint)) {
-                usec_t usec;
-
-                w->n_retries++;
-                usec = usec_add(now(CLOCK_MONOTONIC), exponential_backoff_milliseconds(w->n_retries));
-                r = event_reset_time(netdev->manager->event, &w->resolve_retry_event_source,
-                                     CLOCK_MONOTONIC, usec, 0, on_resolve_retry, netdev,
-                                     0, "wireguard-resolve-retry", true);
-                if (r < 0) {
-                        log_netdev_warning_errno(netdev, r, "Could not arm resolve retry handler: %m");
-                        return 0;
-                }
+        if (peer->n_retries > 0) {
+                r = event_reset_time_relative(netdev->manager->event,
+                                              &peer->resolve_retry_event_source,
+                                              clock_boottime_or_monotonic(),
+                                              peer_next_resolve_usec(peer), 0,
+                                              on_resolve_retry, peer, 0, "wireguard-resolve-retry", true);
+                if (r < 0)
+                        log_netdev_warning_errno(netdev, r, "Could not arm resolve retry handler for endpoint %s:%s, ignoring: %m",
+                                                 peer->endpoint_host, peer->endpoint_port);
         }
 
+        wireguard_resolve_endpoints(netdev);
         return 0;
 }
 
-static void resolve_endpoints(NetDev *netdev) {
+static int peer_resolve_endpoint(WireguardPeer *peer) {
         static const struct addrinfo hints = {
                 .ai_family = AF_UNSPEC,
                 .ai_socktype = SOCK_DGRAM,
                 .ai_protocol = IPPROTO_UDP
         };
+        NetDev *netdev;
+        int r;
+
+        assert(peer);
+        assert(peer->wireguard);
+
+        netdev = NETDEV(peer->wireguard);
+
+        if (!peer->endpoint_host || !peer->endpoint_port)
+                /* Not necessary to resolve the endpoint. */
+                return 0;
+
+        if (event_source_is_enabled(peer->resolve_retry_event_source) > 0)
+                /* Timer event source is enabled. The endpoint will be resolved later. */
+                return 0;
+
+        if (peer->resolve_query)
+                /* Being resolved, or already resolved. */
+                return 0;
+
+        r = sd_resolve_getaddrinfo(netdev->manager->resolve,
+                                   &peer->resolve_query,
+                                   peer->endpoint_host,
+                                   peer->endpoint_port,
+                                   &hints,
+                                   wireguard_peer_resolve_handler,
+                                   peer);
+        if (r < 0)
+                return log_netdev_full_errno(netdev, r == -ENOBUFS ? LOG_DEBUG : LOG_WARNING, r,
+                                             "Failed to create endpoint resolver for %s:%s, ignoring: %m",
+                                             peer->endpoint_host, peer->endpoint_port);
+
+        return 0;
+}
+
+static void wireguard_resolve_endpoints(NetDev *netdev) {
         WireguardPeer *peer;
         Wireguard *w;
-        int r;
 
         assert(netdev);
         w = WIREGUARD(netdev);
         assert(w);
 
-        SET_FOREACH(peer, w->peers_with_unresolved_endpoint) {
-                r = resolve_getaddrinfo(netdev->manager->resolve,
-                                        NULL,
-                                        peer->endpoint_host,
-                                        peer->endpoint_port,
-                                        &hints,
-                                        wireguard_resolve_handler,
-                                        wireguard_peer_destroy_callback,
-                                        peer);
-                if (r == -ENOBUFS)
+        LIST_FOREACH(peers, peer, w->peers)
+                if (peer_resolve_endpoint(peer) == -ENOBUFS)
+                        /* Too many requests. Let's resolve remaining endpoints later. */
                         break;
-                if (r < 0) {
-                        log_netdev_error_errno(netdev, r, "Failed to create resolver: %m");
-                        continue;
-                }
-
-                /* Avoid freeing netdev. It will be unrefed by the destroy callback. */
-                netdev_ref(netdev);
-
-                (void) set_remove(w->peers_with_unresolved_endpoint, peer);
-        }
 }
 
 static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
@@ -431,7 +432,7 @@ static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_m
         assert(WIREGUARD(netdev));
 
         (void) wireguard_set_interface(netdev);
-        resolve_endpoints(netdev);
+        wireguard_resolve_endpoints(netdev);
         return 0;
 }
 
@@ -761,7 +762,6 @@ int config_parse_wireguard_endpoint(
 
                 peer->endpoint_host = mfree(peer->endpoint_host);
                 peer->endpoint_port = mfree(peer->endpoint_port);
-                set_remove(w->peers_with_unresolved_endpoint, peer);
 
                 TAKE_PTR(peer);
                 return 0;
@@ -803,10 +803,6 @@ int config_parse_wireguard_endpoint(
         if (r < 0)
                 return log_oom();
 
-        r = set_ensure_put(&w->peers_with_unresolved_endpoint, NULL, peer);
-        if (r < 0)
-                return log_oom();
-
         TAKE_PTR(peer); /* The peer may already have been in the hash map, that is fine too. */
         return 0;
 }
@@ -1054,14 +1050,10 @@ static void wireguard_done(NetDev *netdev) {
         w = WIREGUARD(netdev);
         assert(w);
 
-        sd_event_source_unref(w->resolve_retry_event_source);
-
         explicit_bzero_safe(w->private_key, WG_KEY_LEN);
         free(w->private_key_file);
 
         hashmap_free_with_destructor(w->peers_by_section, wireguard_peer_free);
-        set_free(w->peers_with_unresolved_endpoint);
-        set_free(w->peers_with_failed_endpoint);
 
         set_free(w->routes);
 }
index 5d4b6da45ec278a6a106985ab052d2a2524219ca..29bacdc77109e170cd22b1f3bdb1630d14917625 100644 (file)
@@ -7,6 +7,9 @@ typedef struct Wireguard Wireguard;
 #include <netinet/in.h>
 #include <linux/wireguard.h>
 
+#include "sd-event.h"
+#include "sd-resolve.h"
+
 #include "in-addr-util.h"
 #include "netdev.h"
 #include "socket-util.h"
@@ -33,6 +36,10 @@ typedef struct WireguardPeer {
         char *endpoint_host;
         char *endpoint_port;
 
+        unsigned n_retries;
+        sd_event_source *resolve_retry_event_source;
+        sd_resolve_query *resolve_query;
+
         uint32_t route_table;
         uint32_t route_priority;
         bool route_table_set;
@@ -53,14 +60,8 @@ struct Wireguard {
         uint32_t fwmark;
 
         Hashmap *peers_by_section;
-        Set *peers_with_unresolved_endpoint;
-        Set *peers_with_failed_endpoint;
-
         LIST_HEAD(WireguardPeer, peers);
 
-        unsigned n_retries;
-        sd_event_source *resolve_retry_event_source;
-
         Set *routes;
         uint32_t route_table;
         uint32_t route_priority;