]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
network: wireguard: automatically configure routes to addresses specified in AllowedIPs=
authorYu Watanabe <watanabe.yu+github@gmail.com>
Mon, 29 Nov 2021 07:36:42 +0000 (16:36 +0900)
committerYu Watanabe <watanabe.yu+github@gmail.com>
Tue, 30 Nov 2021 19:00:11 +0000 (04:00 +0900)
Closes #14176.

man/systemd.netdev.xml
src/network/netdev/netdev-gperf.gperf
src/network/netdev/wireguard.c
src/network/netdev/wireguard.h
src/network/networkd-route.c
test/fuzz/fuzz-netdev-parser/directives.netdev

index bd493a8c53fc06774c3a296ba5ec421e856332c4..e4e7e611e7783db9c78fd01ac2feea1e78c5f136 100644 (file)
           <para>Sets a firewall mark on outgoing WireGuard packets from this interface. Takes a number between 1 and 4294967295.</para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term><varname>RouteTable=</varname></term>
+        <listitem>
+          <para>The table identifier for the routes to the addresses specified in the
+          <varname>AllowedIPs=</varname>. Takes the special value <literal>off</literal>, one of the
+          predefined names <literal>default</literal>, <literal>main</literal>, and
+          <literal>local</literal>, names defined in <varname>RouteTable=</varname> in
+          <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+          or a number in the range 1…4294967295. When <literal>off</literal> the routes to the
+          addresses specified in the <varname>AllowedIPs=</varname> setting will not be configured.
+          Defaults to <literal>main</literal>. This setting will be ignored when the same setting is
+          specified in the [WireGuardPeer] section.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>RouteMetric=</varname></term>
+        <listitem>
+          <para>The priority of the routes to the addresses specified in the
+          <varname>AllowedIPs=</varname>. Takes an integer in the range 0…4294967295. Defaults to 0
+          for IPv4 addresses, and 1024 for IPv6 addresses. This setting will be ignored when the same
+          setting is specified in the [WireGuardPeer] section.</para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
           Most users will not need this.</para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term><varname>RouteTable=</varname></term>
+        <listitem>
+          <para>The table identifier for the routes to the addresses specified in the
+          <varname>AllowedIPs=</varname>. Takes the special value <literal>off</literal>, one of the
+          predefined names <literal>default</literal>, <literal>main</literal>, and
+          <literal>local</literal>, names defined in <varname>RouteTable=</varname> in
+          <citerefentry><refentrytitle>networkd.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
+          or a number in the range 1…4294967295. Defaults to unset, and the value specified in the
+          same setting in the [WireGuard] section will be used.</para>
+        </listitem>
+      </varlistentry>
+      <varlistentry>
+        <term><varname>RouteMetric=</varname></term>
+        <listitem>
+          <para>The priority of the routes to the addresses specified in the
+          <varname>AllowedIPs=</varname>. Takes an integer in the range 0…4294967295. Defaults to
+          unset, and the value specified in the same setting in the [WireGuard] section will be used.
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 316592005f2838bb3b35f949a65e0e9234cfa89e..37a0d9fa5d55a528c9b56f8e663eff866fe6c706 100644 (file)
@@ -229,12 +229,16 @@ WireGuard.FwMark,                         config_parse_unsigned,
 WireGuard.ListenPort,                     config_parse_wireguard_listen_port,        0,                             offsetof(Wireguard, port)
 WireGuard.PrivateKey,                     config_parse_wireguard_private_key,        0,                             0
 WireGuard.PrivateKeyFile,                 config_parse_wireguard_private_key_file,   0,                             0
