diff --git a/conf/plugins/kernel-libipsec.opt b/conf/plugins/kernel-libipsec.opt index e76db63d9..a79d00d1c 100644 --- a/conf/plugins/kernel-libipsec.opt +++ b/conf/plugins/kernel-libipsec.opt @@ -5,3 +5,10 @@ charon.plugins.kernel-libipsec.allow_peer_ts = no installed for such traffic (via TUN device) usually prevents further IKE traffic. The fwmark options for the _kernel-netlink_ and _socket-default_ plugins can be used to circumvent that problem. + +charon.plugins.kernel-libipsec.fwmark = charon.plugins.socket-default.fwmark + Firewall mark to set on outbound raw ESP packets. + +charon.plugins.kernel-libipsec.raw_esp = no + Whether to send and receive ESP packets without UDP encapsulation if + supported on this platform and no NAT is detected. diff --git a/src/libcharon/plugins/kernel_libipsec/Makefile.am b/src/libcharon/plugins/kernel_libipsec/Makefile.am index 4757280b4..604d6b4fb 100644 --- a/src/libcharon/plugins/kernel_libipsec/Makefile.am +++ b/src/libcharon/plugins/kernel_libipsec/Makefile.am @@ -15,7 +15,8 @@ endif libstrongswan_kernel_libipsec_la_SOURCES = \ kernel_libipsec_plugin.h kernel_libipsec_plugin.c \ kernel_libipsec_ipsec.h kernel_libipsec_ipsec.c \ - kernel_libipsec_router.h kernel_libipsec_router.c + kernel_libipsec_router.h kernel_libipsec_router.c \ + kernel_libipsec_esp_handler.h kernel_libipsec_esp_handler.c libstrongswan_kernel_libipsec_la_LIBADD = $(top_builddir)/src/libipsec/libipsec.la diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_esp_handler.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_esp_handler.c new file mode 100644 index 000000000..095ad67b4 --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_esp_handler.c @@ -0,0 +1,351 @@ +/* + * Copyright (C) 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. + */ + +/* for struct in6_pktinfo */ +#define _GNU_SOURCE + +#include "kernel_libipsec_esp_handler.h" + +#ifdef __linux__ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +typedef struct private_kernel_libipsec_esp_handler_t private_kernel_libipsec_esp_handler_t; + +/** + * Private data + */ +struct private_kernel_libipsec_esp_handler_t { + + /** + * Public interface + */ + kernel_libipsec_esp_handler_t public; + + /** + * Queue for outbound ESP packets (esp_packet_t*) + */ + blocking_queue_t *queue; + + /** + * Socket to send/receive IPv4 ESP packets + */ + int skt_v4; + + /** + * Socket to send/receive IPv6 ESP packets + */ + int skt_v6; +}; + +METHOD(kernel_libipsec_esp_handler_t, send_, void, + private_kernel_libipsec_esp_handler_t *this, esp_packet_t *packet) +{ + this->queue->enqueue(this->queue, packet); +} + +CALLBACK(send_esp, job_requeue_t, + private_kernel_libipsec_esp_handler_t *this) +{ + packet_t *packet; + host_t *source, *destination; + chunk_t data; + struct msghdr msg = {}; + struct cmsghdr *cmsg; + struct iovec iov; + char ancillary[64] = {}; + ssize_t len; + int skt; + + packet = (packet_t*)this->queue->dequeue(this->queue); + + data = packet->get_data(packet); + source = packet->get_source(packet); + destination = packet->get_destination(packet); + DBG2(DBG_NET, "sending raw ESP packet: from %H to %H (%zu data bytes)", + source, destination, data.len); + + /* the port of the destination address acts as protocol selector for RAW + * sockets, for IPv4 the kernel ignores it, for IPv6 it does not and + * complains if it isn't zero or doesn't match the one set on the socket */ + destination->set_port(destination, 0); + + msg.msg_name = destination->get_sockaddr(destination); + msg.msg_namelen = *destination->get_sockaddr_len(destination); + iov.iov_base = data.ptr; + iov.iov_len = data.len; + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_flags = 0; + msg.msg_control = ancillary; + + if (source->get_family(source) == AF_INET) + { + struct in_pktinfo *pktinfo; + const struct sockaddr_in *sin; + + msg.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg); + sin = (struct sockaddr_in*)source->get_sockaddr(source); + memcpy(&pktinfo->ipi_spec_dst, &sin->sin_addr, sizeof(struct in_addr)); + skt = this->skt_v4; + } + else + { + struct in6_pktinfo *pktinfo; + const struct sockaddr_in6 *sin; + + msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + + pktinfo = (struct in6_pktinfo*)CMSG_DATA(cmsg); + sin = (struct sockaddr_in6*)source->get_sockaddr(source); + memcpy(&pktinfo->ipi6_addr, &sin->sin6_addr, sizeof(struct in6_addr)); + skt = this->skt_v6; + } + + len = sendmsg(skt, &msg, 0); + if (len != data.len) + { + DBG1(DBG_KNL, "error writing to ESP socket: %s", strerror(errno)); + } + packet->destroy(packet); + return JOB_REQUEUE_DIRECT; +} + +CALLBACK(receive_esp, bool, + private_kernel_libipsec_esp_handler_t *this, int fd, watcher_event_t event) +{ + char buf[2048]; + struct msghdr msg; + struct cmsghdr *cmsg; + struct iovec iov; + char ancillary[64]; + union { + struct sockaddr_in in4; + struct sockaddr_in6 in6; + } src; + host_t *source, *destination = NULL; + packet_t *packet; + chunk_t data; + ssize_t len; + + msg.msg_name = &src; + msg.msg_namelen = sizeof(src); + iov.iov_base = buf; + iov.iov_len = sizeof(buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = ancillary; + msg.msg_controllen = sizeof(ancillary); + msg.msg_flags = 0; + + len = recvmsg(fd, &msg, MSG_DONTWAIT|MSG_TRUNC); + if (len < 0) + { + if (errno != EAGAIN && errno != EWOULDBLOCK) + { + DBG1(DBG_KNL, "receiving from ESP socket failed: %s", + strerror(errno)); + } + return TRUE; + } + else if (msg.msg_flags & MSG_TRUNC) + { + DBG1(DBG_KNL, "ESP packet with length %zd exceeds buffer size of %zu", + len, sizeof(buf)); + return TRUE; + } + data = chunk_create(buf, len); + /* skip the IP header returned by IPv4 raw sockets */ + if (fd == this->skt_v4) + { + data = chunk_skip(data, sizeof(struct iphdr)); + } + + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_PKTINFO) + { + const struct in_pktinfo *pktinfo = (struct in_pktinfo*)CMSG_DATA(cmsg); + struct sockaddr_in dst = { + .sin_family = AF_INET, + }; + + memcpy(&dst.sin_addr, &pktinfo->ipi_addr, sizeof(dst.sin_addr)); + destination = host_create_from_sockaddr((sockaddr_t*)&dst); + } + else if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_PKTINFO) + { + const struct in6_pktinfo *pktinfo = (struct in6_pktinfo*)CMSG_DATA(cmsg); + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + }; + + memcpy(&dst.sin6_addr, &pktinfo->ipi6_addr, sizeof(dst.sin6_addr)); + destination = host_create_from_sockaddr((sockaddr_t*)&dst); + } + if (destination) + { + break; + } + } + if (!destination) + { + DBG1(DBG_KNL, "error reading destination IP address for ESP packet"); + return TRUE; + } + source = host_create_from_sockaddr((sockaddr_t*)&src); + DBG2(DBG_NET, "received raw ESP packet: from %#H to %#H (%zu data bytes)", + source, destination, data.len); + + packet = packet_create(); + packet->set_source(packet, source); + packet->set_destination(packet, destination); + packet->set_data(packet, chunk_clone(data)); + ipsec->processor->queue_inbound(ipsec->processor, + esp_packet_create_from_packet(packet)); + return TRUE; +} + +METHOD(kernel_libipsec_esp_handler_t, destroy, void, + private_kernel_libipsec_esp_handler_t *this) +{ + if (this->skt_v4 >= 0) + { + lib->watcher->remove(lib->watcher, this->skt_v4); + close(this->skt_v4); + } + if (this->skt_v6 >= 0) + { + lib->watcher->remove(lib->watcher, this->skt_v6); + close(this->skt_v6); + } + this->queue->destroy_offset(this->queue, offsetof(esp_packet_t, destroy)); + free(this); +} + +/** + * Create a RAW socket for the given address family + */ +static int create_socket(int family) +{ + const char *fwmark; + mark_t mark; + int skt, on = 1; + + skt = socket(family, SOCK_RAW, IPPROTO_ESP); + if (skt == -1) + { + DBG1(DBG_KNL, "opening RAW socket for ESP failed: %s", strerror(errno)); + return -1; + } + if (setsockopt(skt, family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6, + family == AF_INET ? IP_PKTINFO : IPV6_RECVPKTINFO, + &on, sizeof(on)) == -1) + { + DBG1(DBG_KNL, "unable to set PKTINFO on ESP socket: %s", + strerror(errno)); + close(skt); + return -1; + } + fwmark = lib->settings->get_str(lib->settings, + "%s.plugins.kernel-libipsec.fwmark", + lib->settings->get_str(lib->settings, + "%s.plugins.socket-default.fwmark", NULL, lib->ns), + lib->ns); + if (fwmark && mark_from_string(fwmark, MARK_OP_NONE, &mark) && + setsockopt(skt, SOL_SOCKET, SO_MARK, &mark.value, sizeof(mark.value)) < 0) + { + DBG1(DBG_KNL, "unable to set SO_MARK on ESP socket: %s", + strerror(errno)); + } + return skt; +} + +/* + * Described in header + */ +kernel_libipsec_esp_handler_t *kernel_libipsec_esp_handler_create() +{ + private_kernel_libipsec_esp_handler_t *this; + + if (!lib->caps->keep(lib->caps, CAP_NET_RAW)) + { /* required to open SOCK_RAW sockets and according to capabilities(7) + * it is also required to use the socket */ + DBG1(DBG_KNL, "kernel-libipsec requires CAP_NET_RAW capability to send " + "and receive ESP packets without UDP encapsulation"); + return NULL; + } + + INIT(this, + .public = { + .send = _send_, + .destroy = _destroy, + }, + .queue = blocking_queue_create(), + .skt_v4 = create_socket(AF_INET), + .skt_v6 = create_socket(AF_INET6), + ); + + if (this->skt_v4 == -1 && this->skt_v6 == -1) + { + destroy(this); + return NULL; + } + if (this->skt_v4 >= 0) + { + lib->watcher->add(lib->watcher, this->skt_v4, WATCHER_READ, + receive_esp, this); + } + if (this->skt_v6 >= 0) + { + lib->watcher->add(lib->watcher, this->skt_v6, WATCHER_READ, + receive_esp, this); + } + lib->processor->queue_job(lib->processor, + (job_t*)callback_job_create(send_esp, this, NULL, + (callback_job_cancel_t)return_false)); + return &this->public; +} + +#else /* __linux__ */ + +kernel_libipsec_esp_handler_t *kernel_libipsec_esp_handler_create() +{ + return NULL; +} + +#endif /* __linux__ */ diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_esp_handler.h b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_esp_handler.h new file mode 100644 index 000000000..6b7a1082c --- /dev/null +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_esp_handler.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 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_libipsec_esp_handler kernel_libipsec_esp_handler + * @{ @ingroup kernel_libipsec + */ + +#ifndef KERNEL_LIBIPSEC_ESP_HANDLER_H_ +#define KERNEL_LIBIPSEC_ESP_HANDLER_H_ + +#include + +typedef struct kernel_libipsec_esp_handler_t kernel_libipsec_esp_handler_t; + +/** + * Class that sends and receives raw ESP packets. + */ +struct kernel_libipsec_esp_handler_t { + + /** + * Send the given ESP packet without UDP encapsulation. + * + * @param packet ESP packet to send + */ + void (*send)(kernel_libipsec_esp_handler_t *this, esp_packet_t *packet); + + /** + * Destroy the given instance. + */ + void (*destroy)(kernel_libipsec_esp_handler_t *this); +}; + +/** + * Create a kernel_libipsec_esp_handler_t instance. + * + * @return created instance, NULL if not supported + */ +kernel_libipsec_esp_handler_t *kernel_libipsec_esp_handler_create(); + +#endif /** KERNEL_LIBIPSEC_ESP_HANDLER_H_ @}*/ diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c index 174751833..22e1200dd 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c @@ -56,6 +56,11 @@ struct private_kernel_libipsec_ipsec_t { * Whether the remote TS may equal the IKE peer */ bool allow_peer_ts; + + /** + * Whether UDP encapsulation is required + */ + bool require_encap; }; typedef struct exclude_route_t exclude_route_t; @@ -241,8 +246,8 @@ static void acquire(uint32_t reqid) METHOD(kernel_ipsec_t, get_features, kernel_feature_t, private_kernel_libipsec_ipsec_t *this) { - return KERNEL_REQUIRE_UDP_ENCAPSULATION | KERNEL_ESP_V3_TFC | - KERNEL_SA_USE_TIME; + return KERNEL_ESP_V3_TFC | KERNEL_SA_USE_TIME | + (this->require_encap ? KERNEL_REQUIRE_UDP_ENCAPSULATION : 0); } METHOD(kernel_ipsec_t, get_spi, status_t, @@ -263,7 +268,7 @@ METHOD(kernel_ipsec_t, add_sa, status_t, private_kernel_libipsec_ipsec_t *this, kernel_ipsec_sa_id_t *id, kernel_ipsec_add_sa_t *data) { - if (!data->encap) + if (this->require_encap && !data->encap) { DBG1(DBG_ESP, "failed to add SAD entry: only UDP encapsulation is " "supported"); @@ -704,6 +709,7 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create() .excludes = linked_list_create(), .allow_peer_ts = lib->settings->get_bool(lib->settings, "%s.plugins.kernel-libipsec.allow_peer_ts", FALSE, lib->ns), + .require_encap = !lib->get(lib, "kernel-libipsec-esp-handler"), ); ipsec->events->register_listener(ipsec->events, &this->ipsec_listener); diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c index 0b25518f4..f539693eb 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Tobias Brunner + * Copyright (C) 2012-2023 Tobias Brunner * * Copyright (C) secunet Security Networks AG * @@ -17,6 +17,7 @@ #include "kernel_libipsec_plugin.h" #include "kernel_libipsec_ipsec.h" #include "kernel_libipsec_router.h" +#include "kernel_libipsec_esp_handler.h" #include #include @@ -45,6 +46,11 @@ struct private_kernel_libipsec_plugin_t { * Packet router */ kernel_libipsec_router_t *router; + + /** + * Raw ESP handler + */ + kernel_libipsec_esp_handler_t *esp_handler; }; METHOD(plugin_t, get_name, char*, @@ -92,6 +98,11 @@ METHOD(plugin_t, destroy, void, lib->set(lib, "kernel-libipsec-tun", NULL); this->tun->destroy(this->tun); } + if (this->esp_handler) + { + lib->set(lib, "kernel-libipsec-esp-handler", NULL); + this->esp_handler->destroy(this->esp_handler); + } libipsec_deinit(); free(this); } @@ -146,5 +157,17 @@ plugin_t *kernel_libipsec_plugin_create() /* set TUN device as default to install VIPs */ lib->settings->set_str(lib->settings, "%s.install_virtual_ip_on", this->tun->get_name(this->tun), lib->ns); + + if (lib->settings->get_bool(lib->settings, + "%s.plugins.kernel-libipsec.raw_esp", FALSE, lib->ns)) + { + this->esp_handler = kernel_libipsec_esp_handler_create(); + if (!this->esp_handler) + { + DBG1(DBG_KNL, "only UDP-encapsulated ESP packets supported by " + "kernel-libipsec on this platform"); + } + lib->set(lib, "kernel-libipsec-esp-handler", this->esp_handler); + } return &this->public.plugin; } diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c index 07a4da4a3..74746e251 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c @@ -18,6 +18,7 @@ #include #include "kernel_libipsec_router.h" +#include "kernel_libipsec_esp_handler.h" #include #include @@ -76,6 +77,11 @@ struct private_kernel_libipsec_router_t { * Pipe to signal handle_plain() about changes regarding TUN devices */ int notify[2]; + + /** + * ESP handler to send raw ESP packets + */ + kernel_libipsec_esp_handler_t *esp_handler; }; /** @@ -95,9 +101,20 @@ static bool tun_entry_equals(tun_entry_t *a, tun_entry_t *b) } CALLBACK(send_esp, void, - void *data, esp_packet_t *packet, bool encap) + private_kernel_libipsec_router_t *this, esp_packet_t *packet, bool encap) { - charon->sender->send_no_marker(charon->sender, (packet_t*)packet); + if (encap) + { + charon->sender->send_no_marker(charon->sender, (packet_t*)packet); + } + else if (this->esp_handler) + { + this->esp_handler->send(this->esp_handler, packet); + } + else + { /* shouldn't happen as UDP encap is forced without ESP handler */ + packet->destroy(packet); + } } CALLBACK(receiver_esp_cb, void, @@ -323,7 +340,8 @@ kernel_libipsec_router_t *kernel_libipsec_router_create() }, .tun = { .tun = lib->get(lib, "kernel-libipsec-tun"), - } + }, + .esp_handler = lib->get(lib, "kernel-libipsec-esp-handler"), ); if (pipe(this->notify) != 0 || @@ -341,7 +359,7 @@ kernel_libipsec_router_t *kernel_libipsec_router_create() this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT); charon->kernel->add_listener(charon->kernel, &this->public.listener); - ipsec->processor->register_outbound(ipsec->processor, send_esp, NULL); + ipsec->processor->register_outbound(ipsec->processor, send_esp, this); ipsec->processor->register_inbound(ipsec->processor, deliver_plain, this); charon->receiver->add_esp_cb(charon->receiver, receiver_esp_cb, NULL); lib->processor->queue_job(lib->processor,