]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/network/netdev/wireguard.c
network: re-indent conf parsers in wireguard.c
[thirdparty/systemd.git] / src / network / netdev / wireguard.c
index 7fefef300468bcfc9692137c59634d0e0233e9b8..61fbc24cfbe43040fb117d949fc9ff7874906518 100644 (file)
 #include "sd-resolve.h"
 
 #include "alloc-util.h"
-#include "parse-util.h"
+#include "event-util.h"
 #include "fd-util.h"
-#include "strv.h"
+#include "fileio.h"
 #include "hexdecoct.h"
-#include "string-util.h"
-#include "wireguard.h"
+#include "memory-util.h"
+#include "netlink-util.h"
 #include "networkd-link.h"
-#include "networkd-util.h"
 #include "networkd-manager.h"
+#include "networkd-util.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "resolve-private.h"
+#include "string-util.h"
+#include "strv.h"
 #include "wireguard-netlink.h"
+#include "wireguard.h"
 
 static void resolve_endpoints(NetDev *netdev);
 
-static WireguardPeer *wireguard_peer_new(Wireguard *w, unsigned section) {
-        WireguardPeer *peer;
+static void wireguard_peer_free(WireguardPeer *peer) {
+        WireguardIPmask *mask;
+
+        if (!peer)
+                return;
+
+        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);
+        }
+
+        network_config_section_free(peer->section);
+
+        while ((mask = peer->ipmasks)) {
+                LIST_REMOVE(ipmasks, peer->ipmasks, mask);
+                free(mask);
+        }
+
+        free(peer->endpoint_host);
+        free(peer->endpoint_port);
+
+        free(peer);
+}
+
+DEFINE_NETWORK_SECTION_FUNCTIONS(WireguardPeer, wireguard_peer_free);
+
+static int wireguard_peer_new_static(Wireguard *w, const char *filename, unsigned section_line, WireguardPeer **ret) {
+        _cleanup_(network_config_section_freep) NetworkConfigSection *n = NULL;
+        _cleanup_(wireguard_peer_freep) WireguardPeer *peer = NULL;
+        int r;
 
         assert(w);
+        assert(ret);
+        assert(filename);
+        assert(section_line > 0);
+
+        r = network_config_section_new(filename, section_line, &n);
+        if (r < 0)
+                return r;
 
-        if (w->last_peer_section == section && w->peers)
-                return w->peers;
+        peer = hashmap_get(w->peers_by_section, n);
+        if (peer) {
+                *ret = TAKE_PTR(peer);
+                return 0;
+        }
 
         peer = new(WireguardPeer, 1);
         if (!peer)
-                return NULL;
+                return -ENOMEM;
 
         *peer = (WireguardPeer) {
                 .flags = WGPEER_F_REPLACE_ALLOWEDIPS,
+                .wireguard = w,
+                .section = TAKE_PTR(n),
         };
 
         LIST_PREPEND(peers, w->peers, peer);
-        w->last_peer_section = section;
 
-        return peer;
+        r = hashmap_ensure_allocated(&w->peers_by_section, &network_config_hash_ops);
+        if (r < 0)
+                return r;
+
+        r = hashmap_put(w->peers_by_section, peer->section, peer);
+        if (r < 0)
+                return r;
+
+        *ret = TAKE_PTR(peer);
+        return 0;
 }
 
