From: Andreas Steffen Date: Tue, 31 Jan 2023 13:59:57 +0000 (+0100) Subject: libipsec: Added Windows tun device support X-Git-Tag: android-2.4.0~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=77b91e6d0eaffb3c69b47221c3de3bb8ff80e01a;p=thirdparty%2Fstrongswan.git libipsec: Added Windows tun device support --- diff --git a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c index 884616345f..28ddcd59eb 100644 --- a/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c +++ b/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c @@ -1,5 +1,7 @@ /* * Copyright (C) 2013 Tobias Brunner + * Coyyright (C) 2020 Noel Kuntze + * Copyright (C) 2023 Andreas Steffen * * Copyright (C) secunet Security Networks AG * @@ -17,6 +19,11 @@ #include #include +#ifdef WIN32 +#include +#include +#endif + #include "kernel_libipsec_router.h" #include @@ -26,6 +33,7 @@ #include #include #include +#include typedef struct private_kernel_libipsec_router_t private_kernel_libipsec_router_t; @@ -35,8 +43,14 @@ typedef struct private_kernel_libipsec_router_t private_kernel_libipsec_router_t typedef struct { /** virtual IP (points to internal data of tun) */ host_t *addr; + /** underlying TUN file descriptor (cached from tun) */ +#ifdef WIN32 + HANDLE handle; +#else /* !WIN32 */ int fd; +#endif + /** TUN device */ tun_device_t *tun; } tun_entry_t; @@ -72,10 +86,51 @@ struct private_kernel_libipsec_router_t { */ rwlock_t *lock; +#ifdef WIN32 + /** + * Event we use to signal handle_plain() about changes regarding tun devices + */ + HANDLE event; + + /** + * This is a notification value that we atomically set and reset if we don't + * use events right now. It's used so that we can avoid using WaitFor* + * functions when busy looping. + */ + volatile bool notify; + + /** + * Setting for waiting on event or using busy loop + */ + bool use_events; + + /** + * Whether a packet could be read from any of the tun devices in the last + * iteration of handle_plain + */ + bool got_result; + + /** + * How long the spinloop should run in microseconds after failing to + * get a packet before it waits for events again. + */ + uint64_t spinloop_threshold; + + /** + * TBD + */ + LARGE_INTEGER switching_time; + + /** + * TBD + */ + bool windows_close; +#else /* !WIN32 */ /** * Pipe to signal handle_plain() about changes regarding TUN devices */ int notify[2]; +#endif }; /** @@ -102,6 +157,15 @@ static void send_esp(void *data, esp_packet_t *packet) charon->sender->send_no_marker(charon->sender, (packet_t*)packet); } +/** + * Raise an acquire event + */ +static void raise_acquire(uint32_t reqid, kernel_acquire_data_t *data) +{ + lib->processor->queue_job(lib->processor, + (job_t *)acquire_job_create(reqid, data)); +} + /** * Receiver callback */ @@ -133,7 +197,7 @@ static void deliver_plain(private_kernel_libipsec_router_t *this, /** * Read and process outbound plaintext packet for the given TUN device */ -static void process_plain(tun_device_t *tun) +static bool process_plain(tun_device_t *tun) { chunk_t raw; @@ -150,9 +214,12 @@ static void process_plain(tun_device_t *tun) { DBG1(DBG_KNL, "invalid IP packet read from TUN device"); } + return TRUE; } + return FALSE; } +#ifndef WIN32 /** * Find flagged revents in a pollfd set by fd */ @@ -169,6 +236,7 @@ static int find_revents(struct pollfd *pfd, int count, int fd) } return 0; } +#endif /** * Job handling outbound plaintext packets @@ -177,8 +245,203 @@ static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this) { enumerator_t *enumerator; tun_entry_t *entry; - bool oldstate; int count = 0; +#ifdef WIN32 + uint64_t processed_packets = 0, failed_calls = 0; + LARGE_INTEGER StartingTime = { .QuadPart = 0 }; + LARGE_INTEGER EndingTime = { .QuadPart = 0 }; + LARGE_INTEGER ElapsedMicrosecs = { .QuadPart = 0}; + LARGE_INTEGER Frequency = { .QuadPart = 0}; + + QueryPerformanceFrequency(&Frequency); + + this->lock->read_lock(this->lock); + + if (this->use_events || !this->got_result) + { + HANDLE *tun_handles; + DWORD ret; + + DBG2(DBG_LIB, "Running in event driven mode."); + QueryPerformanceCounter(&this->switching_time); + + /* Check if any of the TUN devices has data for reading */ + tun_handles = alloca(sizeof(HANDLE)* (this->tuns->get_count(this->tuns)+2)); + tun_handles[count] = this->event; + count++; + tun_handles[count] = this->tun.handle; + count++; + + enumerator = this->tuns->create_enumerator(this->tuns); + while (enumerator->enumerate(enumerator, NULL, &entry)) + { + tun_handles[count] = entry->handle; + count++; + } + enumerator->destroy(enumerator); + + this->lock->unlock(this->lock); + QueryPerformanceCounter(&StartingTime); + ret = WaitForMultipleObjects(count, tun_handles, FALSE, INFINITE); + QueryPerformanceCounter(&EndingTime); + ElapsedMicrosecs.QuadPart = EndingTime.QuadPart - StartingTime.QuadPart; + ElapsedMicrosecs.QuadPart *= 1000000000; + ElapsedMicrosecs.QuadPart /= Frequency.QuadPart; + DBG2(DBG_LIB, "Waited for %lld nanoseconds (%lld miliseconds)", + ElapsedMicrosecs.QuadPart, ElapsedMicrosecs.QuadPart/1000000); + this->lock->read_lock(this->lock); + + if (ret >= WAIT_OBJECT_0 || ret <= WAIT_OBJECT_0 + count -1) + { + int offset = ret - WAIT_OBJECT_0; + this->got_result = TRUE; + + switch (offset) + { + case 0: + DBG2(DBG_LIB, "Interrupt job from event"); + ResetEvent(tun_handles[offset]); + break; + case 1: + DBG2(DBG_LIB, "got packet in event mode"); + process_plain(this->tun.tun); + break; + default: + DBG2(DBG_LIB, "got packet in event mode"); + enumerator = this->tuns->create_enumerator(this->tuns); + while (enumerator->enumerate(enumerator, NULL, &entry)) + { + if (WaitForSingleObjectEx(entry->handle, 0, FALSE) == WAIT_OBJECT_0) + { + process_plain(entry->tun); + } + } + enumerator->destroy(enumerator); + break; + } + } + else if (ret >= WAIT_ABANDONED_0 || ret <= WAIT_ABANDONED_0 + count -1) + { + int offset = ret - WAIT_ABANDONED_0; + this->got_result = FALSE; + switch(offset) + { + case 0: + DBG2(DBG_LIB, "Notify handle closed."); + break; + case 1: + DBG2(DBG_LIB, "Primary tun handle closed"); + break; + default: + DBG2(DBG_LIB, "Other tun handle closed at offset %d", offset); + break; + } + return JOB_REQUEUE_NONE; + } + else if (ret == WAIT_FAILED) + { + DBG1(DBG_LIB, "Failed to wait for tun devices to be ready for reading"); + } + QueryPerformanceCounter(&this->switching_time); + } + else + { + /* TODO: Set realtime priority for charon-svc.exe + * (otherwise Windows suspends the process after only a couple + * of processes or stops waking up the process events) + */ + + /* Check each handle individually */ + QueryPerformanceCounter(&EndingTime); + ElapsedMicrosecs.QuadPart = EndingTime.QuadPart - + this->switching_time.QuadPart; + ElapsedMicrosecs.QuadPart *= 1000000000; + ElapsedMicrosecs.QuadPart /= Frequency.QuadPart; + DBG2(DBG_LIB, "Delay between switching is %lld nanoseconds", + ElapsedMicrosecs.QuadPart); + + do { + /* Because the NT kernel scheduler stops waking up the process + * if we wait too often, we need to avoid calling any WaitFor* functions. + * Thus we busy loop in user space until we get no result for some time */ + this->got_result = FALSE; + if (process_plain(this->tun.tun)) + { + this->got_result |= TRUE; + processed_packets++; + } + else + { + failed_calls++; + } + ResetEvent(this->tun.handle); + + enumerator = this->tuns->create_enumerator(this->tuns); + while(enumerator->enumerate(enumerator, NULL, &entry)) + { + if (process_plain(entry->tun)) + { + processed_packets++; + this->got_result |= TRUE; + } + else + { + failed_calls++; + } + ResetEvent(entry->handle); + } + enumerator->destroy(enumerator); + + if (!this->got_result) + { + if (!StartingTime.QuadPart) + { + QueryPerformanceCounter(&StartingTime); + } + else + { + QueryPerformanceCounter(&EndingTime); + ElapsedMicrosecs.QuadPart = EndingTime.QuadPart - + StartingTime.QuadPart; + ElapsedMicrosecs.QuadPart *= 1000000000; + ElapsedMicrosecs.QuadPart /= Frequency.QuadPart; + } + + enumerator = this->tuns->create_enumerator(this->tuns); + while(enumerator->enumerate(enumerator, NULL, &entry)) + { + ResetEvent(entry->handle); + } + enumerator->destroy(enumerator); + + if (ElapsedMicrosecs.QuadPart >= this->spinloop_threshold) + { + DBG2(DBG_LIB, "Processed %lld packets, " + "failed %lld calls to read packets. " + "Reached threshold at %lld ns, switching back to events.", + processed_packets, failed_calls, + ElapsedMicrosecs.QuadPart); + + ResetEvent(this->event); + this->notify = FALSE; + break; + } + } + + if(this->notify) + { + DBG2(DBG_LIB, "Processed %lld packets, Interrupt job from bool", + processed_packets); + ResetEvent(this->event); + this->notify = FALSE; + break; + } + + } + while (TRUE); + } +#else /* !WIN32 */ + bool oldstate; char buf[1]; struct pollfd *pfd; @@ -235,8 +498,8 @@ static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this) } } enumerator->destroy(enumerator); +#endif this->lock->unlock(this->lock); - return JOB_REQUEUE_DIRECT; } @@ -244,14 +507,20 @@ METHOD(kernel_listener_t, tun, bool, private_kernel_libipsec_router_t *this, tun_device_t *tun, bool created) { tun_entry_t *entry, lookup; +#ifndef WIN32 char buf[] = {0x01}; +#endif this->lock->write_lock(this->lock); if (created) { INIT(entry, .addr = tun->get_address(tun, NULL), +#ifdef WIN32 + .handle = tun->get_handle(tun), +#else /* !WIN32 */ .fd = tun->get_fd(tun), +#endif .tun = tun, ); this->tuns->put(this->tuns, entry, entry); @@ -263,7 +532,12 @@ METHOD(kernel_listener_t, tun, bool, free(entry); } /* notify handler thread to recreate FD set */ +#ifdef WIN32 + SetEvent(this->event); + this->notify = TRUE; +#else /* !WIN32 */ ignore_result(write(this->notify[1], buf, sizeof(buf))); +#endif this->lock->unlock(this->lock); return TRUE; } @@ -298,15 +572,25 @@ METHOD(kernel_libipsec_router_t, destroy, void, (ipsec_outbound_cb_t)send_esp); ipsec->processor->unregister_inbound(ipsec->processor, (ipsec_inbound_cb_t)deliver_plain); + ipsec->processor->unregister_acquire(ipsec->processor, + (ipsec_acquire_cb_t)raise_acquire); charon->kernel->remove_listener(charon->kernel, &this->public.listener); - this->lock->destroy(this->lock); - this->tuns->destroy(this->tuns); +#ifdef WIN32 + SetEvent(this->event); + this->tun.tun->destroy(this->tun.tun); + CloseHandle(this->tun.handle); + CloseHandle(this->event); +#else /* !WIN32 */ close(this->notify[0]); close(this->notify[1]); +#endif + this->lock->destroy(this->lock); + this->tuns->destroy(this->tuns); router = NULL; free(this); } +#ifndef WIN32 /** * Set O_NONBLOCK on the given socket. */ @@ -315,6 +599,19 @@ static bool set_nonblock(int socket) int flags = fcntl(socket, F_GETFL); return flags != -1 && fcntl(socket, F_SETFL, flags | O_NONBLOCK) != -1; } +#endif + +#ifdef WIN32 +static void reload(private_kernel_libipsec_router_t *this) +{ + this->use_events = lib->settings->get_bool( + lib->settings, "%s.use_events", FALSE, lib->ns); + this->spinloop_threshold = lib->settings->get_int( + lib->settings, "%s.spinloop_threshold", 4000000, lib->ns); + DBG1(DBG_LIB, "Read new use_events setting %d and spinloop_threshold %lld", + this->use_events, this->spinloop_threshold); +} +#endif /* * See header file @@ -336,16 +633,22 @@ kernel_libipsec_router_t *kernel_libipsec_router_create() } ); +#ifdef WIN32 + reload(this); + this->tun.handle = this->tun.tun->get_handle(this->tun.tun); + if (!(this->event = CreateEvent(NULL, FALSE, FALSE, FALSE))) +#else /* !WIN32 */ if (pipe(this->notify) != 0 || !set_nonblock(this->notify[0]) || !set_nonblock(this->notify[1])) +#endif { - DBG1(DBG_KNL, "creating notify pipe for kernel-libipsec router failed"); + DBG1(DBG_KNL, "creating notify for kernel-libipsec router failed"); free(this); return NULL; } - +#ifndef WIN32 this->tun.fd = this->tun.tun->get_fd(this->tun.tun); - +#endif this->tuns = hashtable_create((hashtable_hash_t)tun_entry_hash, (hashtable_equals_t)tun_entry_equals, 4); this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT); @@ -354,6 +657,7 @@ kernel_libipsec_router_t *kernel_libipsec_router_create() ipsec->processor->register_outbound(ipsec->processor, send_esp, NULL); ipsec->processor->register_inbound(ipsec->processor, (ipsec_inbound_cb_t)deliver_plain, this); + ipsec->processor->register_acquire(ipsec->processor, raise_acquire, NULL); charon->receiver->add_esp_cb(charon->receiver, (receiver_esp_cb_t)receiver_esp_cb, NULL); lib->processor->queue_job(lib->processor, diff --git a/src/libipsec/ipsec_processor.h b/src/libipsec/ipsec_processor.h index 734e8f53e8..af7cd18350 100644 --- a/src/libipsec/ipsec_processor.h +++ b/src/libipsec/ipsec_processor.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2012 Tobias Brunner + * Copyright (C) 2020 Noel Kuntze * * Copyright (C) secunet Security Networks AG * @@ -46,6 +47,15 @@ typedef void (*ipsec_inbound_cb_t)(void *data, ip_packet_t *packet); */ typedef void (*ipsec_outbound_cb_t)(void *data, esp_packet_t *packet); + +/** + * Callback called to raise acquire events. + * + * @param reqid reqid of the matched IPsec policy + * @param data data supplied during registration of the callback + */ +typedef void (*ipsec_acquire_cb_t)(uint32_t reqid, kernel_acquire_data_t *data); + /** * IPsec processor */ @@ -99,6 +109,22 @@ struct ipsec_processor_t { void (*unregister_outbound)(ipsec_processor_t *this, ipsec_outbound_cb_t cb); + /** + * Register the callback used to raise an acquire event. + * + * @param cb the callback function + * @param data optional data provided to the callback + */ + void (*register_acquire)(ipsec_processor_t *this, ipsec_acquire_cb_t cb, + void *data); + + /** + * Unregister a previously registered acquire callback. + * + * @param cb previously registered acquire callback + */ + void (*unregister_acquire)(ipsec_processor_t *this, ipsec_acquire_cb_t cb); + /** * Destroy an ipsec_processor_t. */ diff --git a/src/libstrongswan/networking/tun_device.h b/src/libstrongswan/networking/tun_device.h index 36ece0e6a2..5e4d4161f1 100644 --- a/src/libstrongswan/networking/tun_device.h +++ b/src/libstrongswan/networking/tun_device.h @@ -2,6 +2,7 @@ * Copyright (C) 2012 Tobias Brunner * Copyright (C) 2012 Giuliano Grassi * Copyright (C) 2012 Ralf Sager + * Copyright (C) 2020 Noel Kuntze * * Copyright (C) secunet Security Networks AG * @@ -100,12 +101,21 @@ struct tun_device_t { */ char *(*get_name)(tun_device_t *this); +#ifdef WIN32 + /** + * Get the underlying HANDLE. + * + * @return file HANDLE of this tun device + */ + HANDLE (*get_handle)(tun_device_t *this); +#else /* !WIN32 */ /** * Get the underlying tun file descriptor. * * @return file descriptor of this tun device */ int (*get_fd)(tun_device_t *this); +#endif /** * Destroy a tun_device_t