+WireGuard.RouteTable,                     config_parse_wireguard_route_table,        0,                             offsetof(Wireguard, route_table)
+WireGuard.RouteMetric,                    config_parse_wireguard_route_priority,     0,                             offsetof(Wireguard, route_priority)
 WireGuardPeer.AllowedIPs,                 config_parse_wireguard_allowed_ips,        0,                             0
 WireGuardPeer.Endpoint,                   config_parse_wireguard_endpoint,           0,                             0
 WireGuardPeer.PublicKey,                  config_parse_wireguard_peer_key,           0,                             0
 WireGuardPeer.PresharedKey,               config_parse_wireguard_peer_key,           0,                             0
 WireGuardPeer.PresharedKeyFile,           config_parse_wireguard_preshared_key_file, 0,                             0
 WireGuardPeer.PersistentKeepalive,        config_parse_wireguard_keepalive,          0,                             0
+WireGuardPeer.RouteTable,                 config_parse_wireguard_peer_route_table,   0,                             0
+WireGuardPeer.RouteMetric,                config_parse_wireguard_peer_route_priority,0,                             0
 Xfrm.InterfaceId,                         config_parse_uint32,                       0,                             offsetof(Xfrm, if_id)
 Xfrm.Independent,                         config_parse_bool,                         0,                             offsetof(Xfrm, independent)
 BatmanAdvanced.Aggregation,               config_parse_bool,                         0,                             offsetof(BatmanAdvanced, aggregation)
index 587e6db1c4709fcf34a4a814ae4c0756324c12f8..f254b05f86efbb724b42df21fb763780961353ba 100644 (file)
@@ -7,6 +7,7 @@
 #include <net/if.h>
 #include <netinet/in.h>
 #include <linux/if_arp.h>
+#include <linux/ipv6_route.h>
 
 #include "sd-resolve.h"
 
@@ -18,6 +19,8 @@
 #include "memory-util.h"
 #include "netlink-util.h"
 #include "networkd-manager.h"
+#include "networkd-route-util.h"
+#include "networkd-route.h"
 #include "networkd-util.h"
 #include "parse-util.h"
 #include "path-util.h"
@@ -827,6 +830,186 @@ int config_parse_wireguard_keepalive(
         return 0;
 }
 
