]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
charon-nm: Use an XFRM interface if available
authorTobias Brunner <tobias@strongswan.org>
Mon, 9 Jan 2023 14:19:43 +0000 (15:19 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 22 Feb 2023 12:43:17 +0000 (13:43 +0100)
This allows NM more freedom in regards to how it wants to use the passed
device.  In particular, if dnsmasq is used with NM as that binds to the
interface to send requests via VPN.

Installing the VIPs on lo avoids weird address removal/addition events
that happen for IPv6 on the physical interface (which would cause the VIP
to get incorrectly detected as non-VIP address and ignored during
deletion).

We could let NM install routes via XFRM interface, however, that causes
problems with e.g. the bypass-lan plugin (the throw routes in table 220
wouldn't have any effect).  We could let it install regular routes in
the main table, but determining the physical interface would be tricky
as the routes installed by NM, also in the main table, would conflict.

So instead we let the kernel-netlink interface install routes via XFRM
interface and to avoid routing the IKE traffic that way, we set a mark
on the IKE socket and exclude traffic with that mark from our routing
table.

conf/options/charon-nm.opt
src/charon-nm/charon-nm.c
src/charon-nm/nm/nm_service.c

index 6372934bd5ef0ad3f5018509b2c27487598fae94..623969512a534cf84b03bb1368a60b508e4bc662 100644 (file)
@@ -1,3 +1,6 @@
 charon-nm.ca_dir = <default>
        Directory from which to load CA certificates if no certificate is
        configured.
+
+charon-nm.mtu = 1400
+       MTU for XFRM interfaces created by the NM plugin.
index db09bec189ef3bf8c4c75ed402436265d762bd5b..9d0a860ef85ce1f3416af7c8999f33c13bd339cb 100644 (file)
@@ -195,6 +195,22 @@ int main(int argc, char *argv[])
        lib->settings->set_default_str(lib->settings, "charon-nm.port", "0");
        lib->settings->set_default_str(lib->settings, "charon-nm.port_nat_t", "0");
 
+       /* install VIPs on lo as NM might modify the physical interface (this seems
+        * to affect IPv6 in particular), it actually installs the VIPs on the
+        * passed device again, but since that happens after we require them for
+        * installing routes, we install them ourselves too */
+       lib->settings->set_default_str(lib->settings,
+                                                                  "charon-nm.install_virtual_ip_on", "lo");
+
+       /* install routes via XFRM interfaces, if we can use them */
+       lib->settings->set_default_str(lib->settings,
+                               "charon-nm.plugins.kernel-netlink.install_routes_xfrmi", "yes");
+       /* bypass IKE traffic from these routes in case traffic selectors conflict */
+       lib->settings->set_default_str(lib->settings,
+                               "charon-nm.plugins.socket-default.fwmark", "220");
+       lib->settings->set_default_str(lib->settings,
+                               "charon-nm.plugins.kernel-netlink.fwmark", "!220");
+
        DBG1(DBG_DMN, "Starting charon NetworkManager backend (strongSwan "VERSION")");
        if (lib->integrity)
        {
index cbac239f76f011b3b270a9a1e239a8554499f586..e4efa85460caa756ebf3baa9589c657f54e97344 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (C) 2017 Lubomir Rintel
- *
- * Copyright (C) 2013-2020 Tobias Brunner
+ * Copyright (C) 2013-2023 Tobias Brunner
  * Copyright (C) 2008-2009 Martin Willi
  *
  * This program is free software; you can redistribute it and/or modify it
  * for more details.
  */
 
+#include <stdio.h>
+#include <inttypes.h>
+#include <net/if.h>
+
 #include "nm_service.h"
 
 #include <daemon.h>
@@ -23,8 +26,9 @@
 #include <config/peer_cfg.h>
 #include <credentials/certificates/x509.h>
 #include <networking/tun_device.h>
+#include <plugins/kernel_netlink/kernel_netlink_xfrmi.h>
 
-#include <stdio.h>
+#define XFRMI_DEFAULT_MTU 1400
 
 /**
  * Private data of NMStrongswanPlugin
@@ -40,7 +44,13 @@ typedef struct {
        nm_creds_t *creds;
        /* attribute handler for DNS/NBNS server information */
        nm_handler_t *handler;
-       /* dummy TUN device */
+       /* manager for XFRM interfaces, if supported */
+       kernel_netlink_xfrmi_t *xfrmi_manager;
+       /* interface ID of XFRM interface */
+       uint32_t xfrmi_id;
+       /* name of XFRM interface if one is used */
+       char *xfrmi;
+       /* dummy TUN device if not using XFRM interface */
        tun_device_t *tun;
        /* name of the connection */
        char *name;
@@ -107,6 +117,24 @@ static GVariant* handler_to_variant(nm_handler_t *handler, char *variant_type,
        return g_variant_builder_end (&builder);
 }
 
+/**
+ * Destroy any allocated XFRM or TUN interface
+ */
+static void delete_interface(NMStrongswanPluginPrivate *priv)
+{
+       if (priv->xfrmi)
+       {
+               priv->xfrmi_manager->delete(priv->xfrmi_manager, priv->xfrmi);
+               free(priv->xfrmi);
+               priv->xfrmi = NULL;
+       }
+       if (priv->tun)
+       {
+               priv->tun->destroy(priv->tun);
+               priv->tun = NULL;
+       }
+}
+
 /**
  * Signal IP config to NM, set connection as established
  */
@@ -127,27 +155,53 @@ static void signal_ip_config(NMVpnServicePlugin *plugin,
 
        handler = priv->handler;
 
-       /* NM apparently requires to know the gateway */
+       /* NM apparently requires to know the gateway (it uses it to install a
+        * direct route via physical interface if conflicting routes are passed) */
        other = ike_sa->get_other_host(ike_sa);
        g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY,
                                                   host_to_variant(other));
 
        /* systemd-resolved requires a device to properly install DNS servers, but
-        * Netkey does not use one.  Passing the physical interface is not ideal,
+        * Netkey does not require one.  Passing the physical interface is not ideal,
         * as NM fiddles around with it and systemd-resolved likes a separate
-        * device. So we pass a dummy TUN device along for NM etc. to play with...
+        * device. So we pass either an XFRM interface or a dummy TUN device along
+        * for NM etc. to play with...
         */
-       DESTROY_IF(priv->tun);
-       priv->tun = tun_device_create(NULL);
-       if (priv->tun)
+       delete_interface(priv);
+       if (priv->xfrmi_manager && priv->xfrmi_id)
+       {
+               char name[IFNAMSIZ];
+               int mtu;
+
+               /* use the interface ID to get a unique name, fine if it's cut off */
+               snprintf(name, sizeof(name), "nm-xfrm-%" PRIu32, priv->xfrmi_id);
+               mtu = lib->settings->get_int(lib->settings, "charon-nm.mtu",
+                                                                        XFRMI_DEFAULT_MTU);
+
+               if (priv->xfrmi_manager->create(priv->xfrmi_manager, name,
+                                                                               priv->xfrmi_id, NULL, mtu))
+               {
+                       priv->xfrmi = strdup(name);
+               }
+       }
+       if (!priv->xfrmi)
+       {
+               priv->tun = tun_device_create(NULL);
+       }
+       if (priv->xfrmi)
+       {
+               g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV,
+                                                          g_variant_new_string (priv->xfrmi));
+       }
+       else if (priv->tun)
        {
                g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV,
                                                           g_variant_new_string (priv->tun->get_name(priv->tun)));
        }
        else
        {
-               DBG1(DBG_CFG, "failed to create dummy TUN device, might affect DNS "
-                        "server installation negatively");
+               DBG1(DBG_CFG, "failed to create XFRM or dummy TUN device, might affect "
+                        "DNS server installation negatively");
        }
 
        /* pass the first virtual IPs we got or use the physical IP */
@@ -191,18 +245,16 @@ static void signal_ip_config(NMVpnServicePlugin *plugin,
                                                           host_to_variant(vip4));
                g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX,
                                                           g_variant_new_uint32 (vip4->get_address(vip4).len * 8));
