Skip to content

Commit 58f278f

Browse files
committed
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.
1 parent 0448650 commit 58f278f

File tree

3 files changed

+103
-35
lines changed

3 files changed

+103
-35
lines changed

conf/options/charon-nm.opt

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
charon-nm.ca_dir = <default>
22
Directory from which to load CA certificates if no certificate is
33
configured.
4+
5+
charon-nm.mtu = 1400
6+
MTU for XFRM interfaces created by the NM plugin.

src/charon-nm/charon-nm.c

+16
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,22 @@ int main(int argc, char *argv[])
195195
lib->settings->set_default_str(lib->settings, "charon-nm.port", "0");
196196
lib->settings->set_default_str(lib->settings, "charon-nm.port_nat_t", "0");
197197

198+
/* install VIPs on lo as NM might modify the physical interface (this seems
199+
* to affect IPv6 in particular), it actually installs the VIPs on the
200+
* passed device again, but since that happens after we require them for
201+
* installing routes, we install them ourselves too */
202+
lib->settings->set_default_str(lib->settings,
203+
"charon-nm.install_virtual_ip_on", "lo");
204+
205+
/* install routes via XFRM interfaces, if we can use them */
206+
lib->settings->set_default_str(lib->settings,
207+
"charon-nm.plugins.kernel-netlink.install_routes_xfrmi", "yes");
208+
/* bypass IKE traffic from these routes in case traffic selectors conflict */
209+
lib->settings->set_default_str(lib->settings,
210+
"charon-nm.plugins.socket-default.fwmark", "220");
211+
lib->settings->set_default_str(lib->settings,
212+
"charon-nm.plugins.kernel-netlink.fwmark", "!220");
213+
198214
DBG1(DBG_DMN, "Starting charon NetworkManager backend (strongSwan "VERSION")");
199215
if (lib->integrity)
200216
{

src/charon-nm/nm/nm_service.c

+84-35
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/*
22
* Copyright (C) 2017 Lubomir Rintel
3-
*
4-
* Copyright (C) 2013-2020 Tobias Brunner
3+
* Copyright (C) 2013-2023 Tobias Brunner
54
* Copyright (C) 2008-2009 Martin Willi
65
*
76
* This program is free software; you can redistribute it and/or modify it
@@ -15,6 +14,10 @@
1514
* for more details.
1615
*/
1716

17+
#include <stdio.h>
18+
#include <inttypes.h>
19+
#include <net/if.h>
20+
1821
#include "nm_service.h"
1922

2023
#include <daemon.h>
@@ -23,8 +26,9 @@
2326
#include <config/peer_cfg.h>
2427
#include <credentials/certificates/x509.h>
2528
#include <networking/tun_device.h>
29+
#include <plugins/kernel_netlink/kernel_netlink_xfrmi.h>
2630

27-
#include <stdio.h>
31+
#define XFRMI_DEFAULT_MTU 1400
2832

2933
/**
3034
* Private data of NMStrongswanPlugin
@@ -40,7 +44,13 @@ typedef struct {
4044
nm_creds_t *creds;
4145
/* attribute handler for DNS/NBNS server information */
4246
nm_handler_t *handler;
43-
/* dummy TUN device */
47+
/* manager for XFRM interfaces, if supported */
48+
kernel_netlink_xfrmi_t *xfrmi_manager;
49+
/* interface ID of XFRM interface */
50+
uint32_t xfrmi_id;
51+
/* name of XFRM interface if one is used */
52+
char *xfrmi;
53+
/* dummy TUN device if not using XFRM interface */
4454
tun_device_t *tun;
4555
/* name of the connection */
4656
char *name;
@@ -107,6 +117,24 @@ static GVariant* handler_to_variant(nm_handler_t *handler, char *variant_type,
107117
return g_variant_builder_end (&builder);
108118
}
109119

120+
/**
121+
* Destroy any allocated XFRM or TUN interface
122+
*/
123+
static void delete_interface(NMStrongswanPluginPrivate *priv)
124+
{
125+
if (priv->xfrmi)
126+
{
127+
priv->xfrmi_manager->delete(priv->xfrmi_manager, priv->xfrmi);
128+
free(priv->xfrmi);
129+
priv->xfrmi = NULL;
130+
}
131+
if (priv->tun)
132+
{
133+
priv->tun->destroy(priv->tun);
134+
priv->tun = NULL;
135+
}
136+
}
137+
110138
/**
111139
* Signal IP config to NM, set connection as established
112140
*/
@@ -127,27 +155,53 @@ static void signal_ip_config(NMVpnServicePlugin *plugin,
127155

128156
handler = priv->handler;
129157

130-
/* NM apparently requires to know the gateway */
158+
/* NM apparently requires to know the gateway (it uses it to install a
159+
* direct route via physical interface if conflicting routes are passed) */
131160
other = ike_sa->get_other_host(ike_sa);
132161
g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_EXT_GATEWAY,
133162
host_to_variant(other));
134163

135164
/* systemd-resolved requires a device to properly install DNS servers, but
136-
* Netkey does not use one. Passing the physical interface is not ideal,
165+
* Netkey does not require one. Passing the physical interface is not ideal,
137166
* as NM fiddles around with it and systemd-resolved likes a separate
138-
* device. So we pass a dummy TUN device along for NM etc. to play with...
167+
* device. So we pass either an XFRM interface or a dummy TUN device along
168+
* for NM etc. to play with...
139169
*/
140-
DESTROY_IF(priv->tun);
141-
priv->tun = tun_device_create(NULL);
142-
if (priv->tun)
170+
delete_interface(priv);
171+
if (priv->xfrmi_manager && priv->xfrmi_id)
172+
{
173+
char name[IFNAMSIZ];
174+
int mtu;
175+
176+
/* use the interface ID to get a unique name, fine if it's cut off */
177+
snprintf(name, sizeof(name), "nm-xfrm-%" PRIu32, priv->xfrmi_id);
178+
mtu = lib->settings->get_int(lib->settings, "charon-nm.mtu",
179+
XFRMI_DEFAULT_MTU);
180+
181+
if (priv->xfrmi_manager->create(priv->xfrmi_manager, name,
182+
priv->xfrmi_id, NULL, mtu))
183+
{
184+
priv->xfrmi = strdup(name);
185+
}
186+
}
187+
if (!priv->xfrmi)
188+
{
189+
priv->tun = tun_device_create(NULL);
190+
}
191+
if (priv->xfrmi)
192+
{
193+
g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV,
194+
g_variant_new_string (priv->xfrmi));
195+
}
196+
else if (priv->tun)
143197
{
144198
g_variant_builder_add (&builder, "{sv}", NM_VPN_PLUGIN_CONFIG_TUNDEV,
145199
g_variant_new_string (priv->tun->get_name(priv->tun)));
146200
}
147201
else
148202
{
149-
DBG1(DBG_CFG, "failed to create dummy TUN device, might affect DNS "
150-
"server installation negatively");
203+
DBG1(DBG_CFG, "failed to create XFRM or dummy TUN device, might affect "
204+
"DNS server installation negatively");
151205
}
152206

