Merge branch 'nm-xfrmi'

Use XFRM interfaces instead of dummy TUN devices to avoid issues with
name resolution if supported by the kernel.

Closes strongswan/strongswan#1048
This commit is contained in:
Tobias Brunner 2023-02-22 13:44:10 +01:00
commit 292eb7893f
16 changed files with 780 additions and 273 deletions

View 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.

View File

@ -28,6 +28,16 @@ charon.plugins.kernel-netlink.hw_offload_feature_interface = lo
cannot be used to obtain the appropriate feature flag, this option can
be used to specify an alternative interface for offload feature detection.
charon.plugins.kernel-netlink.install_routes_xfrmi = no
Whether to install routes for SAs that reference XFRM interfaces.
Whether routes via XFRM interfaces are automatically installed for SAs that
reference such an interface via _if_id_. If the traffic selectors include
the IKE traffic to the peer, this requires special care (e.g. installing
bypass policies and/or routes, or setting a mark on the IKE socket and
excluding such packets from the configured routing table via _fwmark_
option).
charon.plugins.kernel-netlink.mss = 0
MSS to set on installed routes, 0 to disable.

View 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)
{

View 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
@ -15,6 +14,10 @@
* 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,21 +155,54 @@ 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...
*/
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 XFRM or dummy TUN device, might affect "
"DNS server installation negatively");
}
/* pass the first virtual IPs we got or use the physical IP */
enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE);
@ -184,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)
@ -204,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);
@ -646,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);
@ -655,11 +720,6 @@ static gboolean connect_(NMVpnServicePlugin *plugin, NMConnection *connection,
priv->name);
DBG4(DBG_CFG, "%s",
nm_setting_to_string(NM_SETTING(vpn)));
if (!priv->tun)
{
DBG1(DBG_CFG, "failed to create dummy TUN device, might affect DNS "
"server installation negatively");
}
ike.remote = (char*)nm_setting_vpn_get_data_item(vpn, "address");
if (!ike.remote || !*ike.remote)
{
@ -989,7 +1049,7 @@ static gboolean do_disconnect(gpointer plugin)
NMStrongswanPluginPrivate *priv = NM_STRONGSWAN_PLUGIN_GET_PRIVATE(plugin);
enumerator_t *enumerator;
ike_sa_t *ike_sa;
u_int id;
u_int id = 0;
/* our ike_sa pointer might be invalid, lookup sa */
enumerator = charon->controller->create_ike_sa_enumerator(
@ -999,20 +1059,27 @@ static gboolean do_disconnect(gpointer plugin)
if (priv->ike_sa == ike_sa)
{
id = ike_sa->get_unique_id(ike_sa);
enumerator->destroy(enumerator);
charon->controller->terminate_ike(charon->controller, id, FALSE,
controller_cb_empty, NULL, 0);
/* clear secrets as we are asked for new secrets (where we'd find
* the cached secrets from earlier connections) before we clear
* them in connect() */
priv->creds->clear(priv->creds);
return FALSE;
break;
}
}
enumerator->destroy(enumerator);
g_debug("Connection not found.");
if (id)
{
charon->controller->terminate_ike(charon->controller, id, FALSE,
controller_cb_empty, NULL, 0);
}
else
{
g_debug("Connection not found.");
}
/* clear secrets as we are asked for new secrets (where we'd find the cached
* secrets from earlier connections) before we clear them in connect() */
priv->creds->clear(priv->creds);
/* delete any allocated interface */
delete_interface(priv);
return FALSE;
}
@ -1044,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 = tun_device_create(NULL);
priv->name = NULL;
priv->xfrmi_manager = lib->get(lib, KERNEL_NETLINK_XFRMI_MANAGER);
}
/**
@ -1058,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);
}

View File

@ -18,7 +18,8 @@ libstrongswan_kernel_netlink_la_SOURCES = \
kernel_netlink_plugin.h kernel_netlink_plugin.c \
kernel_netlink_ipsec.h kernel_netlink_ipsec.c \
kernel_netlink_net.h kernel_netlink_net.c \
kernel_netlink_shared.h kernel_netlink_shared.c
kernel_netlink_shared.h kernel_netlink_shared.c \
kernel_netlink_xfrmi.h kernel_netlink_xfrmi.c
libstrongswan_kernel_netlink_la_LIBADD = $(DLLIB)

View File

@ -63,6 +63,7 @@
#include "kernel_netlink_ipsec.h"
#include "kernel_netlink_shared.h"
#include "kernel_netlink_xfrmi.h"
#include <daemon.h>
#include <utils/debug.h>
@ -337,6 +338,11 @@ struct private_kernel_netlink_ipsec_t {
*/
netlink_socket_t *socket_xfrm;
/**
* XFRM interface manager
*/
kernel_netlink_xfrmi_t *xfrmi;
/**
* Netlink xfrm socket to receive acquire and expire events
*/
@ -352,6 +358,11 @@ struct private_kernel_netlink_ipsec_t {
*/
bool install_routes;
/**
* Whether to install routes via XFRM interfaces
*/
bool install_routes_xfrmi;
/**
* Whether to set protocol and ports on selector installed with transport
* mode IPsec SAs
@ -2730,6 +2741,30 @@ static void policy_change_done(private_kernel_netlink_ipsec_t *this,
this->mutex->unlock(this->mutex);
}
/**
* Find an XFRM interface with the given ID
*/
static bool find_xfrmi(private_kernel_netlink_ipsec_t *this, uint32_t target,
char **if_name)
{
enumerator_t *enumerator;
char *name;
uint32_t if_id;
enumerator = this->xfrmi->create_enumerator(this->xfrmi);
while (enumerator->enumerate(enumerator, &name, &if_id, NULL, NULL))
{
if (if_id == target)
{
*if_name = strdup(name);
enumerator->destroy(enumerator);
return TRUE;
}
}
enumerator->destroy(enumerator);
return FALSE;
}
/**
* Install a route for the given policy if enabled and required
*/
@ -2759,9 +2794,15 @@ static void install_route(private_kernel_netlink_ipsec_t *this,
if (!ipsec->dst->is_anyaddr(ipsec->dst))
{
route->gateway = charon->kernel->get_nexthop(charon->kernel,
ipsec->dst, -1, ipsec->src,
&route->if_name);
/* if if_ids are used, install a route via XFRM interface if any,
* otherwise install the route via the interface we reach the peer */
if (!policy->if_id || !this->xfrmi ||
!find_xfrmi(this, policy->if_id, &route->if_name))
{
route->gateway = charon->kernel->get_nexthop(charon->kernel,
ipsec->dst, -1, ipsec->src,
&route->if_name);
}
}
else
{ /* for shunt policies */
@ -3000,12 +3041,12 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this,
* - this is an outbound policy (to just get one for each child)
* - routing is not disabled via strongswan.conf
* - the selector is not for a specific protocol/port
* - no XFRM interface ID is configured
* - routes via XFRM interfaces are enabled or no interface ID is configured
* - we are in tunnel/BEET mode or install a bypass policy
*/
if (policy->direction == POLICY_OUT && this->install_routes &&
!policy->sel.proto && !policy->sel.dport && !policy->sel.sport &&
!policy->if_id)
(this->install_routes_xfrmi || !policy->if_id))
{
if (mapping->type == POLICY_PASS ||
(mapping->type == POLICY_IPSEC && ipsec->cfg.mode != MODE_TRANSPORT))
@ -3949,6 +3990,11 @@ METHOD(kernel_ipsec_t, destroy, void,
DESTROY_IF(this->socket_link_events);
DESTROY_IF(this->socket_xfrm_events);
array_destroy_function(this->bypass, remove_port_bypass, this);
if (this->xfrmi)
{
lib->set(lib, KERNEL_NETLINK_XFRMI_MANAGER, NULL);
kernel_netlink_xfrmi_destroy(this->xfrmi);
}
DESTROY_IF(this->socket_xfrm);
enumerator = this->policies->create_enumerator(this->policies);
while (enumerator->enumerate(enumerator, NULL, &policy))
@ -4137,9 +4183,13 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create()
.get_priority = dlsym(RTLD_DEFAULT,
"kernel_netlink_get_priority_custom"),
.policy_update = lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-netlink.policy_update", FALSE, lib->ns),
"%s.plugins.kernel-netlink.policy_update",
FALSE, lib->ns),
.install_routes = lib->settings->get_bool(lib->settings,
"%s.install_routes", TRUE, lib->ns),
"%s.install_routes", TRUE, lib->ns),
.install_routes_xfrmi = lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-netlink.install_routes_xfrmi",
FALSE, lib->ns),
.proto_port_transport = lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-netlink.set_proto_port_transport_sa",
FALSE, lib->ns),
@ -4188,5 +4238,11 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create()
return NULL;
}
}
this->xfrmi = kernel_netlink_xfrmi_create(TRUE);
if (this->xfrmi)
{
lib->set(lib, KERNEL_NETLINK_XFRMI_MANAGER, this->xfrmi);
}
return &this->public;
}

