From 85d56b1c6adecd97007f58280800b341889bdc80 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 5 Jan 2023 16:24:42 +0100 Subject: [PATCH 1/5] charon-nm: Tie lifetime of dummy TUN device to connection NM doesn't terminate charon-nm after disconnecting, so the TUN device previously stayed around even if no connection was established. This might be a bit more what users expect. --- src/charon-nm/nm/nm_service.c | 47 ++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/charon-nm/nm/nm_service.c b/src/charon-nm/nm/nm_service.c index 5d6d32950..cbac239f7 100644 --- a/src/charon-nm/nm/nm_service.c +++ b/src/charon-nm/nm/nm_service.c @@ -137,11 +137,18 @@ static void signal_ip_config(NMVpnServicePlugin *plugin, * 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... */ + DESTROY_IF(priv->tun); + priv->tun = tun_device_create(NULL); 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"); + } /* pass the first virtual IPs we got or use the physical IP */ enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, TRUE); @@ -655,11 +662,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 +991,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 +1001,31 @@ 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 the dummy TUN device */ + if (priv->tun) + { + priv->tun->destroy(priv->tun); + priv->tun = NULL; + } return FALSE; } @@ -1044,7 +1057,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->tun = NULL; priv->name = NULL; } From e1ff1eefcffb356213a3e119de19149fcc2474cd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 11 Jul 2022 13:12:46 +0200 Subject: [PATCH 2/5] kernel-netlink: Add manager for XFRM interfaces The manager will allow charon-nm to create XFRM interfaces if supported by the kernel instead of creating an unused dummy TUN interface. The xfrmi tool is mostly obsolete nowadays as iproute2 supports creating XFRM interfaces since 5.1.0 (2019-05). Older Debians don't ship that and early versions didn't list the interface IDs. So there might still be some uses for this tool. --- .../plugins/kernel_netlink/Makefile.am | 3 +- .../kernel_netlink/kernel_netlink_ipsec.c | 17 + .../kernel_netlink/kernel_netlink_xfrmi.c | 413 ++++++++++++++++++ .../kernel_netlink/kernel_netlink_xfrmi.h | 84 ++++ src/xfrmi/xfrmi.c | 252 ++--------- .../net2net-xfrmi-ike/hosts/sun/etc/updown.py | 8 +- .../route-based/net2net-xfrmi-ike/pretest.dat | 4 +- .../net2net-xfrmi-netns/hosts/sun/etc/updown | 2 +- .../net2net-xfrmi-netns/pretest.dat | 2 +- .../net2net-xfrmi/hosts/sun/etc/updown | 4 +- .../route-based/net2net-xfrmi/pretest.dat | 4 +- .../route-based/rw-shared-xfrmi/pretest.dat | 2 +- 12 files changed, 570 insertions(+), 225 deletions(-) create mode 100644 src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c create mode 100644 src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.h diff --git a/src/libcharon/plugins/kernel_netlink/Makefile.am b/src/libcharon/plugins/kernel_netlink/Makefile.am index 71bc09d5b..3db197142 100644 --- a/src/libcharon/plugins/kernel_netlink/Makefile.am +++ b/src/libcharon/plugins/kernel_netlink/Makefile.am @@ -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) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 6580c9138..229d9d718 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -63,6 +63,7 @@ #include "kernel_netlink_ipsec.h" #include "kernel_netlink_shared.h" +#include "kernel_netlink_xfrmi.h" #include #include @@ -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 */ @@ -3949,6 +3955,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)) @@ -4188,5 +4199,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; } diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c new file mode 100644 index 000000000..1bdd95edd --- /dev/null +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c @@ -0,0 +1,413 @@ +/* + * 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 . + * + * 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 + +#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; +}; + +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 TRUE; + 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; +} diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.h b/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.h new file mode 100644 index 000000000..a590a9246 --- /dev/null +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.h @@ -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 . + * + * 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 + +#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_ @}*/ diff --git a/src/xfrmi/xfrmi.c b/src/xfrmi/xfrmi.c index 82d19fd90..946402ed3 100644 --- a/src/xfrmi/xfrmi.c +++ b/src/xfrmi/xfrmi.c @@ -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 #include #include -#include -#include -#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); } diff --git a/testing/tests/route-based/net2net-xfrmi-ike/hosts/sun/etc/updown.py b/testing/tests/route-based/net2net-xfrmi-ike/hosts/sun/etc/updown.py index 67985a405..4abc1aa54 100755 --- a/testing/tests/route-based/net2net-xfrmi-ike/hosts/sun/etc/updown.py +++ b/testing/tests/route-based/net2net-xfrmi-ike/hosts/sun/etc/updown.py @@ -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, diff --git a/testing/tests/route-based/net2net-xfrmi-ike/pretest.dat b/testing/tests/route-based/net2net-xfrmi-ike/pretest.dat index a314a3467..062b12774 100644 --- a/testing/tests/route-based/net2net-xfrmi-ike/pretest.dat +++ b/testing/tests/route-based/net2net-xfrmi-ike/pretest.dat @@ -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 diff --git a/testing/tests/route-based/net2net-xfrmi-netns/hosts/sun/etc/updown b/testing/tests/route-based/net2net-xfrmi-netns/hosts/sun/etc/updown index 71e904dfc..f018d0360 100755 --- a/testing/tests/route-based/net2net-xfrmi-netns/hosts/sun/etc/updown +++ b/testing/tests/route-based/net2net-xfrmi-netns/hosts/sun/etc/updown @@ -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 diff --git a/testing/tests/route-based/net2net-xfrmi-netns/pretest.dat b/testing/tests/route-based/net2net-xfrmi-netns/pretest.dat index 54f302e08..b70aac667 100644 --- a/testing/tests/route-based/net2net-xfrmi-netns/pretest.dat +++ b/testing/tests/route-based/net2net-xfrmi-netns/pretest.dat @@ -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 diff --git a/testing/tests/route-based/net2net-xfrmi/hosts/sun/etc/updown b/testing/tests/route-based/net2net-xfrmi/hosts/sun/etc/updown index bf0d0b86a..2a220b598 100755 --- a/testing/tests/route-based/net2net-xfrmi/hosts/sun/etc/updown +++ b/testing/tests/route-based/net2net-xfrmi/hosts/sun/etc/updown @@ -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}" diff --git a/testing/tests/route-based/net2net-xfrmi/pretest.dat b/testing/tests/route-based/net2net-xfrmi/pretest.dat index b7ca425d0..cd7f2ca0e 100644 --- a/testing/tests/route-based/net2net-xfrmi/pretest.dat +++ b/testing/tests/route-based/net2net-xfrmi/pretest.dat @@ -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 diff --git a/testing/tests/route-based/rw-shared-xfrmi/pretest.dat b/testing/tests/route-based/rw-shared-xfrmi/pretest.dat index f4104cf32..76d75f875 100644 --- a/testing/tests/route-based/rw-shared-xfrmi/pretest.dat +++ b/testing/tests/route-based/rw-shared-xfrmi/pretest.dat @@ -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 From 0cc780d317561b9837566132cc8a131b627ca219 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 9 Jan 2023 14:54:28 +0100 Subject: [PATCH 3/5] kernel-netlink: Automatically bring up XFRM interfaces after creation --- .../kernel_netlink/kernel_netlink_xfrmi.c | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c index 1bdd95edd..1cc44d8e9 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_xfrmi.c @@ -47,6 +47,38 @@ struct private_kernel_netlink_xfrmi_t { 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) @@ -106,7 +138,7 @@ METHOD(kernel_netlink_xfrmi_t, create, bool, switch (this->socket->send_ack(this->socket, hdr)) { case SUCCESS: - return TRUE; + return interface_up(this, name); case ALREADY_DONE: DBG1(DBG_KNL, "XFRM interface '%s' already exists", name); break; From 04486507b212c1b0850e231ad4c120f518e2c1dd Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 9 Jan 2023 15:10:55 +0100 Subject: [PATCH 4/5] kernel-netlink: Add an option to install routes for SAs with XFRM interfaces Since these might conflict with IKE traffic, this requires special care. One option is to install bypass policies for the peer, which install appropriate (throw) routes. However, that won't work if the traffic to the gateway itself should be protected, in particular, for host-to-host tunnels. So an alternative is to set a mark for the IKE socket and then exclude that traffic from table 220 via the kernel-netlink plugin's fwmark option. --- conf/plugins/kernel-netlink.opt | 10 ++++ .../kernel_netlink/kernel_netlink_ipsec.c | 53 ++++++++++++++++--- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/conf/plugins/kernel-netlink.opt b/conf/plugins/kernel-netlink.opt index 1ca5a42dd..f1767ef34 100644 --- a/conf/plugins/kernel-netlink.opt +++ b/conf/plugins/kernel-netlink.opt @@ -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. diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index 229d9d718..35fd7bd6c 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -358,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 @@ -2736,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 */ @@ -2765,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 */ @@ -3006,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)) @@ -4148,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), From 58f278f93239668691e907886726ba62b01f99d5 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Mon, 9 Jan 2023 15:19:43 +0100 Subject: [PATCH 5/5] 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. --- conf/options/charon-nm.opt | 3 + src/charon-nm/charon-nm.c | 16 +++++ src/charon-nm/nm/nm_service.c | 119 ++++++++++++++++++++++++---------- 3 files changed, 103 insertions(+), 35 deletions(-) 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); }