+int config_parse_wireguard_route_table(
+                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) {
+
+        NetDev *netdev = userdata;
+        uint32_t *table = data;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+        assert(userdata);
+
+        if (isempty(rvalue)) {
+                *table = RT_TABLE_MAIN;
+                return 0;
+        }
+
+        if (streq(rvalue, "off")) {
+                *table = 0;
+                return 0;
+        }
+
+        r = manager_get_route_table_from_string(netdev->manager, rvalue, table);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse %s=, ignoring assignment: %s",
+                           lvalue, rvalue);
+                return 0;
+        }
+
+        return 0;
+}
+
+int config_parse_wireguard_peer_route_table(
+                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;
+        NetDev *netdev = userdata;
+        Wireguard *w;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(netdev);
+        assert(netdev->manager);
+
+        w = WIREGUARD(netdev);
+        assert(w);
+
+        r = wireguard_peer_new_static(w, filename, section_line, &peer);
+        if (r < 0)
+                return log_oom();
+
+        if (isempty(rvalue)) {
+                peer->route_table_set = false; /* Use the table specified in [WireGuard] section. */
+                TAKE_PTR(peer);
+                return 0;
+        }
+
+        if (streq(rvalue, "off")) {
+                peer->route_table = 0; /* Disabled. */
+                peer->route_table_set = true;
+                TAKE_PTR(peer);
+                return 0;
+        }
+
+        r = manager_get_route_table_from_string(netdev->manager, rvalue, &peer->route_table);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Failed to parse %s=, ignoring assignment: %s",
+                           lvalue, rvalue);
+                return 0;
+        }
+
+        peer->route_table_set = true;
+        TAKE_PTR(peer);
+        return 0;
+}
+
+int config_parse_wireguard_route_priority(
+                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) {
+
+        uint32_t *priority = data;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (isempty(rvalue)) {
+                *priority = 0;
+                return 0;
+        }
+
+        r = safe_atou32(rvalue, priority);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue);
+                return 0;
+        }
+
+        return 0;
+}
+
+int config_parse_wireguard_peer_route_priority(
+                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(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(userdata);
+
+        w = WIREGUARD(userdata);
+        assert(w);
+
+        r = wireguard_peer_new_static(w, filename, section_line, &peer);
+        if (r < 0)
+                return log_oom();
+
+        if (isempty(rvalue)) {
+                peer->route_priority_set = false; /* Use the priority specified in [WireGuard] section. */
+                TAKE_PTR(peer);
+                return 0;
+        }
+
+        r = safe_atou32(rvalue, &peer->route_priority);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r,
+                           "Could not parse route priority \"%s\", ignoring assignment: %m", rvalue);
+                return 0;
+        }
+
+        peer->route_priority_set = true;
+        TAKE_PTR(peer);
+        return 0;
+}
+
 static void wireguard_init(NetDev *netdev) {
         Wireguard *w;
 
@@ -835,6 +1018,7 @@ static void wireguard_init(NetDev *netdev) {
         assert(w);
 
         w->flags = WGDEVICE_F_REPLACE_PEERS;
+        w->route_table = RT_TABLE_MAIN;
 }
 
 static void wireguard_done(NetDev *netdev) {
@@ -852,6 +1036,8 @@ static void wireguard_done(NetDev *netdev) {
         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);
 }
 
 static int wireguard_read_key_file(const char *filename, uint8_t dest[static WG_KEY_LEN]) {
@@ -924,9 +1110,40 @@ static int wireguard_verify(NetDev *netdev, const char *filename) {
                                               "%s: Missing PrivateKey= or PrivateKeyFile=, "
                                               "Ignoring network device.", filename);
 
-        LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers)
-                if (wireguard_peer_verify(peer) < 0)
+        LIST_FOREACH_SAFE(peers, peer, peer_next, w->peers) {
+                WireguardIPmask *ipmask;
+
+                if (wireguard_peer_verify(peer) < 0) {
                         wireguard_peer_free(peer);
+                        continue;
+                }
+
+                if ((peer->route_table_set ? peer->route_table : w->route_table) == 0)
+                        continue;
+
+                LIST_FOREACH(ipmasks, ipmask, peer->ipmasks) {
+                        _cleanup_(route_freep) Route *route = NULL;
+
+                        r = route_new(&route);
+                        if (r < 0)
+                                return log_oom();
+
+                        route->family = ipmask->family;
+                        route->dst = ipmask->ip;
+                        route->dst_prefixlen = ipmask->cidr;
+                        route->scope = RT_SCOPE_UNIVERSE;
+                        route->protocol = RTPROT_STATIC;
+                        route->table = peer->route_table_set ? peer->route_table : w->route_table;
+                        route->priority = peer->route_priority_set ? peer->route_priority : w->route_priority;
+                        if (route->priority == 0 && route->family == AF_INET6)
+                                route->priority = IP6_RT_PRIO_USER;
+                        route->source = NETWORK_CONFIG_SOURCE_STATIC;
+
+                        r = set_ensure_consume(&w->routes, &route_hash_ops, TAKE_PTR(route));
+                        if (r < 0)
+                                return log_oom();
+                }
+        }
 
         return 0;
 }
index b9b5ae9871d6a9cf1c4681a073c7a2913fd3a21f..5d4b6da45ec278a6a106985ab052d2a2524219ca 100644 (file)
@@ -33,6 +33,11 @@ typedef struct WireguardPeer {
         char *endpoint_host;
         char *endpoint_port;
 
+        uint32_t route_table;
+        uint32_t route_priority;
+        bool route_table_set;
+        bool route_priority_set;
+
         LIST_HEAD(WireguardIPmask, ipmasks);
         LIST_FIELDS(struct WireguardPeer, peers);
 } WireguardPeer;