View File

@ -0,0 +1,445 @@
/*
* Copyright (C) 2019-2023 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
#include <net/if.h>
#include "kernel_netlink_xfrmi.h"
#include "kernel_netlink_shared.h"
#ifndef IFLA_XFRM_MAX
enum {
IFLA_XFRM_UNSPEC,
IFLA_XFRM_LINK,
IFLA_XFRM_IF_ID,
__IFLA_XFRM_MAX
};
#define IFLA_XFRM_MAX (__IFLA_XFRM_MAX - 1)
#endif
typedef struct private_kernel_netlink_xfrmi_t private_kernel_netlink_xfrmi_t;
/**
* Private data
*/
struct private_kernel_netlink_xfrmi_t {
/**
* Public interface
*/
kernel_netlink_xfrmi_t public;
/**
* Netlink socket
*/
netlink_socket_t *socket;
};
/**
* "up" the interface with the given name
*/
static bool interface_up(private_kernel_netlink_xfrmi_t *this, char *name)
{
netlink_buf_t request;
struct nlmsghdr *hdr;
struct ifinfomsg *msg;
memset(&request, 0, sizeof(request));
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
hdr->nlmsg_type = RTM_SETLINK;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
msg = NLMSG_DATA(hdr);
msg->ifi_family = AF_UNSPEC;
msg->ifi_change |= IFF_UP;
msg->ifi_flags |= IFF_UP;
netlink_add_attribute(hdr, IFLA_IFNAME, chunk_from_str(name),
sizeof(request));
if (this->socket->send_ack(this->socket, hdr) != SUCCESS)
{
DBG1(DBG_KNL, "failed to bring up XFRM interface '%s'", name);
return FALSE;
}
return TRUE;
}
METHOD(kernel_netlink_xfrmi_t, create, bool,
private_kernel_netlink_xfrmi_t *this, char *name, uint32_t if_id,
char *phys, uint32_t mtu)
{
netlink_buf_t request;
struct nlmsghdr *hdr;
struct ifinfomsg *msg;
struct rtattr *linkinfo, *info_data;
uint32_t ifindex = 0;
if (phys)
{
ifindex = if_nametoindex(phys);
if (!ifindex)
{
DBG1(DBG_KNL, "physical interface '%s' not found", phys);
return FALSE;
}
}
memset(&request, 0, sizeof(request));
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
hdr->nlmsg_type = RTM_NEWLINK;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
msg = NLMSG_DATA(hdr);
msg->ifi_family = AF_UNSPEC;
netlink_add_attribute(hdr, IFLA_IFNAME, chunk_from_str(name),
sizeof(request));
if (mtu)
{
netlink_add_attribute(hdr, IFLA_MTU, chunk_from_thing(mtu),
sizeof(request));
}
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
sizeof(request));
info_data = netlink_nested_start(hdr, sizeof(request), IFLA_INFO_DATA);
netlink_add_attribute(hdr, IFLA_XFRM_IF_ID, chunk_from_thing(if_id),
sizeof(request));
if (ifindex)
{
netlink_add_attribute(hdr, IFLA_XFRM_LINK, chunk_from_thing(ifindex),
sizeof(request));
}
netlink_nested_end(hdr, info_data);
netlink_nested_end(hdr, linkinfo);
switch (this->socket->send_ack(this->socket, hdr))
{
case SUCCESS:
return interface_up(this, name);
case ALREADY_DONE:
DBG1(DBG_KNL, "XFRM interface '%s' already exists", name);
break;
default:
DBG1(DBG_KNL, "failed to create XFRM interface '%s'", name);
break;
}
return FALSE;
}
/** enumerator over XFRM interfaces */
typedef struct {
/** public interface */
enumerator_t public;
/** message from the kernel */
struct nlmsghdr *msg;
/** current message from the kernel */
struct nlmsghdr *current;
/** remaining length */
size_t len;
/** current physical interface (if any) */
char phys[IFNAMSIZ];
} interface_enumerator_t;
METHOD(enumerator_t, destroy_enumerator, void,
interface_enumerator_t *this)
{
free(this->msg);
free(this);
}
/**
* Parse attributes nested in IFLA_INFO_DATA
*/
static void parse_info_data(struct rtattr *rta, size_t rtasize, bool *has_phys,
char *phys, uint32_t *if_id)
{
uint32_t ifindex;
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
case IFLA_XFRM_IF_ID:
if (RTA_PAYLOAD(rta) == sizeof(*if_id))
{
*if_id = *(uint32_t*)RTA_DATA(rta);
}
break;
case IFLA_XFRM_LINK:
if (RTA_PAYLOAD(rta) == sizeof(ifindex))
{
ifindex = *(uint32_t*)RTA_DATA(rta);
if (ifindex)
{
if_indextoname(ifindex, phys);
*has_phys = TRUE;
}
}
break;
default:
break;
}
rta = RTA_NEXT(rta, rtasize);
}
}
/**
* Parse attributes nested in IFLA_LINKINFO
*/
static void parse_linkinfo(struct rtattr *rta, size_t rtasize, bool *type_match,
bool *has_phys, char *phys, uint32_t *if_id)
{
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
case IFLA_INFO_KIND:
*type_match = streq("xfrm", RTA_DATA(rta));
break;
case IFLA_INFO_DATA:
parse_info_data(RTA_DATA(rta), RTA_PAYLOAD(rta), has_phys,
phys, if_id);
break;
default:
break;
}
rta = RTA_NEXT(rta, rtasize);
}
}
METHOD(enumerator_t, enumerate, bool,
interface_enumerator_t *this, va_list args)
{
char **name;
uint32_t *if_id, *mtu;
char **phys;
VA_ARGS_VGET(args, name, if_id, phys, mtu);
if (!this->current)
{
this->current = this->msg;
}
else
{
this->current = NLMSG_NEXT(this->current, this->len);
}
while (NLMSG_OK(this->current, this->len))
{
switch (this->current->nlmsg_type)
{
case NLMSG_DONE:
break;
case RTM_NEWLINK:
{
struct ifinfomsg *msg = NLMSG_DATA(this->current);
struct rtattr *rta = IFLA_RTA(msg);
size_t rtasize = IFLA_PAYLOAD(this->current);
bool type_match = FALSE, has_phys = FALSE;
*name = NULL;
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
case IFLA_IFNAME:
*name = RTA_DATA(rta);
break;
case IFLA_MTU:
if (mtu && RTA_PAYLOAD(rta) == sizeof(*mtu))
{
*mtu = *(uint32_t*)RTA_DATA(rta);
}
break;
case IFLA_LINKINFO:
parse_linkinfo(RTA_DATA(rta), RTA_PAYLOAD(rta),
&type_match, &has_phys, this->phys,
if_id);
break;
default:
break;
}
rta = RTA_NEXT(rta, rtasize);
}
if (*name && type_match)
{
if (phys)
{
*phys = has_phys ? this->phys : NULL;
}
return TRUE;
}
/* fall-through */
}
default:
this->current = NLMSG_NEXT(this->current, this->len);
continue;
}
break;
}
return FALSE;
}
METHOD(kernel_netlink_xfrmi_t, create_enumerator, enumerator_t*,
private_kernel_netlink_xfrmi_t *this)
{
netlink_buf_t request;
struct nlmsghdr *hdr, *out;
struct ifinfomsg *msg;
struct rtattr *linkinfo;
size_t len;
interface_enumerator_t *enumerator;
memset(&request, 0, sizeof(request));
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETLINK;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
msg = NLMSG_DATA(hdr);
msg->ifi_family = AF_UNSPEC;
/* if the kernel doesn't know the type we set here, it will just return all
* interfaces, so we filter the type ourselves too in the callback */
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
sizeof(request));
netlink_nested_end(hdr, linkinfo);
if (this->socket->send(this->socket, hdr, &out, &len) != SUCCESS)
{
DBG2(DBG_KNL, "enumerating XFRM interfaces failed");
return enumerator_create_empty();
}
INIT(enumerator,
.public = {
.enumerate = enumerator_enumerate_default,
.venumerate = _enumerate,
.destroy = _destroy_enumerator,
},
.msg = out,
.len = len,
);
return &enumerator->public;
}
METHOD(kernel_netlink_xfrmi_t, delete, bool,
private_kernel_netlink_xfrmi_t *this, char *name)
{
netlink_buf_t request;
struct nlmsghdr *hdr;
struct ifinfomsg *msg;
struct rtattr *linkinfo;
memset(&request, 0, sizeof(request));
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
hdr->nlmsg_type = RTM_DELLINK;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
msg = NLMSG_DATA(hdr);
msg->ifi_family = AF_UNSPEC;
netlink_add_attribute(hdr, IFLA_IFNAME, chunk_from_str(name),
sizeof(request));
/* the type doesn't seem to matter, but let's still set it */
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
sizeof(request));
netlink_nested_end(hdr, linkinfo);
switch (this->socket->send_ack(this->socket, hdr))
{
case SUCCESS:
return TRUE;
case NOT_FOUND:
DBG1(DBG_KNL, "XFRM interface '%s' not found to delete", name);
break;
default:
DBG1(DBG_KNL, "failed to delete XFRM interface '%s'", name);
break;
}
return FALSE;
}
void kernel_netlink_xfrmi_destroy(kernel_netlink_xfrmi_t *pub)
{
private_kernel_netlink_xfrmi_t *this = (private_kernel_netlink_xfrmi_t*)pub;
this->socket->destroy(this->socket);
free(this);
}
/*
* Described in header
*/
kernel_netlink_xfrmi_t *kernel_netlink_xfrmi_create(bool test)
{
private_kernel_netlink_xfrmi_t *this;
char name[IFNAMSIZ] = {};
uint32_t if_id;
INIT(this,
.public = {
.create = _create,
.create_enumerator = _create_enumerator,
.delete = _delete,
},
.socket = netlink_socket_create(NETLINK_ROUTE, NULL, FALSE),
);
if (!this->socket)
{
free(this);
return NULL;
}
if (test)
{
/* try to create an XFRM interface to see if the kernel supports it, use
* a random ID and name for the test to avoid conflicts */
if_id = random();
snprintf(name, sizeof(name), "xfrmi-test-%u", if_id);
if (!create(this, name, if_id, NULL, 0))
{
kernel_netlink_xfrmi_destroy(&this->public);
return NULL;
}
DBG2(DBG_KNL, "XFRM interfaces supported by kernel");
delete(this, name);
}
return &this->public;
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2022-2023 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*/
/**
* @defgroup kernel_netlink_xfrmi kernel_netlink_xfrmi
* @{ @ingroup kernel_netlink
*/
#ifndef KERNEL_NETLINK_XFRMI_H_
#define KERNEL_NETLINK_XFRMI_H_
#include <library.h>
#define KERNEL_NETLINK_XFRMI_MANAGER "kernel-netlink-xfrmi"
typedef struct kernel_netlink_xfrmi_t kernel_netlink_xfrmi_t;
/**
* Simple manager for XFRM interfaces. An instance can be retrieved via
* lib::get() under the key "kernel-netlink-xfrmi" if the kernel-netlink plugin
* is loaded and XFRM interfaces are supported by the kernel.
*/
struct kernel_netlink_xfrmi_t {
/**
* Creates an XFRM interface with the given name, interface ID and
* optional underlying physical interface and MTU.
*
* @param name name of the XFRM interface
* @param if_id interface ID (has to match SAs/policies)
* @param phys name of the underlying physical interface (optional)
* @param mtu MTU of the interface (optional, 0 for default)
* @return TRUE if interface was successfully created
*/
bool (*create)(kernel_netlink_xfrmi_t *this, char *name, uint32_t if_id,
char *phys, uint32_t mtu);
/**
* Enumerate existing XFRM interfaces.
*
* @return enumerator over (char *name, uint32_t if_id,
* char *phys, u_int mtu)
*/
enumerator_t *(*create_enumerator)(kernel_netlink_xfrmi_t *this);
/**
* Deletes the XFRM interface with the given name.
*
* @note This deletes any type of interface with the given name.
*
* @param name name of the XFRM interface
* @return TRUE if interface was successfully deleted
*/
bool (*delete)(kernel_netlink_xfrmi_t *this, char *name);
};
/**
* Create the manager.
*
* @param test test if XFRM interfaces can be created (requires CAP_NET_ADMIN)
* @return kernel_netlink_xfrmi_t instance, or NULL if test failed
*/
kernel_netlink_xfrmi_t *kernel_netlink_xfrmi_create(bool test);
/**
* Destroy the given manager. Not a method in the interface above to prevent
* users from destroying the manager.
*/
void kernel_netlink_xfrmi_destroy(kernel_netlink_xfrmi_t *this);
#endif /** KERNEL_NETLINK_XFRMI_H_ @}*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 Tobias Brunner
* Copyright (C) 2019-2023 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
@ -19,222 +19,46 @@
#include <getopt.h>
#include <errno.h>
#include <net/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "kernel_netlink_shared.h"
#ifndef IFLA_XFRM_MAX
enum {
IFLA_XFRM_UNSPEC,
IFLA_XFRM_LINK,
IFLA_XFRM_IF_ID,
__IFLA_XFRM_MAX
};
#define IFLA_XFRM_MAX (__IFLA_XFRM_MAX - 1)
#endif
#include "kernel_netlink_xfrmi.h"
/**
* Create an XFRM interface with the given ID and underlying interface
* Default MTU
*/
static int add_xfrm_interface(char *name, uint32_t xfrm_id, uint32_t ifindex)
{
netlink_buf_t request;
struct nlmsghdr *hdr;
struct ifinfomsg *msg;
struct rtattr *linkinfo, *info_data;
netlink_socket_t *socket;
int status = 1;
socket = netlink_socket_create(NETLINK_ROUTE, NULL, FALSE);
if (!socket)
{
return 1;
}
memset(&request, 0, sizeof(request));
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
hdr->nlmsg_type = RTM_NEWLINK;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
msg = NLMSG_DATA(hdr);
msg->ifi_family = AF_UNSPEC;
netlink_add_attribute(hdr, IFLA_IFNAME, chunk_from_str(name),
sizeof(request));
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
sizeof(request));
info_data = netlink_nested_start(hdr, sizeof(request), IFLA_INFO_DATA);
netlink_add_attribute(hdr, IFLA_XFRM_IF_ID, chunk_from_thing(xfrm_id),
sizeof(request));
netlink_add_attribute(hdr, IFLA_XFRM_LINK, chunk_from_thing(ifindex),
sizeof(request));
netlink_nested_end(hdr, info_data);
netlink_nested_end(hdr, linkinfo);
switch (socket->send_ack(socket, hdr))
{
case SUCCESS:
status = 0;
break;
case ALREADY_DONE:
fprintf(stderr, "XFRM interface already exists\n");
break;
default:
fprintf(stderr, "failed to create XFRM interface\n");
break;
}
socket->destroy(socket);
return status;
}
#define XFRMI_DEFAULT_MTU 1400
/**
* Parse attributes nested in IFLA_INFO_DATA
* Manager for XFRM interfaces
*/
static void parse_info_data(struct rtattr *rta, size_t rtasize, char *phys,
uint32_t *if_id)
{
uint32_t ifindex;
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
case IFLA_XFRM_IF_ID:
if (RTA_PAYLOAD(rta) == sizeof(*if_id))
{
*if_id = *(uint32_t*)RTA_DATA(rta);
}
break;
case IFLA_XFRM_LINK:
if (RTA_PAYLOAD(rta) == sizeof(ifindex))
{
ifindex = *(uint32_t*)RTA_DATA(rta);
if_indextoname(ifindex, phys);
}
break;
default:
break;
}
rta = RTA_NEXT(rta, rtasize);
}
}
static kernel_netlink_xfrmi_t *manager;
/**
* Parse attributes nested in IFLA_LINKINFO
* Destroy the allocated manager
*/
static void parse_linkinfo(struct rtattr *rta, size_t rtasize, char *phys,
uint32_t *if_id)
static void destroy_manager()
{
while (RTA_OK(rta, rtasize))
if (manager)
{
switch (rta->rta_type)
{
case IFLA_INFO_DATA:
parse_info_data(RTA_DATA(rta), RTA_PAYLOAD(rta), phys, if_id);
break;
default:
break;
}
rta = RTA_NEXT(rta, rtasize);
kernel_netlink_xfrmi_destroy(manager);
}
}
/**
* List all installed XFRM interfaces
*/
static int list_xfrm_interfaces()
static void list_xfrm_interfaces(kernel_netlink_xfrmi_t *manager)
{
netlink_buf_t request;
struct nlmsghdr *hdr, *out, *current;
struct ifinfomsg *msg;
struct rtattr *linkinfo;
netlink_socket_t *socket;
size_t len;
int status = 0;
enumerator_t *enumerator;
char *name, *dev;
uint32_t xfrm_id, mtu;
socket = netlink_socket_create(NETLINK_ROUTE, NULL, FALSE);
if (!socket)
enumerator = manager->create_enumerator(manager);
while (enumerator->enumerate(enumerator, &name, &xfrm_id, &dev, &mtu))
{
return 1;
printf("%2u: %-16s dev %-12s if_id 0x%.8x [%10u] mtu %u\n",
if_nametoindex(name), name, dev ?: "-", xfrm_id, xfrm_id, mtu);
}
memset(&request, 0, sizeof(request));
hdr = &request.hdr;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETLINK;
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
msg = NLMSG_DATA(hdr);
msg->ifi_family = AF_UNSPEC;
linkinfo = netlink_nested_start(hdr, sizeof(request), IFLA_LINKINFO);
netlink_add_attribute(hdr, IFLA_INFO_KIND, chunk_from_str("xfrm"),
sizeof(request));
netlink_nested_end(hdr, linkinfo);
if (socket->send(socket, hdr, &out, &len) != SUCCESS)
{
return FAILED;
}
current = out;
while (NLMSG_OK(current, len))
{
switch (current->nlmsg_type)
{
case NLMSG_DONE:
break;
case RTM_NEWLINK:
msg = NLMSG_DATA(current);
struct rtattr *rta = IFLA_RTA(msg);
size_t rtasize = IFLA_PAYLOAD(current);
char *name = NULL, phys[IF_NAMESIZE] = {};
uint32_t if_id = 0;
while (RTA_OK(rta, rtasize))
{
switch (rta->rta_type)
{
case IFLA_IFNAME:
name = RTA_DATA(rta);
break;
case IFLA_LINKINFO:
parse_linkinfo(RTA_DATA(rta), RTA_PAYLOAD(rta),
phys, &if_id);
break;
default:
break;
}
rta = RTA_NEXT(rta, rtasize);
}
if (name)
{
printf("%2u: %-16s dev %-8s if_id 0x%.8x [%u]\n",
msg->ifi_index, name, phys, if_id, if_id);
}
/* fall through */
default:
current = NLMSG_NEXT(current, len);
continue;
}
break;
}
free(out);
socket->destroy(socket);
return status;
enumerator->destroy(enumerator);
}
static void usage(FILE *out, char *name)
@ -247,19 +71,22 @@ static void usage(FILE *out, char *name)
fprintf(out, " -l, --list list XFRM interfaces.\n");
fprintf(out, " -n, --name=NAME name of the XFRM interface.\n");
fprintf(out, " -i, --id=ID optional numeric XFRM ID.\n");
fprintf(out, " -d, --dev=DEVICE underlying physical interface.\n");
fprintf(out, " -d, --dev=DEVICE optional underlying physical interface.\n");
fprintf(out, " -m, --mtu=MTU optional MTU, default: 1400 (use 0 for kernel default).\n");
fprintf(out, "\n");
}
int main(int argc, char *argv[])
{
char *name = NULL, *dev = NULL, *end;
uint32_t xfrm_id = 0;
u_int ifindex;
uint32_t xfrm_id = 0, mtu = XFRMI_DEFAULT_MTU;
library_init(NULL, "xfrmi");
atexit(library_deinit);
manager = kernel_netlink_xfrmi_create(FALSE);
atexit(destroy_manager);
while (true)
{
struct option long_opts[] = {
@ -269,9 +96,10 @@ int main(int argc, char *argv[])
{"name", required_argument, NULL, 'n' },
{"id", required_argument, NULL, 'i' },
{"dev", required_argument, NULL, 'd' },
{"mtu", required_argument, NULL, 'm' },
{0,0,0,0 },
};
switch (getopt_long(argc, argv, "hvln:i:d:", long_opts, NULL))
switch (getopt_long(argc, argv, "hvln:i:d:m:", long_opts, NULL))
{
case EOF:
break;
@ -279,7 +107,7 @@ int main(int argc, char *argv[])
usage(stdout, argv[0]);
return 0;
case 'l':
list_xfrm_interfaces();
list_xfrm_interfaces(manager);
return 0;
case 'v':
dbg_default_set_level(atoi(optarg));
@ -300,24 +128,26 @@ int main(int argc, char *argv[])
case 'd':
dev = optarg;
continue;
case 'm':
errno = 0;
mtu = strtoul(optarg, &end, 0);
if (errno || *end)
{
fprintf(stderr, "invalid MTU: %s\n",
errno ? strerror(errno) : end);
return 1;
}
continue;
default:
usage(stderr, argv[0]);
return 1;
}
break;
}
if (!name || !dev)
if (!name)
{
fprintf(stderr, "please specify a name and a physical interface\n");
fprintf(stderr, "please specify a name\n");
return 1;
}
ifindex = if_nametoindex(dev);
if (!ifindex)
{
fprintf(stderr, "physical interface %s not found\n", dev);
return 1;
}
return add_xfrm_interface(name, xfrm_id, ifindex);
return !manager->create(manager, name, xfrm_id, dev, mtu);
}

