From: Tobias Brunner Date: Mon, 9 Jan 2023 14:19:43 +0000 (+0100) Subject: charon-nm: Use an XFRM interface if available X-Git-Tag: 5.9.10rc1~5^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=58f278f93239668691e907886726ba62b01f99d5;p=thirdparty%2Fstrongswan.git charon-nm: Use an XFRM interface if available 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. --- diff --git a/conf/options/charon-nm.opt b/conf/options/charon-nm.opt index 6372934bd5..623969512a 100644 --- a/conf/options/charon-nm.opt +++ b/conf/options/charon-nm.opt @@ -1,3 +1,6 @@ charon-nm.ca_dir = 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. diff --git a/src/charon-nm/charon-nm.c b/src/charon-nm/charon-nm.c index db09bec189..9d0a860ef8 100644 --- a/src/charon-nm/charon-nm.c +++ b/src/charon-nm/charon-nm.c @@ -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) { diff --git a/src/charon-nm/nm/nm_service.c b/src/charon-nm/nm/nm_service.c index cbac239f76..e4efa85460 100644 --- a/src/charon-nm/nm/nm_service.c +++ b/src/charon-nm/nm/nm_service.c @@ -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 @@ -15,6 +14,10 @@ * for more details. */ +#include +#include +#include + #include "nm_service.h" #include @@ -23,8 +26,9 @@ #include #include #include +#include -#include +#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); }