kernel-libipsec: Add support to send/receive raw ESP packets

This is currently only supported on Linux and with the appropriate
permissions.

Since it's experimental, it's disabled by default.

The log messages for each sent and received ESP message are logged in NET
like the ones in the socket-default plugin for UDP-encapsulated messages.
This commit is contained in:
Tobias Brunner 2023-05-11 18:34:58 +02:00
parent 29e8cb3f90
commit e306fa5f73
7 changed files with 469 additions and 9 deletions

View File

@ -5,3 +5,10 @@ charon.plugins.kernel-libipsec.allow_peer_ts = no
installed for such traffic (via TUN device) usually prevents further IKE installed for such traffic (via TUN device) usually prevents further IKE
traffic. The fwmark options for the _kernel-netlink_ and _socket-default_ traffic. The fwmark options for the _kernel-netlink_ and _socket-default_
plugins can be used to circumvent that problem. 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.

View File

@ -15,7 +15,8 @@ endif
libstrongswan_kernel_libipsec_la_SOURCES = \ libstrongswan_kernel_libipsec_la_SOURCES = \
kernel_libipsec_plugin.h kernel_libipsec_plugin.c \ kernel_libipsec_plugin.h kernel_libipsec_plugin.c \
kernel_libipsec_ipsec.h kernel_libipsec_ipsec.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 libstrongswan_kernel_libipsec_la_LIBADD = $(top_builddir)/src/libipsec/libipsec.la

View File

@ -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 <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.
*/
/* for struct in6_pktinfo */
#define _GNU_SOURCE
#include "kernel_libipsec_esp_handler.h"
#ifdef __linux__
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <ipsec.h>
#include <collections/blocking_queue.h>
#include <processing/jobs/callback_job.h>
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__ */

View File

@ -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 <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_libipsec_esp_handler kernel_libipsec_esp_handler
* @{ @ingroup kernel_libipsec
*/
#ifndef KERNEL_LIBIPSEC_ESP_HANDLER_H_
#define KERNEL_LIBIPSEC_ESP_HANDLER_H_
#include <esp_packet.h>
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_ @}*/

View File

@ -56,6 +56,11 @@ struct private_kernel_libipsec_ipsec_t {
* Whether the remote TS may equal the IKE peer * Whether the remote TS may equal the IKE peer
*/ */
bool allow_peer_ts; bool allow_peer_ts;
/**
* Whether UDP encapsulation is required
*/
bool require_encap;
}; };
typedef struct exclude_route_t exclude_route_t; 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, METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
private_kernel_libipsec_ipsec_t *this) private_kernel_libipsec_ipsec_t *this)
{ {
return KERNEL_REQUIRE_UDP_ENCAPSULATION | KERNEL_ESP_V3_TFC | return KERNEL_ESP_V3_TFC | KERNEL_SA_USE_TIME |
KERNEL_SA_USE_TIME; (this->require_encap ? KERNEL_REQUIRE_UDP_ENCAPSULATION : 0);
} }
METHOD(kernel_ipsec_t, get_spi, status_t, 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, private_kernel_libipsec_ipsec_t *this, kernel_ipsec_sa_id_t *id,
kernel_ipsec_add_sa_t *data) 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 " DBG1(DBG_ESP, "failed to add SAD entry: only UDP encapsulation is "
"supported"); "supported");
@ -704,6 +709,7 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create()
.excludes = linked_list_create(), .excludes = linked_list_create(),
.allow_peer_ts = lib->settings->get_bool(lib->settings, .allow_peer_ts = lib->settings->get_bool(lib->settings,
"%s.plugins.kernel-libipsec.allow_peer_ts", FALSE, lib->ns), "%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); ipsec->events->register_listener(ipsec->events, &this->ipsec_listener);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2012-2013 Tobias Brunner * Copyright (C) 2012-2023 Tobias Brunner
* *
* Copyright (C) secunet Security Networks AG * Copyright (C) secunet Security Networks AG
* *
@ -17,6 +17,7 @@
#include "kernel_libipsec_plugin.h" #include "kernel_libipsec_plugin.h"
#include "kernel_libipsec_ipsec.h" #include "kernel_libipsec_ipsec.h"
#include "kernel_libipsec_router.h" #include "kernel_libipsec_router.h"
#include "kernel_libipsec_esp_handler.h"
#include <daemon.h> #include <daemon.h>
#include <ipsec.h> #include <ipsec.h>
@ -45,6 +46,11 @@ struct private_kernel_libipsec_plugin_t {
* Packet router * Packet router
*/ */
kernel_libipsec_router_t *router; kernel_libipsec_router_t *router;
/**
* Raw ESP handler
*/
kernel_libipsec_esp_handler_t *esp_handler;
}; };
METHOD(plugin_t, get_name, char*, METHOD(plugin_t, get_name, char*,
@ -92,6 +98,11 @@ METHOD(plugin_t, destroy, void,
lib->set(lib, "kernel-libipsec-tun", NULL); lib->set(lib, "kernel-libipsec-tun", NULL);
this->tun->destroy(this->tun); 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(); libipsec_deinit();
free(this); free(this);
} }
@ -146,5 +157,17 @@ plugin_t *kernel_libipsec_plugin_create()
/* set TUN device as default to install VIPs */ /* set TUN device as default to install VIPs */
lib->settings->set_str(lib->settings, "%s.install_virtual_ip_on", lib->settings->set_str(lib->settings, "%s.install_virtual_ip_on",
this->tun->get_name(this->tun), lib->ns); 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; return &this->public.plugin;
} }

View File

@ -18,6 +18,7 @@
#include <fcntl.h> #include <fcntl.h>
#include "kernel_libipsec_router.h" #include "kernel_libipsec_router.h"
#include "kernel_libipsec_esp_handler.h"
#include <daemon.h> #include <daemon.h>
#include <ipsec.h> #include <ipsec.h>
@ -76,6 +77,11 @@ struct private_kernel_libipsec_router_t {
* Pipe to signal handle_plain() about changes regarding TUN devices * Pipe to signal handle_plain() about changes regarding TUN devices
*/ */
int notify[2]; 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, 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, CALLBACK(receiver_esp_cb, void,
@ -323,7 +340,8 @@ kernel_libipsec_router_t *kernel_libipsec_router_create()
}, },
.tun = { .tun = {
.tun = lib->get(lib, "kernel-libipsec-tun"), .tun = lib->get(lib, "kernel-libipsec-tun"),
} },
.esp_handler = lib->get(lib, "kernel-libipsec-esp-handler"),
); );
if (pipe(this->notify) != 0 || if (pipe(this->notify) != 0 ||
@ -341,7 +359,7 @@ kernel_libipsec_router_t *kernel_libipsec_router_create()
this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT); this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT);
charon->kernel->add_listener(charon->kernel, &this->public.listener); 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); ipsec->processor->register_inbound(ipsec->processor, deliver_plain, this);
charon->receiver->add_esp_cb(charon->receiver, receiver_esp_cb, NULL); charon->receiver->add_esp_cb(charon->receiver, receiver_esp_cb, NULL);
lib->processor->queue_job(lib->processor, lib->processor->queue_job(lib->processor,