View File

@ -27,10 +27,10 @@ def handle_interfaces(ike_sa, up):
if up:
logger.info("add XFRM interfaces %s and %s", ifname_in, ifname_out)
subprocess.call(["/usr/local/libexec/ipsec/xfrmi", "-n", ifname_out,
"-i", str(if_id_out), "-d", "eth0"])
subprocess.call(["/usr/local/libexec/ipsec/xfrmi", "-n", ifname_in,
"-i", str(if_id_in), "-d", "eth0"])
subprocess.call(["ip", "link", "add", ifname_out, "type", "xfrm",
"if_id", str(if_id_out), "dev", "eth0"])
subprocess.call(["ip", "link", "add", ifname_in, "type", "xfrm",
"if_id", str(if_id_in), "dev", "eth0"])
subprocess.call(["ip", "link", "set", ifname_out, "up"])
subprocess.call(["ip", "link", "set", ifname_in, "up"])
subprocess.call(["iptables", "-A", "FORWARD", "-o", ifname_out,

View File

@ -1,7 +1,7 @@
moon::iptables-restore < /etc/iptables.rules
sun::iptables-restore < /etc/iptables.rules
moon::/usr/local/libexec/ipsec/xfrmi -n xfrm-moon-out -d eth0 -i 1337
moon::/usr/local/libexec/ipsec/xfrmi -n xfrm-moon-in -d eth0 -i 42
moon::ip link add xfrm-moon-out type xfrm dev eth0 if_id 1337
moon::ip link add xfrm-moon-in type xfrm dev eth0 if_id 42
moon::ip link set xfrm-moon-out up
moon::ip link set xfrm-moon-in up
moon::ip route add 10.2.0.0/16 dev xfrm-moon-out

View File

@ -4,7 +4,7 @@ IF_NAME="xfrmi-${PLUTO_IF_ID_IN}"
case "${PLUTO_VERB}" in
up-client)
/usr/local/libexec/ipsec/xfrmi -n "${IF_NAME}" -i "${PLUTO_IF_ID_IN}" -d eth0
ip link add "${IF_NAME}" type xfrm if_id "${PLUTO_IF_ID_IN}" dev eth0
ip link set "${IF_NAME}" up
ip route add 10.1.0.0/16 dev "${IF_NAME}"
iptables -A FORWARD -i "${IF_NAME}" -j ACCEPT

View File

@ -1,6 +1,6 @@
moon::iptables-restore < /etc/iptables.rules
sun::iptables-restore < /etc/iptables.rules
moon::/usr/local/libexec/ipsec/xfrmi -n xfrm-moon -i 42 -d eth0
moon::ip link add xfrm-moon type xfrm if_id 42 dev eth0
moon::ip link set xfrm-moon up
moon::ip route add 10.2.0.0/16 dev xfrm-moon
moon::iptables -A FORWARD -i xfrm-moon -j ACCEPT

View File

@ -6,8 +6,8 @@ IF_NAME_OUT="${IF_NAME}${PLUTO_IF_ID_OUT}-out"
case "${PLUTO_VERB}" in
up-client)
/usr/local/libexec/ipsec/xfrmi -n "${IF_NAME_OUT}" -i "${PLUTO_IF_ID_OUT}" -d eth0
/usr/local/libexec/ipsec/xfrmi -n "${IF_NAME_IN}" -i "${PLUTO_IF_ID_IN}" -d eth0
ip link add "${IF_NAME_OUT}" type xfrm if_id "${PLUTO_IF_ID_OUT}" dev eth0
ip link add "${IF_NAME_IN}" type xfrm if_id "${PLUTO_IF_ID_IN}" dev eth0
ip link set "${IF_NAME_OUT}" up
ip link set "${IF_NAME_IN}" up
ip route add 10.1.0.0/16 dev "${IF_NAME_OUT}"

