diff --git a/conf/options/charon-nm.opt b/conf/options/charon-nm.opt index 6372934bd..623969512 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 db09bec18..9d0a860ef 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 cbac239f7..e4efa8546 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)); + 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. we set our own route in our - * own routing table + /* 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)); - - 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)); } 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); }