-
-               /* prevent NM from changing the default route. we set our own route in our
-                * own routing table
-                */
-               g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT,
-                                                          g_variant_new_boolean (TRUE));
-
                g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS,
                                                           handler_to_variant(handler, "au", INTERNAL_IP4_DNS));
-
                g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NBNS,
                                                           handler_to_variant(handler, "au", INTERNAL_IP4_NBNS));
+
+               /* prevent NM from changing the default route, as we set our own routes
+                * in a separate routing table
+                */
+               g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT,
+                                                          g_variant_new_boolean (TRUE));
        }
 
        if (vip6)
@@ -211,11 +263,12 @@ static void signal_ip_config(NMVpnServicePlugin *plugin,
                                                           host_to_variant(vip6));
                g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_PREFIX,
                                                           g_variant_new_uint32 (vip6->get_address(vip6).len * 8));
-               g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT,
-                                                          g_variant_new_boolean (TRUE));
                g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_DNS,
                                                           handler_to_variant(handler, "aay", INTERNAL_IP6_DNS));
                /* NM_VPN_PLUGIN_IP6_CONFIG_NBNS is not defined */
+
+               g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT,
+                                                          g_variant_new_boolean (TRUE));
        }
 
        ip4config = g_variant_builder_end (&ip4builder);
@@ -653,6 +706,11 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection,
                                                                                                NM_TYPE_SETTING_CONNECTION));
        vpn = NM_SETTING_VPN(nm_connection_get_setting(connection,
                                                                                                NM_TYPE_SETTING_VPN));
+       if (priv->xfrmi_manager)
+       {
+               /* allocate a random interface ID */
+               priv->xfrmi_id = random();
+       }
        if (priv->name)
        {
                free(priv->name);
@@ -1020,12 +1078,8 @@ static gboolean do_disconnect(gpointer plugin)
         * secrets from earlier connections) before we clear them in connect() */
        priv->creds->clear(priv->creds);
 
-       /* delete the dummy TUN device */
-       if (priv->tun)
-       {
-               priv->tun->destroy(priv->tun);
-               priv->tun = NULL;
-       }
+       /* delete any allocated interface */
+       delete_interface(priv);
        return FALSE;
 }
 
@@ -1057,8 +1111,7 @@ static void nm_strongswan_plugin_init(NMStrongswanPlugin *plugin)
        priv->listener.ike_reestablish_pre = _ike_reestablish_pre;
        priv->listener.ike_reestablish_post = _ike_reestablish_post;
        charon->bus->add_listener(charon->bus, &priv->listener);
-       priv->tun = NULL;
-       priv->name = NULL;
+       priv->xfrmi_manager = lib->get(lib, KERNEL_NETLINK_XFRMI_MANAGER);
 }
 
 /**
@@ -1071,11 +1124,7 @@ static void nm_strongswan_plugin_dispose(GObject *obj)
 
        plugin = NM_STRONGSWAN_PLUGIN(obj);
        priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(plugin);
-       if (priv->tun)
-       {
-               priv->tun->destroy(priv->tun);
-               priv->tun = NULL;
-       }
+       delete_interface(priv);
        G_OBJECT_CLASS (nm_strongswan_plugin_parent_class)->dispose (obj);
 }