View File

@ -1,7 +1,7 @@
moon::iptables-restore < /etc/iptables.rules
sun::iptables-restore < /etc/iptables.rules
moon::/usr/local/libexec/ipsec/xfrmi -n xfrm-moon-out -d eth0 -i 1337
moon::/usr/local/libexec/ipsec/xfrmi -n xfrm-moon-in -d eth0 -i 42
moon::ip link add xfrm-moon-out type xfrm dev eth0 if_id 1337
moon::ip link add xfrm-moon-in type xfrm dev eth0 if_id 42
moon::ip link set xfrm-moon-out up
moon::ip link set xfrm-moon-in up
moon::ip route add 10.2.0.0/16 dev xfrm-moon-out

View File

@ -1,7 +1,7 @@
moon::iptables-restore < /etc/iptables.rules
carol::iptables-restore < /etc/iptables.rules
dave::iptables-restore < /etc/iptables.rules
moon::/usr/local/libexec/ipsec/xfrmi -n xfrm-moon -i 42 -d eth0
moon::ip link add xfrm-moon type xfrm if_id 42 dev eth0
moon::ip link set xfrm-moon up
moon::ip route add 10.3.0.0/28 dev xfrm-moon
moon::iptables -A FORWARD -i xfrm-moon -j ACCEPT