@@ -55,6 +60,10 @@ struct Wireguard {
 
         unsigned n_retries;
         sd_event_source *resolve_retry_event_source;
+
+        Set *routes;
+        uint32_t route_table;
+        uint32_t route_priority;
 };
 
 DEFINE_NETDEV_CAST(WIREGUARD, Wireguard);
@@ -68,3 +77,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key);
 CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_private_key_file);
 CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_preshared_key_file);
 CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_keepalive);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_route_table);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_peer_route_table);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_route_priority);
+CONFIG_PARSER_PROTOTYPE(config_parse_wireguard_peer_route_priority);
index 28ef058651ba23edd83861524e23cdab5e81959c..20d6aa47f6609f894ea52dc53a557224fc219c98 100644 (file)
@@ -19,6 +19,7 @@
 #include "string-util.h"
 #include "strv.h"
 #include "vrf.h"
+#include "wireguard.h"
 
 int route_new(Route **ret) {
         _cleanup_(route_freep) Route *route = NULL;
@@ -865,6 +866,28 @@ static bool route_by_kernel(const Route *route) {
         return false;
 }
 
+static void link_unmark_wireguard_routes(Link *link) {
+        Route *route, *existing;
+        NetDev *netdev;
+        Wireguard *w;
+
+        assert(link);
+
+        if (!streq_ptr(link->kind, "wireguard"))
+                return;
+
+        if (netdev_get(link->manager, link->ifname, &netdev) < 0)
+                return;
+
+        w = WIREGUARD(netdev);
+        if (!w)
+                return;
+
+        SET_FOREACH(route, w->routes)
+                if (route_get(NULL, link, route, &existing) >= 0)
+                        route_unmark(existing);
+}
+
 int link_drop_foreign_routes(Link *link) {
         Route *route;
         int k, r;
@@ -914,6 +937,8 @@ int link_drop_foreign_routes(Link *link) {
                                 route_unmark(existing);
         }
 
+        link_unmark_wireguard_routes(link);
+
         r = 0;
         SET_FOREACH(route, link->routes) {
                 if (!route_is_marked(route))
@@ -1342,6 +1367,36 @@ static int link_request_static_route(Link *link, Route *route) {
                                   &link->static_route_messages, static_route_handler, NULL);
 }
 
+static int link_request_wireguard_routes(Link *link, bool only_ipv4) {
+        NetDev *netdev;
+        Wireguard *w;
+        Route *route;
+        int r;
+
+        assert(link);
+
+        if (!streq_ptr(link->kind, "wireguard"))
+                return 0;
+
+        if (netdev_get(link->manager, link->ifname, &netdev) < 0)
+                return 0;
+
+        w = WIREGUARD(netdev);
+        if (!w)
+                return 0;
+
+        SET_FOREACH(route, w->routes) {
+                if (only_ipv4 && route->family != AF_INET)
+                        continue;
+
+                r = link_request_static_route(link, route);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 int link_request_static_routes(Link *link, bool only_ipv4) {
         Route *route;
         int r;
@@ -1363,6 +1418,10 @@ int link_request_static_routes(Link *link, bool only_ipv4) {
                         return r;
         }
 
+        r = link_request_wireguard_routes(link, only_ipv4);
+        if (r < 0)
+                return r;
+
         if (link->static_route_messages == 0) {
                 link->static_routes_configured = true;
                 link_check_ready(link);
index c7c3682eabd0318d8ffc7a20f9a43d18acfb54b1..e34d16af117c677846aff0f372ad8d56d03b4baf 100644 (file)
@@ -17,6 +17,8 @@ PrivateKey=
 PrivateKeyFile=
 FwMark=
 FirewallMark=
+RouteTable=
+RouteMetric=
 [MACVTAP]
 Mode=
 SourceMACAddress=
@@ -67,6 +69,8 @@ PresharedKeyFile=
 PersistentKeepalive=
 PublicKey=
 AllowedIPs=
+RouteTable=
+RouteMetric=
 [Tunnel]
 FooOverUDP=
 IPv6FlowLabel=