-static int set_wireguard_interface(NetDev *netdev) {
+static int wireguard_set_ipmask_one(NetDev *netdev, sd_netlink_message *message, const WireguardIPmask *mask, uint16_t index) {
         int r;
-        unsigned i, j;
-        WireguardPeer *peer, *peer_start;
-        WireguardIPmask *mask, *mask_start = NULL;
+
+        assert(message);
+        assert(mask);
+        assert(index > 0);
+
+        /* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */
+
+        r = sd_netlink_message_open_array(message, index);
+        if (r < 0)
+                return 0;
+
+        r = sd_netlink_message_append_u16(message, WGALLOWEDIP_A_FAMILY, mask->family);
+        if (r < 0)
+                goto cancel;
+
+        r = netlink_message_append_in_addr_union(message, WGALLOWEDIP_A_IPADDR, mask->family, &mask->ip);
+        if (r < 0)
+                goto cancel;
+
+        r = sd_netlink_message_append_u8(message, WGALLOWEDIP_A_CIDR_MASK, mask->cidr);
+        if (r < 0)
+                goto cancel;
+
+        r = sd_netlink_message_close_container(message);
+        if (r < 0)
+                return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
+
+        return 1;
+
+cancel:
+        r = sd_netlink_message_cancel_array(message);
+        if (r < 0)
+                return log_netdev_error_errno(netdev, r, "Could not cancel wireguard allowed ip message attribute: %m");
+
+        return 0;
+}
+
+static int wireguard_set_peer_one(NetDev *netdev, sd_netlink_message *message, const WireguardPeer *peer, uint16_t index, WireguardIPmask **mask_start) {
+        WireguardIPmask *mask, *start;
+        uint16_t j = 0;
+        int r;
+
+        assert(message);
+        assert(peer);
+        assert(index > 0);
+        assert(mask_start);
+
+        /* This returns 1 on success, 0 on recoverable error, and negative errno on failure. */
+
+        start = *mask_start ?: peer->ipmasks;
+
+        r = sd_netlink_message_open_array(message, index);
+        if (r < 0)
+                return 0;
+
+        r = sd_netlink_message_append_data(message, WGPEER_A_PUBLIC_KEY, &peer->public_key, sizeof(peer->public_key));
+        if (r < 0)
+                goto cancel;
+
+        if (!*mask_start) {
+                r = sd_netlink_message_append_data(message, WGPEER_A_PRESHARED_KEY, &peer->preshared_key, WG_KEY_LEN);
+                if (r < 0)
+                        goto cancel;
+
+                r = sd_netlink_message_append_u32(message, WGPEER_A_FLAGS, peer->flags);
+                if (r < 0)
+                        goto cancel;
+
+                r = sd_netlink_message_append_u16(message, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval);
+                if (r < 0)
+                        goto cancel;
+
+                if (IN_SET(peer->endpoint.sa.sa_family, AF_INET, AF_INET6)) {
+                        r = netlink_message_append_sockaddr_union(message, WGPEER_A_ENDPOINT, &peer->endpoint);
+                        if (r < 0)
+                                goto cancel;
+                }
+        }
+
+        r = sd_netlink_message_open_container(message, WGPEER_A_ALLOWEDIPS);
+        if (r < 0)
+                goto cancel;
+
+        LIST_FOREACH(ipmasks, mask, start) {
+                r = wireguard_set_ipmask_one(netdev, message, mask, ++j);
+                if (r < 0)
+                        return r;
+                if (r == 0)
+                        break;
+        }
+
+        r = sd_netlink_message_close_container(message);
+        if (r < 0)
+                return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
+
+        r = sd_netlink_message_close_container(message);
+        if (r < 0)
+                return log_netdev_error_errno(netdev, r, "Could not add wireguard peer: %m");
+
+        *mask_start = mask; /* Start next cycle from this mask. */
+        return !mask;
+
+cancel:
+        r = sd_netlink_message_cancel_array(message);
+        if (r < 0)
+                return log_netdev_error_errno(netdev, r, "Could not cancel wireguard peers: %m");
+
+        return 0;
+}
+
+static int wireguard_set_interface(NetDev *netdev) {
         _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL;
-        Wireguard *w;
+        WireguardIPmask *mask_start = NULL;
+        WireguardPeer *peer, *peer_start;
         uint32_t serial;
+        Wireguard *w;
+        int r;
 
         assert(netdev);
         w = WIREGUARD(netdev);
         assert(w);
 
-        peer_start = w->peers;
+        for (peer_start = w->peers; peer_start; ) {
+                uint16_t i = 0;
 
-        do {
                 message = sd_netlink_message_unref(message);
 
                 r = sd_genl_message_new(netdev->manager->genl, SD_GENL_WIREGUARD, WG_CMD_SET_DEVICE, &message);
@@ -92,97 +262,14 @@ static int set_wireguard_interface(NetDev *netdev) {
                 if (r < 0)
                         return log_netdev_error_errno(netdev, r, "Could not append wireguard peer attributes: %m");
 
-                i = 0;
-
                 LIST_FOREACH(peers, peer, peer_start) {
-                        r = sd_netlink_message_open_array(message, ++i);
+                        r = wireguard_set_peer_one(netdev, message, peer, ++i, &mask_start);
                         if (r < 0)
+                                return r;
+                        if (r == 0)
                                 break;
-
-                        r = sd_netlink_message_append_data(message, WGPEER_A_PUBLIC_KEY, &peer->public_key, sizeof(peer->public_key));
-                        if (r < 0)
-                                break;
-
-                        if (!mask_start) {
-                                r = sd_netlink_message_append_data(message, WGPEER_A_PRESHARED_KEY, &peer->preshared_key, WG_KEY_LEN);
-                                if (r < 0)
-                                        break;
-
-                                r = sd_netlink_message_append_u32(message, WGPEER_A_FLAGS, peer->flags);
-                                if (r < 0)
-                                        break;
-
-                                r = sd_netlink_message_append_u16(message, WGPEER_A_PERSISTENT_KEEPALIVE_INTERVAL, peer->persistent_keepalive_interval);
-                                if (r < 0)
-                                        break;
-
-                                if (peer->endpoint.sa.sa_family == AF_INET) {
-                                        r = sd_netlink_message_append_data(message, WGPEER_A_ENDPOINT, &peer->endpoint.in, sizeof(peer->endpoint.in));
-                                        if (r < 0)
-                                                break;
-                                } else if (peer->endpoint.sa.sa_family == AF_INET6) {
-                                        r = sd_netlink_message_append_data(message, WGPEER_A_ENDPOINT, &peer->endpoint.in6, sizeof(peer->endpoint.in6));
-                                        if (r < 0)
-                                                break;
-                                }
-
-                                mask_start = peer->ipmasks;
-                        }
-
-                        r = sd_netlink_message_open_container(message, WGPEER_A_ALLOWEDIPS);
-                        if (r < 0) {
-                                mask_start = NULL;
-                                break;
-                        }
-                        j = 0;
-                        LIST_FOREACH(ipmasks, mask, mask_start) {
-                                r = sd_netlink_message_open_array(message, ++j);
-                                if (r < 0)
-                                        break;
-
-                                r = sd_netlink_message_append_u16(message, WGALLOWEDIP_A_FAMILY, mask->family);
-                                if (r < 0)
-                                        break;
-
-                                if (mask->family == AF_INET) {
-                                        r = sd_netlink_message_append_in_addr(message, WGALLOWEDIP_A_IPADDR, &mask->ip.in);
-                                        if (r < 0)
-                                                break;
-                                } else if (mask->family == AF_INET6) {
-                                        r = sd_netlink_message_append_in6_addr(message, WGALLOWEDIP_A_IPADDR, &mask->ip.in6);
-                                        if (r < 0)
-                                                break;
-                                }
-
-                                r = sd_netlink_message_append_u8(message, WGALLOWEDIP_A_CIDR_MASK, mask->cidr);
-                                if (r < 0)
-                                        break;
-
-                                r = sd_netlink_message_close_container(message);
-                                if (r < 0)
-                                        return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
-                        }
-                        mask_start = mask;
-                        if (mask_start) {
-                                r = sd_netlink_message_cancel_array(message);
-                                if (r < 0)
-                                        return log_netdev_error_errno(netdev, r, "Could not cancel wireguard allowed ip message attribute: %m");
-                        }
-                        r = sd_netlink_message_close_container(message);
-                        if (r < 0)
-                                return log_netdev_error_errno(netdev, r, "Could not add wireguard allowed ip: %m");
-
-                        r = sd_netlink_message_close_container(message);
-                        if (r < 0)
-                                return log_netdev_error_errno(netdev, r, "Could not add wireguard peer: %m");
-                }
-
-                peer_start = peer;
-                if (peer_start && !mask_start) {
-                        r = sd_netlink_message_cancel_array(message);
-                        if (r < 0)
-                                return log_netdev_error_errno(netdev, r, "Could not cancel wireguard peers: %m");
                 }
+                peer_start = peer; /* Start next cycle from this peer. */
 
                 r = sd_netlink_message_close_container(message);
                 if (r < 0)
@@ -191,31 +278,24 @@ static int set_wireguard_interface(NetDev *netdev) {
                 r = sd_netlink_send(netdev->manager->genl, message, &serial);
                 if (r < 0)
                         return log_netdev_error_errno(netdev, r, "Could not set wireguard device: %m");
-
-        } while (peer || mask_start);
+        }
 
         return 0;
 }
 
-static WireguardEndpoint* wireguard_endpoint_free(WireguardEndpoint *e) {
-        if (!e)
-                return NULL;
-        e->host = mfree(e->host);
-        e->port = mfree(e->port);
-        return mfree(e);
-}
+static void wireguard_peer_destroy_callback(WireguardPeer *peer) {
+        NetDev *netdev;
 
-static void wireguard_endpoint_destroy_callback(void *userdata) {
-        WireguardEndpoint *e = userdata;
+        assert(peer);
+        assert(peer->wireguard);
 
-        assert(e);
-        assert(e->netdev);
+        netdev = NETDEV(peer->wireguard);
 
-        netdev_unref(e->netdev);
-        wireguard_endpoint_free(e);
-}
+        if (section_is_invalid(peer->section))
+                wireguard_peer_free(peer);
 
-DEFINE_TRIVIAL_CLEANUP_FUNC(WireguardEndpoint*, wireguard_endpoint_free);
+        netdev_unref(netdev);
+}
 
 static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) {
         NetDev *netdev = userdata;
@@ -225,12 +305,12 @@ static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) {
         w = WIREGUARD(netdev);
         assert(w);
 
-        if (!netdev->manager)
-                /* The netdev is detached. */
+        if (!netdev_is_managed(netdev))
                 return 0;
 
-        assert(!w->unresolved_endpoints);
-        w->unresolved_endpoints = TAKE_PTR(w->failed_endpoints);
+        assert(set_isempty(w->peers_with_unresolved_endpoint));
+
+        SWAP_TWO(w->peers_with_unresolved_endpoint, w->peers_with_failed_endpoint);
 
         resolve_endpoints(netdev);
 
@@ -242,72 +322,70 @@ static int on_resolve_retry(sd_event_source *s, usec_t usec, void *userdata) {
  * increasing time in milliseconds to wait starting at 200ms and capped at 25 seconds.
  */
 static int exponential_backoff_milliseconds(unsigned n_retries) {
-        return (2 << MAX(n_retries, 7U)) * 100 * USEC_PER_MSEC;
+        return (2 << MIN(n_retries, 7U)) * 100 * USEC_PER_MSEC;
 }
 
 static int wireguard_resolve_handler(sd_resolve_query *q,
                                      int ret,
                                      const struct addrinfo *ai,
-                                     void *userdata) {
-        _cleanup_(netdev_unrefp) NetDev *netdev_will_unrefed = NULL;
-        NetDev *netdev = NULL;
-        WireguardEndpoint *e;
+                                     WireguardPeer *peer) {
+        NetDev *netdev;
         Wireguard *w;
         int r;
 
-        assert(userdata);
-        e = userdata;
-        netdev = e->netdev;
+        assert(peer);
+        assert(peer->wireguard);
 
-        assert(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
+        w = peer->wireguard;
+        netdev = NETDEV(w);
 
-        if (!netdev->manager)
-                /* The netdev is detached. */
+        if (!netdev_is_managed(netdev))
                 return 0;
 
         if (ret != 0) {
-                log_netdev_error(netdev, "Failed to resolve host '%s:%s': %s", e->host, e->port, gai_strerror(ret));
-                LIST_PREPEND(endpoints, w->failed_endpoints, e);
-                (void) sd_resolve_query_set_destroy_callback(q, NULL); /* Avoid freeing endpoint by destroy callback. */
-                netdev_will_unrefed = netdev; /* But netdev needs to be unrefed. */
+                log_netdev_error(netdev, "Failed to resolve host '%s:%s': %s", peer->endpoint_host, peer->endpoint_port, gai_strerror(ret));
+
+                r = set_ensure_allocated(&w->peers_with_failed_endpoint, NULL);
+                if (r < 0) {
+                        log_oom();
+                        peer->section->invalid = true;
+                        goto resolve_next;
+                }
+
+                r = set_put(w->peers_with_failed_endpoint, peer);
+                if (r < 0) {
+                        log_netdev_error(netdev, "Failed to save a peer, dropping the peer: %m");
+                        peer->section->invalid = true;
+                        goto resolve_next;
+                }
+
         } 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)))
-                memcpy(&e->peer->endpoint, ai->ai_addr, ai->ai_addrlen);
+                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", e->host, e->port);
+                log_netdev_error(netdev, "Neither IPv4 nor IPv6 address found for peer endpoint %s:%s, ignoring the address.",
+                                 peer->endpoint_host, peer->endpoint_port);
 
-        if (w->unresolved_endpoints) {
+resolve_next:
+        if (!set_isempty(w->peers_with_unresolved_endpoint)) {
                 resolve_endpoints(netdev);
                 return 0;
         }
 
-        set_wireguard_interface(netdev);
-        if (w->failed_endpoints) {
-                _cleanup_(sd_event_source_unrefp) sd_event_source *s = NULL;
+        (void) wireguard_set_interface(netdev);
+
+        if (!set_isempty(w->peers_with_failed_endpoint)) {
+                usec_t usec;
 
                 w->n_retries++;
-                r = sd_event_add_time(netdev->manager->event,
-                                      &s,
-                                      CLOCK_MONOTONIC,
-                                      now(CLOCK_MONOTONIC) + exponential_backoff_milliseconds(w->n_retries),
-                                      0,
-                                      on_resolve_retry,
-                                      netdev);
+                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;
                 }
-
-                r = sd_event_source_set_destroy_callback(s, (sd_event_destroy_t) netdev_destroy_callback);
-                if (r < 0) {
-                        log_netdev_warning_errno(netdev, r, "Failed to set destroy callback to event source: %m");
-                        return 0;
-                }
-
-                (void) sd_event_source_set_floating(s, true);
-                netdev_ref(netdev);
         }
 
         return 0;
@@ -319,25 +397,24 @@ static void resolve_endpoints(NetDev *netdev) {
                 .ai_socktype = SOCK_DGRAM,
                 .ai_protocol = IPPROTO_UDP
         };
-        WireguardEndpoint *endpoint;
+        WireguardPeer *peer;
         Wireguard *w;
+        Iterator i;
         int r = 0;
 
         assert(netdev);
         w = WIREGUARD(netdev);
         assert(w);
 
-        LIST_FOREACH(endpoints, endpoint, w->unresolved_endpoints) {
-                _cleanup_(sd_resolve_query_unrefp) sd_resolve_query *q = NULL;
-
-                r = sd_resolve_getaddrinfo(netdev->manager->resolve,
-                                           &q,
-                                           endpoint->host,
-                                           endpoint->port,
-                                           &hints,
-                                           wireguard_resolve_handler,
-                                           endpoint);
-
+        SET_FOREACH(peer, w->peers_with_unresolved_endpoint, i) {
+                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)
                         break;
                 if (r < 0) {
@@ -345,43 +422,34 @@ static void resolve_endpoints(NetDev *netdev) {
                         continue;
                 }
 
-                r = sd_resolve_query_set_destroy_callback(q, wireguard_endpoint_destroy_callback);
-                if (r < 0) {
-                        log_netdev_error_errno(netdev, r, "Failed to set destroy callback to resolving query: %m");
-                        continue;
-                }
-
-                (void) sd_resolve_query_set_floating(q, true);
-
                 /* Avoid freeing netdev. It will be unrefed by the destroy callback. */
                 netdev_ref(netdev);
 
-                LIST_REMOVE(endpoints, w->unresolved_endpoints, endpoint);
+                (void) set_remove(w->peers_with_unresolved_endpoint, peer);
         }
 }
 
 static int netdev_wireguard_post_create(NetDev *netdev, Link *link, sd_netlink_message *m) {
-        Wireguard *w;
-
         assert(netdev);
-        w = WIREGUARD(netdev);
-        assert(w);
+        assert(WIREGUARD(netdev));
 
-        set_wireguard_interface(netdev);
+        (void) wireguard_set_interface(netdev);
         resolve_endpoints(netdev);
         return 0;
 }
 
-int config_parse_wireguard_listen_port(const char *unit,
-                                       const char *filename,
-                                       unsigned line,
-                                       const char *section,
-                                       unsigned section_line,
-                                       const char *lvalue,
-                                       int ltype,
-                                       const char *rvalue,
-                                       void *data,
-                                       void *userdata) {
+int config_parse_wireguard_listen_port(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
         uint16_t *s = data;
         uint16_t port = 0;
         int r;
@@ -390,175 +458,205 @@ int config_parse_wireguard_listen_port(const char *unit,
         assert(data);
 
         if (!streq(rvalue, "auto")) {
-                r = parse_ip_port(rvalue, &port);
-                if (r < 0)
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Invalid port specification, ignoring assignment: %s", rvalue);
+                r = parse_ip_port(rvalue, s);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Invalid port specification, ignoring assignment: %s", rvalue);
+                        return 0;
+                }
         }
 
         *s = port;
-
         return 0;
 }
 
-static int parse_wireguard_key(const char *unit,
-                               const char *filename,
-                               unsigned line,
-                               const char *section,
-                               unsigned section_line,
-                               const char *lvalue,
-                               int ltype,
-                               const char *rvalue,
-                               void *data,
-                               void *userdata) {
+static int wireguard_decode_key_and_warn(
+                const char *rvalue,
+                uint8_t *ret,
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *lvalue) {
+
         _cleanup_free_ void *key = NULL;
         size_t len;
         int r;
 
-        assert(filename);
         assert(rvalue);
-        assert(userdata);
+        assert(ret);
+        assert(filename);
+        assert(lvalue);
+
+        if (isempty(rvalue)) {
+                memzero(ret, WG_KEY_LEN);
+                return 0;
+        }
 
         r = unbase64mem(rvalue, strlen(rvalue), &key, &len);
         if (r < 0) {
-                log_syntax(unit, LOG_ERR, filename, line, r, "Could not parse wireguard key \"%s\", ignoring assignment: %m", rvalue);
+                log_syntax(unit, LOG_ERR, filename, line, r,
+                           "Failed to decode wireguard key provided by %s=, ignoring assignment: %m", lvalue);
                 return 0;
         }
         if (len != WG_KEY_LEN) {
-                log_syntax(unit, LOG_ERR, filename, line, EINVAL,
-                           "Wireguard key is too short, ignoring assignment: %s", rvalue);
+                log_syntax(unit, LOG_ERR, filename, line, 0,
+                           "Wireguard key provided by %s= has invalid length (%zu bytes), ignoring assignment.",
+                           lvalue, len);
                 return 0;
         }
 
-        memcpy(userdata, key, WG_KEY_LEN);
+        memcpy(ret, key, WG_KEY_LEN);
         return true;
 }
 
-int config_parse_wireguard_private_key(const char *unit,
-                                       const char *filename,
-                                       unsigned line,
-                                       const char *section,
-                                       unsigned section_line,
-                                       const char *lvalue,
-                                       int ltype,
-                                       const char *rvalue,
-                                       void *data,
-                                       void *userdata) {
+int config_parse_wireguard_private_key(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
         Wireguard *w;
 
         assert(data);
-
         w = WIREGUARD(data);
-
         assert(w);
 
-        return parse_wireguard_key(unit,
-                                   filename,
-                                   line,
-                                   section,
-                                   section_line,
-                                   lvalue,
-                                   ltype,
-                                   rvalue,
-                                   data,
-                                   &w->private_key);
+        return wireguard_decode_key_and_warn(rvalue, w->private_key, unit, filename, line, lvalue);
 
 }
 
-int config_parse_wireguard_preshared_key(const char *unit,
-                                         const char *filename,
-                                         unsigned line,
-                                         const char *section,
-                                         unsigned section_line,
-                                         const char *lvalue,
-                                         int ltype,
-                                         const char *rvalue,
-                                         void *data,
-                                         void *userdata) {
+int config_parse_wireguard_private_key_file(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_free_ char *path = NULL;
         Wireguard *w;
-        WireguardPeer *peer;
 
         assert(data);
-
         w = WIREGUARD(data);
-
         assert(w);
 
-        peer = wireguard_peer_new(w, section_line);
-        if (!peer)
+        if (isempty(rvalue)) {
+                w->private_key_file = mfree(w->private_key_file);
+                return 0;
+        }
+
+        path = strdup(rvalue);
+        if (!path)
                 return log_oom();
 
-        return parse_wireguard_key(unit,
-                                   filename,
-                                   line,
-                                   section,
-                                   section_line,
-                                   lvalue,
-                                   ltype,
-                                   rvalue,
-                                   data,
-                                   peer->preshared_key);
+        if (path_simplify_and_warn(path, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue) < 0)
+                return 0;
+
+        return free_and_replace(w->private_key_file, path);
 }
 
-int config_parse_wireguard_public_key(const char *unit,
-                                      const char *filename,
-                                      unsigned line,
-                                      const char *section,
-                                      unsigned section_line,
-                                      const char *lvalue,
-                                      int ltype,
-                                      const char *rvalue,
-                                      void *data,
-                                      void *userdata) {
+int config_parse_wireguard_preshared_key(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
         Wireguard *w;
-        WireguardPeer *peer;
+        int r;
 
         assert(data);
-
         w = WIREGUARD(data);
+        assert(w);
+
+        r = wireguard_peer_new_static(w, filename, section_line, &peer);
+        if (r < 0)
+                return r;
+
+        r = wireguard_decode_key_and_warn(rvalue, peer->preshared_key, unit, filename, line, lvalue);
+        if (r < 0)
+                return r;
+
+        TAKE_PTR(peer);
+        return 0;
+}
 
+int config_parse_wireguard_public_key(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
+        Wireguard *w;
+        int r;
+
+        assert(data);
+        w = WIREGUARD(data);
         assert(w);
 
-        peer = wireguard_peer_new(w, section_line);
-        if (!peer)
-                return log_oom();
+        r = wireguard_peer_new_static(w, filename, section_line, &peer);
+        if (r < 0)
+                return r;
 
-        return parse_wireguard_key(unit,
-                                   filename,
-                                   line,
-                                   section,
-                                   section_line,
-                                   lvalue,
-                                   ltype,
-                                   rvalue,
-                                   data,
-                                   peer->public_key);
+        r = wireguard_decode_key_and_warn(rvalue, peer->public_key, unit, filename, line, lvalue);
+        if (r < 0)
+                return r;
+
+        TAKE_PTR(peer);
+        return 0;
 }
 
-int config_parse_wireguard_allowed_ips(const char *unit,
-                                       const char *filename,
-                                       unsigned line,
-                                       const char *section,
-                                       unsigned section_line,
-                                       const char *lvalue,
-                                       int ltype,
-                                       const char *rvalue,
-                                       void *data,
-                                       void *userdata) {
+int config_parse_wireguard_allowed_ips(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
         union in_addr_union addr;
         unsigned char prefixlen;
         int r, family;
         Wireguard *w;
-        WireguardPeer *peer;
         WireguardIPmask *ipmask;
 
         assert(rvalue);
         assert(data);
 
         w = WIREGUARD(data);
+        assert(w);
 
-        peer = wireguard_peer_new(w, section_line);
-        if (!peer)
-                return log_oom();
+        r = wireguard_peer_new_static(w, filename, section_line, &peer);
+        if (r < 0)
+                return r;
 
         for (;;) {
                 _cleanup_free_ char *word = NULL;
@@ -569,14 +667,16 @@ int config_parse_wireguard_allowed_ips(const char *unit,
                 if (r == -ENOMEM)
                         return log_oom();
                 if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Failed to split allowed ips \"%s\" option: %m", rvalue);
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to split allowed ips \"%s\" option: %m", rvalue);
                         break;
                 }
 
                 r = in_addr_prefix_from_string_auto(word, &family, &addr, &prefixlen);
                 if (r < 0) {
-                        log_syntax(unit, LOG_ERR, filename, line, r, "Network address is invalid, ignoring assignment: %s", word);
-                        return 0;
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Network address is invalid, ignoring assignment: %s", word);
+                        continue;
                 }
 
                 ipmask = new(WireguardIPmask, 1);
@@ -592,48 +692,53 @@ int config_parse_wireguard_allowed_ips(const char *unit,
                 LIST_PREPEND(ipmasks, peer->ipmasks, ipmask);
         }
 
+        TAKE_PTR(peer);
         return 0;
 }
 
-int config_parse_wireguard_endpoint(const char *unit,
-                                    const char *filename,
-                                    unsigned line,
-                                    const char *section,
-                                    unsigned section_line,
-                                    const char *lvalue,
-                                    int ltype,
-                                    const char *rvalue,
-                                    void *data,
-                                    void *userdata) {
+int config_parse_wireguard_endpoint(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
+        const char *begin, *end;
         Wireguard *w;
-        WireguardPeer *peer;
         size_t len;
-        const char *begin, *end = NULL;
-        _cleanup_free_ char *host = NULL, *port = NULL;
-        _cleanup_(wireguard_endpoint_freep) WireguardEndpoint *endpoint = NULL;
+        int r;
 
         assert(data);
         assert(rvalue);
 
         w = WIREGUARD(data);
-
         assert(w);
 
-        peer = wireguard_peer_new(w, section_line);
-        if (!peer)
-                return log_oom();
+        r = wireguard_peer_new_static(w, filename, section_line, &peer);
+        if (r < 0)
+                return r;
 
         if (rvalue[0] == '[') {
                 begin = &rvalue[1];
                 end = strchr(rvalue, ']');
                 if (!end) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find matching brace of endpoint, ignoring assignment: %s", rvalue);
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Unable to find matching brace of endpoint, ignoring assignment: %s",
+                                   rvalue);
                         return 0;
                 }
                 len = end - begin;
                 ++end;
                 if (*end != ':' || !*(end + 1)) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find port of endpoint: %s", rvalue);
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Unable to find port of endpoint, ignoring assignment: %s",
+                                   rvalue);
                         return 0;
                 }
                 ++end;
@@ -641,71 +746,77 @@ int config_parse_wireguard_endpoint(const char *unit,
                 begin = rvalue;
                 end = strrchr(rvalue, ':');
                 if (!end || !*(end + 1)) {
-                        log_syntax(unit, LOG_ERR, filename, line, 0, "Unable to find port of endpoint: %s", rvalue);
+                        log_syntax(unit, LOG_ERR, filename, line, 0,
+                                   "Unable to find port of endpoint, ignoring assignment: %s",
+                                   rvalue);
                         return 0;
                 }
                 len = end - begin;
                 ++end;
         }
 
-        host = strndup(begin, len);
-        if (!host)
+        r = free_and_strndup(&peer->endpoint_host, begin, len);
+        if (r < 0)
                 return log_oom();
 
-        port = strdup(end);
-        if (!port)
+        r = free_and_strdup(&peer->endpoint_port, end);
+        if (r < 0)
                 return log_oom();
 
-        endpoint = new(WireguardEndpoint, 1);
-        if (!endpoint)
+        r = set_ensure_allocated(&w->peers_with_unresolved_endpoint, NULL);
+        if (r < 0)
                 return log_oom();
 
-        *endpoint = (WireguardEndpoint) {
-                .peer = TAKE_PTR(peer),
-                .host = TAKE_PTR(host),
-                .port = TAKE_PTR(port),
-                .netdev = data,
-        };
-        LIST_PREPEND(endpoints, w->unresolved_endpoints, TAKE_PTR(endpoint));
+        r = set_put(w->peers_with_unresolved_endpoint, peer);
+        if (r < 0)
+                return r;
 
+        TAKE_PTR(peer);
         return 0;
 }
 
-int config_parse_wireguard_keepalive(const char *unit,
-                                     const char *filename,
-                                     unsigned line,
-                                     const char *section,
-                                     unsigned section_line,
-                                     const char *lvalue,
-                                     int ltype,
-                                     const char *rvalue,
-                                     void *data,
-                                     void *userdata) {
-        int r;
+int config_parse_wireguard_keepalive(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        _cleanup_(wireguard_peer_free_or_set_invalidp) WireguardPeer *peer = NULL;
         uint16_t keepalive = 0;
         Wireguard *w;
-        WireguardPeer *peer;
+        int r;
 
         assert(rvalue);
         assert(data);
 
         w = WIREGUARD(data);
-
         assert(w);
 
-        peer = wireguard_peer_new(w, section_line);
-        if (!peer)
-                return log_oom();
+        r = wireguard_peer_new_static(w, filename, section_line, &peer);
+        if (r < 0)
+                return r;
 
         if (streq(rvalue, "off"))
                 keepalive = 0;
         else {
                 r = safe_atou16(rvalue, &keepalive);
-                if (r < 0)
-                        log_syntax(unit, LOG_ERR, filename, line, r, "The persistent keepalive interval must be 0-65535. Ignore assignment: %s", rvalue);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "The persistent keepalive interval must be 0-65535. Ignore assignment: %s",
+                                   rvalue);
+                        return 0;
+                }
         }
 
         peer->persistent_keepalive_interval = keepalive;
+
+        TAKE_PTR(peer);
         return 0;
 }
 
@@ -713,9 +824,7 @@ static void wireguard_init(NetDev *netdev) {
         Wireguard *w;
 
         assert(netdev);
-
         w = WIREGUARD(netdev);
-
         assert(w);
 
         w->flags = WGDEVICE_F_REPLACE_PEERS;
@@ -723,32 +832,97 @@ static void wireguard_init(NetDev *netdev) {
 
 static void wireguard_done(NetDev *netdev) {
         Wireguard *w;
-        WireguardPeer *peer;
-        WireguardIPmask *mask;
-        WireguardEndpoint *e;
 
         assert(netdev);
         w = WIREGUARD(netdev);
         assert(w);
 
-        while ((peer = w->peers)) {
-                LIST_REMOVE(peers, w->peers, peer);
-                while ((mask = peer->ipmasks)) {
-                        LIST_REMOVE(ipmasks, peer->ipmasks, mask);
-                        free(mask);
-                }
-                free(peer);
-        }
+        sd_event_source_unref(w->resolve_retry_event_source);
 
-        while ((e = w->unresolved_endpoints)) {
-                LIST_REMOVE(endpoints, w->unresolved_endpoints, e);
-                wireguard_endpoint_free(e);
-        }
+        free(w->private_key_file);
 
-        while ((e = w->failed_endpoints)) {
-                LIST_REMOVE(endpoints, w->failed_endpoints, e);
-                wireguard_endpoint_free(e);
-        }
+        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);
+}
+
+static int wireguard_read_private_key_file(Wireguard *w, bool fatal) {
+        _cleanup_free_ char *contents = NULL;
+        _cleanup_free_ void *key = NULL;
+        size_t size, key_len;
+        NetDev *netdev;
+        int level, r;
+
+        assert(w);
+
+        netdev = NETDEV(w);
+
+        if (!w->private_key_file)
+                return 0;
+
+        level = fatal ? LOG_ERR : LOG_INFO;
+
+        r = read_full_file(w->private_key_file, &contents, &size);
+        if (r < 0)
+                return log_netdev_full(netdev, level, r,
+                                       "Failed to read private key from '%s'%s: %m",
+                                       w->private_key_file, fatal ? "" : ", ignoring");
+
+        r = unbase64mem(contents, size, &key, &key_len);
+        if (r < 0)
+                return log_netdev_full(netdev, level, r,
+                                       "Failed to decode private key%s: %m",
+                                       fatal ? "" : ", ignoring");
+
+        if (key_len != WG_KEY_LEN)
+                return log_netdev_full(netdev, level, SYNTHETIC_ERRNO(EINVAL),
+                                       "Wireguard private key has invalid length (%zu bytes)%s: %m",
+                                       key_len, fatal ? "" : ", ignoring");
+
+        memcpy(w->private_key, key, WG_KEY_LEN);
+        return 0;
+}
+
+static int wireguard_peer_verify(WireguardPeer *peer) {
+        NetDev *netdev = NETDEV(peer->wireguard);
+
+        if (section_is_invalid(peer->section))
+                return -EINVAL;
+
+        if (eqzero(peer->public_key))
+                return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+                                              "%s: WireGuardPeer section without PublicKey= configured. "
+                                              "Ignoring [WireGuardPeer] section from line %u.",
+                                              peer->section->filename, peer->section->line);
+
+        return 0;
+}
+
+static int wireguard_verify(NetDev *netdev, const char *filename) {
+        WireguardPeer *peer, *peer_next;
+        Wireguard *w;
+        bool empty;
+        int r;
+
+        assert(netdev);
+        w = WIREGUARD(netdev);
+        assert(w);
+
+        empty = eqzero(w->private_key);
+        if (empty && !w->private_key_file)
+                return log_netdev_error_errno(netdev, SYNTHETIC_ERRNO(EINVAL),
+                                              "%s: Missing PrivateKey= or PrivateKeyFile=, ignoring.",
+                                              filename);
+
+        r = wireguard_read_private_key_file(w, empty);
+        if (r < 0 && empty)
+                return r;
+
+        LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers)
+                if (wireguard_peer_verify(peer) < 0)
+                        wireguard_peer_free(peer);
+
+        return 0;
 }
 
 const NetDevVTable wireguard_vtable = {
@@ -758,4 +932,5 @@ const NetDevVTable wireguard_vtable = {
         .init = wireguard_init,
         .done = wireguard_done,
         .create_type = NETDEV_CREATE_INDEPENDENT,
+        .config_verify = wireguard_verify,
 };