153207
/* pass the first virtual IPs we got or use the physical IP */
@@ -191,18 +245,16 @@ static void signal_ip_config(NMVpnServicePlugin *plugin,
191245
host_to_variant(vip4));
192246
g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_PREFIX,
193247
g_variant_new_uint32 (vip4->get_address(vip4).len * 8));
194-
195-
/* prevent NM from changing the default route. we set our own route in our
196-
* own routing table
197-
*/
198-
g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT,
199-
g_variant_new_boolean (TRUE));
200-
201248
g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_DNS,
202249
handler_to_variant(handler, "au", INTERNAL_IP4_DNS));
203-
204250
g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NBNS,
205251
handler_to_variant(handler, "au", INTERNAL_IP4_NBNS));
252+
253+
/* prevent NM from changing the default route, as we set our own routes
254+
* in a separate routing table
255+
*/
256+
g_variant_builder_add (&ip4builder, "{sv}", NM_VPN_PLUGIN_IP4_CONFIG_NEVER_DEFAULT,
257+
g_variant_new_boolean (TRUE));
206258
}
207259

208260
if (vip6)
@@ -211,11 +263,12 @@ static void signal_ip_config(NMVpnServicePlugin *plugin,
211263
host_to_variant(vip6));
212264
g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_PREFIX,
213265
g_variant_new_uint32 (vip6->get_address(vip6).len * 8));
214-
g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT,
215-
g_variant_new_boolean (TRUE));
216266
g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_DNS,
217267
handler_to_variant(handler, "aay", INTERNAL_IP6_DNS));
218268
/* NM_VPN_PLUGIN_IP6_CONFIG_NBNS is not defined */
269+
270+
g_variant_builder_add (&ip6builder, "{sv}", NM_VPN_PLUGIN_IP6_CONFIG_NEVER_DEFAULT,
271+
g_variant_new_boolean (TRUE));
219272
}
220273

221274
ip4config = g_variant_builder_end (&ip4builder);
@@ -653,6 +706,11 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection,
653706
NM_TYPE_SETTING_CONNECTION));
654707
vpn = NM_SETTING_VPN(nm_connection_get_setting(connection,
655708
NM_TYPE_SETTING_VPN));
709+
if (priv->xfrmi_manager)
710+
{
711+
/* allocate a random interface ID */
712+
priv->xfrmi_id = random();
713+
}
656714
if (priv->name)
657715
{
658716
free(priv->name);
@@ -1020,12 +1078,8 @@ static gboolean do_disconnect(gpointer plugin)
10201078
* secrets from earlier connections) before we clear them in connect() */
10211079
priv->creds->clear(priv->creds);
10221080

1023-
/* delete the dummy TUN device */
1024-
if (priv->tun)
1025-
{
1026-
priv->tun->destroy(priv->tun);
1027-
priv->tun = NULL;
1028-
}
1081+
/* delete any allocated interface */
1082+
delete_interface(priv);
10291083
return FALSE;
10301084
}
10311085

@@ -1057,8 +1111,7 @@ static void nm_strongswan_plugin_init(NMStrongswanPlugin *plugin)
10571111
priv->listener.ike_reestablish_pre = _ike_reestablish_pre;
10581112
priv->listener.ike_reestablish_post = _ike_reestablish_post;
10591113
charon->bus->add_listener(charon->bus, &priv->listener);
1060-
priv->tun = NULL;
1061-
priv->name = NULL;
1114+
priv->xfrmi_manager = lib->get(lib, KERNEL_NETLINK_XFRMI_MANAGER);
10621115
}
10631116

10641117
/**
@@ -1071,11 +1124,7 @@ static void nm_strongswan_plugin_dispose(GObject *obj)
10711124

10721125
plugin = NM_STRONGSWAN_PLUGIN(obj);
10731126
priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(plugin);
1074-
if (priv->tun)
1075-
{
1076-
priv->tun->destroy(priv->tun);
1077-
priv->tun = NULL;
1078-
}
1127+
delete_interface(priv);
10791128
G_OBJECT_CLASS (nm_strongswan_plugin_parent_class)->dispose (obj);
10801129
}
10811130

0 commit comments

Comments
 (0)