aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--configure.ac8
-rwxr-xr-xscripts/test.sh3
-rw-r--r--src/libcharon/Makefile.am14
-rw-r--r--src/libcharon/plugins/kernel_iph/Makefile.am20
-rw-r--r--src/libcharon/plugins/kernel_iph/kernel_iph_net.c772
-rw-r--r--src/libcharon/plugins/kernel_iph/kernel_iph_net.h46
-rw-r--r--src/libcharon/plugins/kernel_iph/kernel_iph_plugin.c76
-rw-r--r--src/libcharon/plugins/kernel_iph/kernel_iph_plugin.h42
-rw-r--r--src/libcharon/plugins/kernel_wfp/Makefile.am33
-rw-r--r--src/libcharon/plugins/kernel_wfp/ipsecdump.c666
-rw-r--r--src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c157
-rw-r--r--src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h205
-rw-r--r--src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c2550
-rw-r--r--src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.h47
-rw-r--r--src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c77
-rw-r--r--src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.h43
-rw-r--r--src/libcharon/plugins/kernel_wfp/mingw-w64-4.8.1.diff26
-rw-r--r--src/libcharon/plugins/socket_win/socket_win_plugin.c1
-rw-r--r--src/libcharon/plugins/socket_win/socket_win_socket.c11
-rw-r--r--src/libcharon/sa/child_sa.c25
-rw-r--r--src/libhydra/kernel/kernel_interface.h2
-rw-r--r--src/libstrongswan/ipsec/ipsec_types.h2
22 files changed, 4822 insertions, 4 deletions
diff --git a/configure.ac b/configure.ac
index 2085ae1b3..736f097c1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -204,6 +204,8 @@ ARG_ENABL_SET([kernel-pfkey], [enable the PF_KEY kernel interface.])
ARG_ENABL_SET([kernel-pfroute], [enable the PF_ROUTE kernel interface.])
ARG_ENABL_SET([kernel-klips], [enable the KLIPS kernel interface.])
ARG_ENABL_SET([kernel-libipsec],[enable the libipsec kernel interface.])
+ARG_ENABL_SET([kernel-iph], [enable the Windows IP Helper based networking backend.])
+ARG_ENABL_SET([kernel-wfp], [enable the Windows Filtering Platform IPsec backend.])
ARG_DISBL_SET([socket-default], [disable default socket implementation for charon.])
ARG_ENABL_SET([socket-dynamic], [enable dynamic socket implementation for charon])
ARG_ENABL_SET([socket-win], [enable Winsock2 based socket implementation for charon])
@@ -1209,6 +1211,8 @@ ADD_PLUGIN([attr], [h charon])
ADD_PLUGIN([attr-sql], [h charon])
ADD_PLUGIN([load-tester], [c charon])
ADD_PLUGIN([kernel-libipsec], [c charon cmd])
+ADD_PLUGIN([kernel-wfp], [c charon])
+ADD_PLUGIN([kernel-iph], [c charon])
ADD_PLUGIN([kernel-pfkey], [h charon starter nm cmd])
ADD_PLUGIN([kernel-pfroute], [h charon starter nm cmd])
ADD_PLUGIN([kernel-klips], [h charon starter])
@@ -1368,6 +1372,8 @@ AM_CONDITIONAL(USE_UNIT_TESTS, test x$unit_tester = xtrue)
AM_CONDITIONAL(USE_LOAD_TESTER, test x$load_tester = xtrue)
AM_CONDITIONAL(USE_HA, test x$ha = xtrue)
AM_CONDITIONAL(USE_KERNEL_LIBIPSEC, test x$kernel_libipsec = xtrue)
+AM_CONDITIONAL(USE_KERNEL_WFP, test x$kernel_wfp = xtrue)
+AM_CONDITIONAL(USE_KERNEL_IPH, test x$kernel_iph = xtrue)
AM_CONDITIONAL(USE_WHITELIST, test x$whitelist = xtrue)
AM_CONDITIONAL(USE_LOOKIP, test x$lookip = xtrue)
AM_CONDITIONAL(USE_ERROR_NOTIFY, test x$error_notify = xtrue)
@@ -1662,6 +1668,8 @@ AC_CONFIG_FILES([
src/libcharon/plugins/uci/Makefile
src/libcharon/plugins/ha/Makefile
src/libcharon/plugins/kernel_libipsec/Makefile
+ src/libcharon/plugins/kernel_wfp/Makefile
+ src/libcharon/plugins/kernel_iph/Makefile
src/libcharon/plugins/whitelist/Makefile
src/libcharon/plugins/lookip/Makefile
src/libcharon/plugins/error_notify/Makefile
diff --git a/scripts/test.sh b/scripts/test.sh
index 9cb2de210..103b41103 100755
--- a/scripts/test.sh
+++ b/scripts/test.sh
@@ -34,7 +34,8 @@ all)
--disable-dumm --disable-kernel-pfroute --disable-keychain
--disable-lock-profiler --disable-maemo --disable-padlock
--disable-osx-attr --disable-tkm --disable-uci --disable-aikgen
- --disable-svc --disable-dbghelp-backtraces --disable-socket-win"
+ --disable-svc --disable-dbghelp-backtraces --disable-socket-win
+ --disable-kernel-wfp --disable-kernel-iph"
if test "$LEAK_DETECTIVE" = "yes"; then
# libgcrypt can't be deinitialized
CONFIG="$CONFIG --disable-gcrypt"
diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am
index 52d20b315..e81c42405 100644
--- a/src/libcharon/Makefile.am
+++ b/src/libcharon/Makefile.am
@@ -489,6 +489,20 @@ if MONOLITHIC
endif
endif
+if USE_KERNEL_WFP
+ SUBDIRS += plugins/kernel_wfp
+if MONOLITHIC
+ libcharon_la_LIBADD += plugins/kernel_wfp/libstrongswan-kernel-wfp.la
+endif
+endif
+
+if USE_KERNEL_IPH
+ SUBDIRS += plugins/kernel_iph
+if MONOLITHIC
+ libcharon_la_LIBADD += plugins/kernel_iph/libstrongswan-kernel-iph.la
+endif
+endif
+
if USE_WHITELIST
SUBDIRS += plugins/whitelist
if MONOLITHIC
diff --git a/src/libcharon/plugins/kernel_iph/Makefile.am b/src/libcharon/plugins/kernel_iph/Makefile.am
new file mode 100644
index 000000000..56946ae1f
--- /dev/null
+++ b/src/libcharon/plugins/kernel_iph/Makefile.am
@@ -0,0 +1,20 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/libstrongswan \
+ -I$(top_srcdir)/src/libhydra \
+ -I$(top_srcdir)/src/libcharon
+
+AM_CFLAGS = \
+ $(PLUGIN_CFLAGS)
+
+if MONOLITHIC
+noinst_LTLIBRARIES = libstrongswan-kernel-iph.la
+else
+plugin_LTLIBRARIES = libstrongswan-kernel-iph.la
+endif
+
+libstrongswan_kernel_iph_la_SOURCES = \
+ kernel_iph_plugin.h kernel_iph_plugin.c \
+ kernel_iph_net.h kernel_iph_net.c
+
+libstrongswan_kernel_iph_la_LDFLAGS = -module -avoid-version
+libstrongswan_kernel_iph_la_LIBADD = -liphlpapi
diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c
new file mode 100644
index 000000000..68b753792
--- /dev/null
+++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c
@@ -0,0 +1,772 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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.
+ */
+
+/* Windows 7, for some iphlpapi.h functionality */
+#define _WIN32_WINNT 0x0601
+#include <winsock2.h>
+#include <ws2ipdef.h>
+#include <windows.h>
+#include <ntddndis.h>
+#include <naptypes.h>
+#include <iphlpapi.h>
+
+#include "kernel_iph_net.h"
+
+#include <hydra.h>
+#include <threading/mutex.h>
+#include <collections/linked_list.h>
+#include <processing/jobs/callback_job.h>
+
+
+/** delay before firing roam events (ms) */
+#define ROAM_DELAY 500
+
+typedef struct private_kernel_iph_net_t private_kernel_iph_net_t;
+
+/**
+ * Private data of kernel_iph_net implementation.
+ */
+struct private_kernel_iph_net_t {
+
+ /**
+ * Public interface.
+ */
+ kernel_iph_net_t public;
+
+ /**
+ * NotifyIpInterfaceChange() handle
+ */
+ HANDLE changes;
+
+ /**
+ * EnableRouter() OVERLAPPED
+ */
+ OVERLAPPED router;
+
+ /**
+ * Mutex to access interface list
+ */
+ mutex_t *mutex;
+
+ /**
+ * Known interfaces, as iface_t
+ */
+ linked_list_t *ifaces;
+
+ /**
+ * Earliest time of the next roam event
+ */
+ timeval_t roam_next;
+
+ /**
+ * Roam event due to address change?
+ */
+ bool roam_address;
+};
+
+/**
+ * Interface entry
+ */
+typedef struct {
+ /** interface index */
+ DWORD ifindex;
+ /** interface name */
+ char *ifname;
+ /** interface description */
+ char *ifdesc;
+ /** type of interface */
+ DWORD iftype;
+ /** interface status */
+ IF_OPER_STATUS status;
+ /** list of known addresses, as host_t */
+ linked_list_t *addrs;
+} iface_t;
+
+/**
+ * Clean up an iface_t
+ */
+static void iface_destroy(iface_t *this)
+{
+ this->addrs->destroy_offset(this->addrs, offsetof(host_t, destroy));
+ free(this->ifname);
+ free(this->ifdesc);
+ free(this);
+}
+
+/**
+ * Enum names for Windows IF_OPER_STATUS
+ */
+ENUM(if_oper_names, IfOperStatusUp, IfOperStatusLowerLayerDown,
+ "Up",
+ "Down",
+ "Testing",
+ "Unknown",
+ "Dormant",
+ "NotPresent",
+ "LowerLayerDown",
+);
+
+/**
+ * Callback function that raises the delayed roam event
+ */
+static job_requeue_t roam_event(private_kernel_iph_net_t *this)
+{
+ bool address;
+
+ this->mutex->lock(this->mutex);
+ address = this->roam_address;
+ this->roam_address = FALSE;
+ this->mutex->unlock(this->mutex);
+
+ hydra->kernel_interface->roam(hydra->kernel_interface, address);
+ return JOB_REQUEUE_NONE;
+}
+
+/**
+ * Fire delayed roam event, caller should hold mutex
+ */
+static void fire_roam_event(private_kernel_iph_net_t *this, bool address)
+{
+ timeval_t now;
+
+ time_monotonic(&now);
+ this->roam_address |= address;
+ if (timercmp(&now, &this->roam_next, >))
+ {
+ timeval_add_ms(&now, ROAM_DELAY);
+ this->roam_next = now;
+ lib->scheduler->schedule_job_ms(lib->scheduler, (job_t*)
+ callback_job_create((callback_job_cb_t)roam_event,
+ this, NULL, NULL),
+ ROAM_DELAY);
+ }
+}
+
+/**
+ * Update addresses for an iface entry
+ */
+static void update_addrs(private_kernel_iph_net_t *this, iface_t *entry,
+ IP_ADAPTER_ADDRESSES *addr, bool log)
+{
+ IP_ADAPTER_UNICAST_ADDRESS *current;
+ enumerator_t *enumerator;
+ linked_list_t *list;
+ host_t *host, *old;
+ bool changes = FALSE;
+
+ list = entry->addrs;
+ entry->addrs = linked_list_create();
+
+ for (current = addr->FirstUnicastAddress; current; current = current->Next)
+ {
+ if (current->Address.lpSockaddr->sa_family == AF_INET6)
+ {
+ struct sockaddr_in6 *sin;
+
+ sin = (struct sockaddr_in6*)current->Address.lpSockaddr;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin->sin6_addr))
+ {
+ continue;
+ }
+ }
+
+ host = host_create_from_sockaddr(current->Address.lpSockaddr);
+ if (host)
+ {
+ bool found = FALSE;
+
+ enumerator = list->create_enumerator(list);
+ while (enumerator->enumerate(enumerator, &old))
+ {
+ if (host->ip_equals(host, old))
+ {
+ list->remove_at(list, enumerator);
+ old->destroy(old);
+ found = TRUE;
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ entry->addrs->insert_last(entry->addrs, host);
+
+ if (!found && log)
+ {
+ DBG1(DBG_KNL, "%H appeared on interface %u '%s'",
+ host, entry->ifindex, entry->ifdesc);
+ changes = TRUE;
+ }
+ }
+ }
+
+ while (list->remove_first(list, (void**)&old) == SUCCESS)
+ {
+ if (log)
+ {
+ DBG1(DBG_KNL, "%H disappeared from interface %u '%s'",
+ old, entry->ifindex, entry->ifdesc);
+ changes = TRUE;
+ }
+ old->destroy(old);
+ }
+ list->destroy(list);
+
+ if (changes)
+ {
+ fire_roam_event(this, TRUE);
+ }
+}
+
+/**
+ * Add an interface entry
+ */
+static void add_interface(private_kernel_iph_net_t *this,
+ IP_ADAPTER_ADDRESSES *addr, bool log)
+{
+ enumerator_t *enumerator;
+ iface_t *entry;
+ bool exists = FALSE;
+
+ this->mutex->lock(this->mutex);
+ enumerator = this->ifaces->create_enumerator(this->ifaces);
+ while (enumerator->enumerate(enumerator, &entry))
+ {
+ if (entry->ifindex == addr->IfIndex)
+ {
+ exists = TRUE;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->mutex->unlock(this->mutex);
+
+ if (!exists)
+ {
+ char desc[128] = "";
+
+ wcstombs(desc, addr->Description, sizeof(desc));
+
+ INIT(entry,
+ .ifindex = addr->IfIndex,
+ .ifname = strdup(addr->AdapterName),
+ .ifdesc = strdup(desc),
+ .iftype = addr->IfType,
+ .status = addr->OperStatus,
+ .addrs = linked_list_create(),
+ );
+
+ if (log)
+ {
+ DBG1(DBG_KNL, "interface %u '%s' appeared",
+ entry->ifindex, entry->ifdesc);
+ }
+
+ this->mutex->lock(this->mutex);
+ update_addrs(this, entry, addr, log);
+ this->ifaces->insert_last(this->ifaces, entry);
+ this->mutex->unlock(this->mutex);
+ }
+}
+
+/**
+ * Remove an interface entry that is gone
+ */
+static void remove_interface(private_kernel_iph_net_t *this, NET_IFINDEX index)
+{
+ enumerator_t *enumerator;
+ iface_t *entry;
+
+ this->mutex->lock(this->mutex);
+ enumerator = this->ifaces->create_enumerator(this->ifaces);
+ while (enumerator->enumerate(enumerator, &entry))
+ {
+ if (entry->ifindex == index)
+ {
+ this->ifaces->remove_at(this->ifaces, enumerator);
+ DBG1(DBG_KNL, "interface %u '%s' disappeared",
+ entry->ifindex, entry->ifdesc);
+ iface_destroy(entry);
+ fire_roam_event(this, TRUE);
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->mutex->unlock(this->mutex);
+}
+
+/**
+ * Update an interface entry changed
+ */
+static void update_interface(private_kernel_iph_net_t *this,
+ IP_ADAPTER_ADDRESSES *addr)
+{
+ enumerator_t *enumerator;
+ iface_t *entry;
+
+ this->mutex->lock(this->mutex);
+ enumerator = this->ifaces->create_enumerator(this->ifaces);
+ while (enumerator->enumerate(enumerator, &entry))
+ {
+ if (entry->ifindex == addr->IfIndex)
+ {
+ if (entry->status != addr->OperStatus)
+ {
+ DBG1(DBG_KNL, "interface %u '%s' changed state from %N to %N",
+ entry->ifindex, entry->ifdesc, if_oper_names,
+ entry->status, if_oper_names, addr->OperStatus);
+ entry->status = addr->OperStatus;
+ fire_roam_event(this, TRUE);
+ }
+ update_addrs(this, entry, addr, TRUE);
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->mutex->unlock(this->mutex);
+}
+
+/**
+ * MinGW gets MIB_IPINTERFACE_ROW wrong, as it packs InterfaceLuid just after
+ * Family. Fix that with our own version of the struct header.
+ */
+typedef struct {
+ ADDRESS_FAMILY Family;
+ union {
+ ULONG64 Value;
+ struct {
+ ULONG64 Reserved :24;
+ ULONG64 NetLuidIndex :24;
+ ULONG64 IfType :16;
+ } Info;
+ } InterfaceLuid;
+ NET_IFINDEX InterfaceIndex;
+ /* more would go here if needed */
+} MIB_IPINTERFACE_ROW_FIXUP;
+
+/**
+ * NotifyIpInterfaceChange() callback
+ */
+static void change_interface(private_kernel_iph_net_t *this,
+ MIB_IPINTERFACE_ROW_FIXUP *row, MIB_NOTIFICATION_TYPE type)
+{
+ IP_ADAPTER_ADDRESSES addrs[64], *current;
+ ULONG res, size = sizeof(addrs);
+
+ if (row && type == MibDeleteInstance)
+ {
+ remove_interface(this, row->InterfaceIndex);
+ }
+ else
+ {
+ res = GetAdaptersAddresses(AF_UNSPEC,
+ GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST |
+ GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_FRIENDLY_NAME,
+ NULL, addrs, &size);
+ if (res == NO_ERROR)
+ {
+ current = addrs;
+ while (current)
+ {
+ /* row is NULL only on MibInitialNotification */
+ if (!row || row->InterfaceIndex == current->IfIndex)
+ {
+ switch (type)
+ {
+ case MibParameterNotification:
+ update_interface(this, current);
+ break;
+ case MibInitialNotification:
+ add_interface(this, current, FALSE);
+ break;
+ case MibAddInstance:
+ add_interface(this, current, TRUE);
+ break;
+ default:
+ break;
+ }
+ }
+ current = current->Next;
+ }
+ }
+ else
+ {
+ DBG1(DBG_KNL, "getting IPH adapter addresses failed: 0x%08lx", res);
+ }
+ }
+}
+
+/**
+ * Get an iface entry for a local address, does no locking
+ */
+static iface_t* address2entry(private_kernel_iph_net_t *this, host_t *ip)
+{
+ enumerator_t *ifaces, *addrs;
+ iface_t *entry, *found = NULL;
+ host_t *host;
+
+ ifaces = this->ifaces->create_enumerator(this->ifaces);
+ while (!found && ifaces->enumerate(ifaces, &entry))
+ {
+ addrs = entry->addrs->create_enumerator(entry->addrs);
+ while (!found && addrs->enumerate(addrs, &host))
+ {
+ if (host->ip_equals(host, ip))
+ {
+ found = entry;
+ }
+ }
+ addrs->destroy(addrs);
+ }
+ ifaces->destroy(ifaces);
+
+ return found;
+}
+
+METHOD(kernel_net_t, get_interface_name, bool,
+ private_kernel_iph_net_t *this, host_t* ip, char **name)
+{
+ iface_t *entry;
+
+ this->mutex->lock(this->mutex);
+ entry = address2entry(this, ip);
+ if (entry && name)
+ {
+ *name = strdup(entry->ifname);
+ }
+ this->mutex->unlock(this->mutex);
+
+ return entry != NULL;
+}
+
+/**
+ * Address enumerator
+ */
+typedef struct {
+ /** implements enumerator_t */
+ enumerator_t public;
+ /** what kind of address should we enumerate? */
+ kernel_address_type_t which;
+ /** enumerator over interfaces */
+ enumerator_t *ifaces;
+ /** current enumerator over addresses, or NULL */
+ enumerator_t *addrs;
+ /** mutex to unlock on destruction */
+ mutex_t *mutex;
+} addr_enumerator_t;
+
+METHOD(enumerator_t, addr_enumerate, bool,
+ addr_enumerator_t *this, host_t **host)
+{
+ iface_t *entry;
+
+ while (TRUE)
+ {
+ while (!this->addrs)
+ {
+ if (!this->ifaces->enumerate(this->ifaces, &entry))
+ {
+ return FALSE;
+ }
+ if (entry->iftype == IF_TYPE_SOFTWARE_LOOPBACK &&
+ !(this->which & ADDR_TYPE_LOOPBACK))
+ {
+ continue;
+ }
+ if (entry->status != IfOperStatusUp &&
+ !(this->which & ADDR_TYPE_DOWN))
+ {
+ continue;
+ }
+ this->addrs = entry->addrs->create_enumerator(entry->addrs);
+ }
+ if (this->addrs->enumerate(this->addrs, host))
+ {
+ return TRUE;
+ }
+ this->addrs->destroy(this->addrs);
+ this->addrs = NULL;
+ }
+}
+
+METHOD(enumerator_t, addr_destroy, void,
+ addr_enumerator_t *this)
+{
+ DESTROY_IF(this->addrs);
+ this->ifaces->destroy(this->ifaces);
+ this->mutex->unlock(this->mutex);
+ free(this);
+}
+
+METHOD(kernel_net_t, create_address_enumerator, enumerator_t*,
+ private_kernel_iph_net_t *this, kernel_address_type_t which)
+{
+ addr_enumerator_t *enumerator;
+
+ if (!(which & ADDR_TYPE_REGULAR))
+ {
+ /* we currently have no virtual, but regular IPs only */
+ return enumerator_create_empty();
+ }
+
+ this->mutex->lock(this->mutex);
+
+ INIT(enumerator,
+ .public = {
+ .enumerate = (void*)_addr_enumerate,
+ .destroy = _addr_destroy,
+ },
+ .which = which,
+ .ifaces = this->ifaces->create_enumerator(this->ifaces),
+ .mutex = this->mutex,
+ );
+ return &enumerator->public;
+}
+
+METHOD(kernel_net_t, get_source_addr, host_t*,
+ private_kernel_iph_net_t *this, host_t *dest, host_t *src)
+{
+ MIB_IPFORWARD_ROW2 route;
+ SOCKADDR_INET best, *sai_dst, *sai_src = NULL;
+ DWORD res, index = 0;
+
+ res = GetBestInterfaceEx(dest->get_sockaddr(dest), &index);
+ if (res != NO_ERROR)
+ {
+ DBG1(DBG_KNL, "getting interface to %H failed: 0x%08x", dest, res);
+ return NULL;
+ }
+
+ sai_dst = (SOCKADDR_INET*)dest->get_sockaddr(dest);
+ if (src)
+ {
+ sai_src = (SOCKADDR_INET*)src->get_sockaddr(src);
+ }
+ res = GetBestRoute2(0, index, sai_src, sai_dst, 0, &route, &best);
+ if (res != NO_ERROR)
+ {
+ DBG2(DBG_KNL, "getting src address to %H failed: 0x%08x", dest, res);
+ return NULL;
+ }
+ return host_create_from_sockaddr((struct sockaddr*)&best);
+}
+
+METHOD(kernel_net_t, get_nexthop, host_t*,
+ private_kernel_iph_net_t *this, host_t *dest, host_t *src)
+{
+ MIB_IPFORWARD_ROW2 route;
+ SOCKADDR_INET best, *sai_dst, *sai_src = NULL;
+ DWORD res, index = 0;
+ host_t *nexthop;
+
+ res = GetBestInterfaceEx(dest->get_sockaddr(dest), &index);
+ if (res != NO_ERROR)
+ {
+ DBG1(DBG_KNL, "getting interface to %H failed: 0x%08x", dest, res);
+ return NULL;
+ }
+
+ sai_dst = (SOCKADDR_INET*)dest->get_sockaddr(dest);
+ if (src)
+ {
+ sai_src = (SOCKADDR_INET*)src->get_sockaddr(src);
+ }
+ res = GetBestRoute2(0, index, sai_src, sai_dst, 0, &route, &best);
+ if (res != NO_ERROR)
+ {
+ DBG2(DBG_KNL, "getting nexthop to %H failed: 0x%08x", dest, res);
+ return NULL;
+ }
+ nexthop = host_create_from_sockaddr((struct sockaddr*)&route.NextHop);
+ if (nexthop)
+ {
+ if (!nexthop->is_anyaddr(nexthop))
+ {
+ return nexthop;
+ }
+ nexthop->destroy(nexthop);
+ }
+ return NULL;
+}
+
+METHOD(kernel_net_t, add_ip, status_t,
+ private_kernel_iph_net_t *this, host_t *virtual_ip, int prefix,
+ char *iface_name)
+{
+ return NOT_SUPPORTED;
+}
+
+METHOD(kernel_net_t, del_ip, status_t,
+ private_kernel_iph_net_t *this, host_t *virtual_ip, int prefix,
+ bool wait)
+{
+ return NOT_SUPPORTED;
+}
+
+/**
+ * Add or remove a route
+ */
+static status_t manage_route(private_kernel_iph_net_t *this, bool add,
+ chunk_t dst, u_int8_t prefixlen, host_t *gtw, char *name)
+{
+ MIB_IPFORWARD_ROW2 row = {
+ .DestinationPrefix = {
+ .PrefixLength = prefixlen,
+ },
+ .SitePrefixLength = prefixlen,
+ .ValidLifetime = INFINITE,
+ .PreferredLifetime = INFINITE,
+ .Metric = 10,
+ .Protocol = MIB_IPPROTO_NETMGMT,
+ };
+ enumerator_t *enumerator;
+ iface_t *entry;
+ ULONG ret;
+
+ this->mutex->lock(this->mutex);
+ enumerator = this->ifaces->create_enumerator(this->ifaces);
+ while (enumerator->enumerate(enumerator, &entry))
+ {
+ if (streq(name, entry->ifname))
+ {
+ row.InterfaceIndex = entry->ifindex;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->mutex->unlock(this->mutex);
+
+ if (!row.InterfaceIndex)
+ {
+ return NOT_FOUND;
+ }
+ switch (dst.len)
+ {
+ case 4:
+ row.DestinationPrefix.Prefix.si_family = AF_INET;
+ memcpy(&row.DestinationPrefix.Prefix.Ipv4.sin_addr,
+ dst.ptr, dst.len);
+ break;
+ case 16:
+ row.DestinationPrefix.Prefix.si_family = AF_INET6;
+ memcpy(&row.DestinationPrefix.Prefix.Ipv6.sin6_addr,
+ dst.ptr, dst.len);
+ break;
+ default:
+ return FAILED;
+ }
+ if (gtw)
+ {
+ memcpy(&row.NextHop, gtw->get_sockaddr(gtw),
+ *gtw->get_sockaddr_len(gtw));
+ }
+
+ if (add)
+ {
+ ret = CreateIpForwardEntry2(&row);
+ }
+ else
+ {
+ ret = DeleteIpForwardEntry2(&row);
+ }
+ if (ret != NO_ERROR)
+ {
+ DBG1(DBG_KNL, "%sing route failed: 0x%08lx", add ? "add" : "remov", ret);
+ return FAILED;
+ }
+
+ if (add)
+ {
+ ret = EnableRouter(NULL, &this->router);
+ if (ret != ERROR_IO_PENDING)
+ {
+ DBG1(DBG_KNL, "EnableRouter router failed: 0x%08lx", ret);
+ }
+ }
+ else
+ {
+ ret = UnenableRouter(&this->router, NULL);
+ if (ret != NO_ERROR)
+ {
+ DBG1(DBG_KNL, "UnenableRouter router failed: 0x%08lx", ret);
+ }
+ }
+ return SUCCESS;
+}
+
+METHOD(kernel_net_t, add_route, status_t,
+ private_kernel_iph_net_t *this, chunk_t dst, u_int8_t prefixlen,
+ host_t *gateway, host_t *src, char *name)
+{
+ return manage_route(this, TRUE, dst, prefixlen, gateway, name);
+}
+
+METHOD(kernel_net_t, del_route, status_t,
+ private_kernel_iph_net_t *this, chunk_t dst, u_int8_t prefixlen,
+ host_t *gateway, host_t *src, char *name)
+{
+ return manage_route(this, FALSE, dst, prefixlen, gateway, name);
+}
+
+METHOD(kernel_net_t, destroy, void,
+ private_kernel_iph_net_t *this)
+{
+ if (this->changes)
+ {
+ CancelMibChangeNotify2(this->changes);
+ }
+ CloseHandle(this->router.hEvent);
+ this->mutex->destroy(this->mutex);
+ this->ifaces->destroy_function(this->ifaces, (void*)iface_destroy);
+ free(this);
+}
+
+/*
+ * Described in header.
+ */
+kernel_iph_net_t *kernel_iph_net_create()
+{
+ private_kernel_iph_net_t *this;
+ ULONG res;
+
+ INIT(this,
+ .public = {
+ .interface = {
+ .get_interface = _get_interface_name,
+ .create_address_enumerator = _create_address_enumerator,
+ .get_source_addr = _get_source_addr,
+ .get_nexthop = _get_nexthop,
+ .add_ip = _add_ip,
+ .del_ip = _del_ip,
+ .add_route = _add_route,
+ .del_route = _del_route,
+ .destroy = _destroy,
+ },
+ },
+ .router = {
+ .hEvent = CreateEvent(NULL, FALSE, FALSE, NULL),
+ },
+ .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
+ .ifaces = linked_list_create(),
+ );
+
+ res = NotifyIpInterfaceChange(AF_UNSPEC, (void*)change_interface,
+ this, TRUE, &this->changes);
+ if (res != NO_ERROR)
+ {
+ DBG1(DBG_KNL, "registering for IPH interface changes failed: 0x%08lx",
+ res);
+ destroy(this);
+ return NULL;
+ }
+
+ return &this->public;
+}
diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.h b/src/libcharon/plugins/kernel_iph/kernel_iph_net.h
new file mode 100644
index 000000000..c8f35de18
--- /dev/null
+++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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_iph_net_i kernel_iph_net
+ * @{ @ingroup kernel_iph
+ */
+
+#ifndef KERNEL_IPH_NET_H_
+#define KERNEL_IPH_NET_H_
+
+#include <kernel/kernel_net.h>
+
+typedef struct kernel_iph_net_t kernel_iph_net_t;
+
+/**
+ * Implementation of the kernel network interface using Windows IP Helper.
+ */
+struct kernel_iph_net_t {
+
+ /**
+ * Implements kernel_net_t interface
+ */
+ kernel_net_t interface;
+};
+
+/**
+ * Create IP Helper network backend instance.
+ *
+ * @return kernel_iph_net_t instance
+ */
+kernel_iph_net_t *kernel_iph_net_create();
+
+#endif /** KERNEL_IPH_NET_H_ @}*/
diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_plugin.c b/src/libcharon/plugins/kernel_iph/kernel_iph_plugin.c
new file mode 100644
index 000000000..c5475e30b
--- /dev/null
+++ b/src/libcharon/plugins/kernel_iph/kernel_iph_plugin.c
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+
+#include "kernel_iph_plugin.h"
+#include "kernel_iph_net.h"
+
+#include <hydra.h>
+
+typedef struct private_kernel_iph_plugin_t private_kernel_iph_plugin_t;
+
+/**
+ * Private data of kernel iph plugin
+ */
+struct private_kernel_iph_plugin_t {
+
+ /**
+ * Implements plugin interface
+ */
+ kernel_iph_plugin_t public;
+};
+
+METHOD(plugin_t, get_name, char*,
+ private_kernel_iph_plugin_t *this)
+{
+ return "kernel-iph";
+}
+
+METHOD(plugin_t, get_features, int,
+ private_kernel_iph_plugin_t *this, plugin_feature_t *features[])
+{
+ static plugin_feature_t f[] = {
+ PLUGIN_CALLBACK(kernel_net_register, kernel_iph_net_create),
+ PLUGIN_PROVIDE(CUSTOM, "kernel-net"),
+ };
+ *features = f;
+ return countof(f);
+}
+
+METHOD(plugin_t, destroy, void,
+ private_kernel_iph_plugin_t *this)
+{
+ free(this);
+}
+
+/*
+ * See header file
+ */
+plugin_t *kernel_iph_plugin_create()
+{
+ private_kernel_iph_plugin_t *this;
+
+ INIT(this,
+ .public = {
+ .plugin = {
+ .get_name = _get_name,
+ .get_features = _get_features,
+ .destroy = _destroy,
+ },
+ },
+ );
+
+ return &this->public.plugin;
+}
diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_plugin.h b/src/libcharon/plugins/kernel_iph/kernel_iph_plugin.h
new file mode 100644
index 000000000..616f90e77
--- /dev/null
+++ b/src/libcharon/plugins/kernel_iph/kernel_iph_plugin.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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_iph kernel_iph
+ * @ingroup cplugins
+ *
+ * @defgroup kernel_iph_plugin kernel_iph_plugin
+ * @{ @ingroup kernel_iph
+ */
+
+#ifndef KERNEL_IPH_PLUGIN_H_
+#define KERNEL_IPH_PLUGIN_H_
+
+#include <plugins/plugin.h>
+
+typedef struct kernel_iph_plugin_t kernel_iph_plugin_t;
+
+/**
+ * Windows IP Helper API based networking backend.
+ */
+struct kernel_iph_plugin_t {
+
+ /**
+ * Implements plugin interface.
+ */
+ plugin_t plugin;
+};
+
+#endif /** KERNEL_IPH_PLUGIN_H_ @}*/
diff --git a/src/libcharon/plugins/kernel_wfp/Makefile.am b/src/libcharon/plugins/kernel_wfp/Makefile.am
new file mode 100644
index 000000000..85e5089a3
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/Makefile.am
@@ -0,0 +1,33 @@
+AM_CPPFLAGS = \
+ -I$(top_srcdir)/src/libstrongswan \
+ -I$(top_srcdir)/src/libhydra \
+ -I$(top_srcdir)/src/libcharon
+
+AM_CFLAGS = \
+ $(PLUGIN_CFLAGS)
+
+if MONOLITHIC
+noinst_LTLIBRARIES = libstrongswan-kernel-wfp.la
+else
+plugin_LTLIBRARIES = libstrongswan-kernel-wfp.la
+endif
+
+libstrongswan_kernel_wfp_la_SOURCES = \
+ kernel_wfp_plugin.h kernel_wfp_plugin.c \
+ kernel_wfp_compat.c kernel_wfp_compat.h \
+ kernel_wfp_ipsec.h kernel_wfp_ipsec.c
+
+libstrongswan_kernel_wfp_la_LDFLAGS = -module -avoid-version
+libstrongswan_kernel_wfp_la_LIBADD = -lfwpuclnt
+
+
+noinst_PROGRAMS = ipsecdump
+
+ipsecdump_SOURCES = \
+ ipsecdump.c
+ipsecdump_LDADD = \
+ libstrongswan-kernel-wfp.la \
+ $(top_builddir)/src/libstrongswan/libstrongswan.la
+
+
+EXTRA_DIST = mingw-w64-4.8.1.diff
diff --git a/src/libcharon/plugins/kernel_wfp/ipsecdump.c b/src/libcharon/plugins/kernel_wfp/ipsecdump.c
new file mode 100644
index 000000000..7ca7df5a1
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/ipsecdump.c
@@ -0,0 +1,666 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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.
+ */
+
+/* Windows 7, for some fwpmu.h functionality */
+#define _WIN32_WINNT 0x0601
+
+#include "kernel_wfp_compat.h"
+
+#include <library.h>
+
+ENUM(auth_type_names, IPSEC_AUTH_MD5, IPSEC_AUTH_AES_256,
+ "MD5",
+ "SHA1",
+ "SHA256",
+ "AES128",
+ "AES192",
+ "AES256",
+);
+
+ENUM(auth_config_names, 0, 5,
+ "HMAC96",
+ "HMAC96",
+ "HMAC128",
+ "GMAC",
+ "GMAC",
+ "GMAC",
+);
+
+ENUM(cipher_type_names, IPSEC_CIPHER_TYPE_DES, IPSEC_CIPHER_TYPE_AES_256,
+ "DES",
+ "3DES",
+ "AES128",
+ "AES192",
+ "AES256",
+);
+
+ENUM(cipher_config_names, 1, 8,
+ "CBC",
+ "CBC",
+ "CBC",
+ "CBC",
+ "CBC",
+ "GCM",
+ "GCM",
+ "GCM",
+);
+
+ENUM(match_type_names, FWP_MATCH_EQUAL, FWP_MATCH_NOT_EQUAL,
+ "equals",
+ "greater",
+ "less than",
+ "greater or equal than",
+ "less or equal than",
+ "in range",
+ "has all flags set",
+ "has any flags set",
+ "has none flags set",
+ "equals case insensitive",
+ "not equal",
+);
+
+ENUM(traffic_type_names, IPSEC_TRAFFIC_TYPE_TRANSPORT, IPSEC_TRAFFIC_TYPE_TUNNEL,
+ "Transport",
+ "Tunnel",
+);
+
+/**
+ * Print a GUID to a static buffer
+ */
+static char *guid2string(GUID *guid)
+{
+ static char buf[64];
+
+ snprintf(buf, sizeof(buf),
+ "%08x,%04x,%04x%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x",
+ guid->Data1, guid->Data2, guid->Data3,
+ guid->Data4[0], guid->Data4[1], guid->Data4[2], guid->Data4[3],
+ guid->Data4[4], guid->Data4[5], guid->Data4[6], guid->Data4[7]);
+
+ return buf;
+}
+
+/**
+ * Convert filter condition key GUID to some known strings
+ */
+static char* cond2name(GUID *guid, bool *address)
+{
+ struct {
+ GUID guid;
+ char *name;
+ bool address;
+ } map[] = {
+ { FWPM_CONDITION_IP_LOCAL_ADDRESS, "local address", TRUE},
+ { FWPM_CONDITION_IP_REMOTE_ADDRESS, "remote address", TRUE},
+ { FWPM_CONDITION_IP_SOURCE_ADDRESS, "source address", TRUE},
+ { FWPM_CONDITION_IP_DESTINATION_ADDRESS, "destination address", TRUE},
+ { FWPM_CONDITION_IP_LOCAL_PORT, "local port", FALSE},
+ { FWPM_CONDITION_IP_REMOTE_PORT, "remote port", FALSE},
+ { FWPM_CONDITION_IP_PROTOCOL, "protocol", FALSE},
+ { FWPM_CONDITION_ICMP_CODE, "icmp code", FALSE},
+ { FWPM_CONDITION_ICMP_TYPE, "icmp type", FALSE},
+ };
+ int i;
+
+ for (i = 0; i < countof(map); i++)
+ {
+ if (memeq(&map[i].guid, guid, sizeof(GUID)))
+ {
+ *address = map[i].address;
+ return map[i].name;
+ }
+ }
+ *address = FALSE;
+ return guid2string(guid);
+}
+
+/**
+ * Print a host from raw data and IP version
+ */
+static void print_host(FWP_IP_VERSION version, void *data)
+{
+ host_t *host = NULL;
+ UINT32 ints[4];
+
+ switch (version)
+ {
+ case FWP_IP_VERSION_V4:
+ ints[0] = untoh32(data);
+ host = host_create_from_chunk(AF_INET, chunk_from_thing(ints[0]), 0);
+ break;
+ case FWP_IP_VERSION_V6:
+ ints[3] = untoh32(data);
+ ints[2] = untoh32(data + 4);
+ ints[1] = untoh32(data + 8);
+ ints[0] = untoh32(data + 12);
+ host = host_create_from_chunk(AF_INET6, chunk_from_thing(ints), 0);
+ break;
+ default:
+ break;
+ }
+ if (host)
+ {
+ printf("%H", host);
+ host->destroy(host);
+ }
+}
+
+/**
+ * Print IPSEC_SA_AUTH_INFORMATION0
+ */
+static void print_auth(IPSEC_SA_AUTH_INFORMATION0 *a)
+{
+ printf("%N-%N",
+ auth_type_names, a->authTransform.authTransformId.authType,
+ auth_config_names, a->authTransform.authTransformId.authConfig);
+}
+
+/**
+ * Print IPSEC_SA_CIPHER_INFORMATION0
+ */
+static void print_cipher(IPSEC_SA_CIPHER_INFORMATION0 *c)
+{
+ printf("%N-%N",
+ cipher_type_names, c->cipherTransform.cipherTransformId.cipherType,
+ cipher_config_names, c->cipherTransform.cipherTransformId.cipherConfig);
+}
+
+/**
+ * Print IPsec SA transform
+ */
+static void list_sa(HANDLE engine, IPSEC_SA0 *sa)
+{
+ printf(" SPI 0x%08x\n", sa->spi);
+ switch (sa->saTransformType)
+ {
+ case IPSEC_TRANSFORM_AH:
+ printf(" AH: ");
+ print_auth(sa->ahInformation);
+ break;
+ case IPSEC_TRANSFORM_ESP_AUTH:
+ printf(" ESP: ");
+ print_auth(sa->espAuthInformation);
+ break;
+ case IPSEC_TRANSFORM_ESP_CIPHER:
+ printf(" ESP: ");
+ print_cipher(sa->espCipherInformation);
+ break;
+ case IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER:
+ printf(" ESP: ");
+ print_auth(&sa->espAuthAndCipherInformation->saAuthInformation);
+ printf(", ");
+ print_cipher(&sa->espAuthAndCipherInformation->saCipherInformation);
+ break;
+ default:
+ printf(" (Transform %d)", sa->saTransformType);
+ break;
+ }
+ printf("\n");
+}
+
+/**
+ * List a filter condition value, optionally as IP address
+ */
+static void print_value(FWP_CONDITION_VALUE0 *value, bool address)
+{
+ chunk_t chunk;
+
+ switch (value->type)
+ {
+ case FWP_EMPTY:
+ printf("empty");
+ break;
+ case FWP_UINT8:
+ printf("%u", value->uint8);
+ break;
+ case FWP_UINT16:
+ printf("%u", value->uint16);
+ break;
+ case FWP_UINT32:
+ if (address)
+ {
+ print_host(FWP_IP_VERSION_V4, &value->uint32);
+ }
+ else
+ {
+ printf("%u", value->uint32);
+ }
+ break;
+ case FWP_UINT64:
+ printf("%llu", value->uint64);
+ break;
+ case FWP_INT8:
+ printf("%d", value->int8);
+ break;
+ case FWP_INT16:
+ printf("%d", value->int16);
+ break;
+ case FWP_INT32:
+ printf("%d", value->int32);
+ break;
+ case FWP_INT64:
+ printf("%lld", value->int64);
+ break;
+ case FWP_FLOAT:
+ printf("%f", value->float32);
+ break;
+ case FWP_DOUBLE:
+ printf("%lf", value->double64);
+ break;
+ case FWP_BYTE_ARRAY16_TYPE:
+ if (address)
+ {
+ print_host(FWP_IP_VERSION_V6, value->byteArray16);
+ }
+ else
+ {
+ chunk = chunk_create((u_char*)value->byteArray16, 16);
+ printf("%#B", &chunk);
+ }
+ break;
+ case FWP_BYTE_BLOB_TYPE:
+ chunk = chunk_create(value->byteBlob->data, value->byteBlob->size);
+ printf("%#B", &chunk);
+ break;
+ case FWP_V4_ADDR_MASK:
+ print_host(FWP_IP_VERSION_V4, &value->v4AddrMask->addr);
+ printf("/");
+ print_host(FWP_IP_VERSION_V4, &value->v4AddrMask->mask);
+ break;
+ case FWP_V6_ADDR_MASK:
+ print_host(FWP_IP_VERSION_V6, &value->v6AddrMask->addr);
+ printf("/%u", &value->v6AddrMask->prefixLength);
+ break;
+ case FWP_RANGE_TYPE:
+ print_value((FWP_CONDITION_VALUE0*)&value->rangeValue->valueLow,
+ address);
+ printf(" - ");
+ print_value((FWP_CONDITION_VALUE0*)&value->rangeValue->valueHigh,
+ address);
+ break;
+ default:
+ printf("(unsupported)");
+ break;
+ }
+}
+
+/**
+ * List a filter condition
+ */
+static void list_cond(HANDLE engine, FWPM_FILTER_CONDITION0 *cond)
+{
+ bool address;
+
+ printf(" '%s' %N '", cond2name(&cond->fieldKey, &address),
+ match_type_names, cond->matchType);
+ print_value(&cond->conditionValue, address);
+ printf("'\n");
+}
+
+/**
+ * Print IPsec SA details
+ */
+static void list_details(HANDLE engine, IPSEC_SA_DETAILS1 *details)
+{
+ int i;
+
+ printf(" %sbound SA: ",
+ details->saDirection == FWP_DIRECTION_INBOUND ? "In" : "Out");
+ print_host(details->traffic.ipVersion, &details->traffic.localV4Address);
+ printf(" %s ", details->saDirection == FWP_DIRECTION_INBOUND ? "<-" : "->");
+ print_host(details->traffic.ipVersion, &details->traffic.remoteV4Address);
+ printf("\n %N, flags: 0x%06x, lifetime: %us\n",
+ traffic_type_names, details->traffic.trafficType,
+ details->saBundle.flags, details->saBundle.lifetime.lifetimeSeconds);
+ if (details->udpEncapsulation)
+ {
+ printf(" UDP encap ports %u - %u\n",
+ details->udpEncapsulation->localUdpEncapPort,
+ details->udpEncapsulation->remoteUdpEncapPort);
+ }
+ for (i = 0; i < details->saBundle.numSAs; i++)
+ {
+ list_sa(engine, &details->saBundle.saList[i]);
+ }
+ printf(" Filter ID %llu\n", details->transportFilter->filterId);
+ for (i = 0; i < details->transportFilter->numFilterConditions; i++)
+ {
+ list_cond(engine, &details->transportFilter->filterCondition[i]);
+ }
+}
+
+/**
+ * List installed SA contexts
+ */
+static bool list_contexts(HANDLE engine)
+{
+ HANDLE handle;
+ UINT32 returned;
+ DWORD res;
+ IPSEC_SA_CONTEXT1 **entries;
+
+ res = IPsecSaContextCreateEnumHandle0(engine, NULL, &handle);
+ if (res != ERROR_SUCCESS)
+ {
+ fprintf(stderr, "IPsecSaContextCreateEnumHandle0(): 0x%08x\n", res);
+ return FALSE;
+ }
+
+ while (TRUE)
+ {
+ res = IPsecSaContextEnum1(engine, handle, 1, &entries, &returned);
+ if (res != ERROR_SUCCESS)
+ {
+ fprintf(stderr, "IPsecSaContextEnum1(): 0x%08x\n", res);
+ IPsecSaContextDestroyEnumHandle0(engine, handle);
+ return FALSE;
+ }
+ if (returned == 0)
+ {
+ break;
+ }
+
+ printf("SA context %llu:\n", entries[0]->saContextId);
+ list_details(engine, entries[0]->inboundSa);
+ list_details(engine, entries[0]->outboundSa);
+
+ FwpmFreeMemory0((void**)&entries);
+ }
+ IPsecSaContextDestroyEnumHandle0(engine, handle);
+ return TRUE;
+}
+
+const GUID FWPM_LAYER_IPSEC_KM_DEMUX_V4 = {
+ 0xf02b1526, 0xa459, 0x4a51, { 0xb9, 0xe3, 0x75, 0x9d, 0xe5, 0x2b, 0x9d, 0x2c }
+};
+const GUID FWPM_LAYER_IPSEC_KM_DEMUX_V6 = {
+ 0x2f755cf6, 0x2fd4, 0x4e88, { 0xb3, 0xe4, 0xa9, 0x1b, 0xca, 0x49, 0x52, 0x35 }
+};
+const GUID FWPM_LAYER_IPSEC_V4 = {
+ 0xeda65c74, 0x610d, 0x4bc5, { 0x94, 0x8f, 0x3c, 0x4f, 0x89, 0x55, 0x68, 0x67 }
+};
+const GUID FWPM_LAYER_IPSEC_V6 = {
+ 0x13c48442, 0x8d87, 0x4261, { 0x9a, 0x29, 0x59, 0xd2, 0xab, 0xc3, 0x48, 0xb4 }
+};
+const GUID FWPM_LAYER_IKEEXT_V4 = {
+ 0xb14b7bdb, 0xdbbd, 0x473e, { 0xbe, 0xd4, 0x8b, 0x47, 0x08, 0xd4, 0xf2, 0x70 }
+};
+const GUID FWPM_LAYER_IKEEXT_V6 = {
+ 0xb64786b3, 0xf687, 0x4eb9, { 0x89, 0xd2, 0x8e, 0xf3, 0x2a, 0xcd, 0xab, 0xe2 }
+};
+const GUID FWPM_LAYER_INBOUND_IPPACKET_V4 = {
+ 0xc86fd1bf, 0x21cd, 0x497e, { 0xa0, 0xbb, 0x17, 0x42, 0x5c, 0x88, 0x5c, 0x58 }
+};
+const GUID FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD = {
+ 0xb5a230d0, 0xa8c0, 0x44f2, { 0x91, 0x6e, 0x99, 0x1b, 0x53, 0xde, 0xd1, 0xf7 }
+};
+const GUID FWPM_LAYER_INBOUND_IPPACKET_V6 = {
+ 0xf52032cb, 0x991c, 0x46e7, { 0x97, 0x1d, 0x26, 0x01, 0x45, 0x9a, 0x91, 0xca }
+};
+const GUID FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD = {
+ 0xbb24c279, 0x93b4, 0x47a2, { 0x83, 0xad, 0xae, 0x16, 0x98, 0xb5, 0x08, 0x85 }
+};
+const GUID FWPM_LAYER_OUTBOUND_IPPACKET_V4 = {
+ 0x1e5c9fae, 0x8a84, 0x4135, { 0xa3, 0x31, 0x95, 0x0b, 0x54, 0x22, 0x9e, 0xcd }
+};
+const GUID FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD = {
+ 0x08e4bcb5, 0xb647, 0x48f3, { 0x95, 0x3c, 0xe5, 0xdd, 0xbd, 0x03, 0x93, 0x7e }
+};
+const GUID FWPM_LAYER_OUTBOUND_IPPACKET_V6 = {
+ 0xa3b3ab6b, 0x3564, 0x488c, { 0x91, 0x17, 0xf3, 0x4e, 0x82, 0x14, 0x27, 0x63 }
+};
+const GUID FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD = {
+ 0x9513d7c4, 0xa934, 0x49dc, { 0x91, 0xa7, 0x6c, 0xcb, 0x80, 0xcc, 0x02, 0xe3 }
+};
+const GUID FWPM_LAYER_IPFORWARD_V4_DISCARD = {
+ 0x9e9ea773, 0x2fae, 0x4210, { 0x8f, 0x17, 0x34, 0x12, 0x9e, 0xf3, 0x69, 0xeb }
+};
+const GUID FWPM_LAYER_IPFORWARD_V6_DISCARD = {
+ 0x31524a5d, 0x1dfe, 0x472f, { 0xbb, 0x93, 0x51, 0x8e, 0xe9, 0x45, 0xd8, 0xa2 }
+};
+const GUID FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD = {
+ 0xac4a9833, 0xf69d, 0x4648, { 0xb2, 0x61, 0x6d, 0xc8, 0x48, 0x35, 0xef, 0x39 }
+};
+const GUID FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD = {
+ 0x2a6ff955, 0x3b2b, 0x49d2, { 0x98, 0x48, 0xad, 0x9d, 0x72, 0xdc, 0xaa, 0xb7 }
+};
+const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD = {
+ 0xc5f10551, 0xbdb0, 0x43d7, { 0xa3, 0x13, 0x50, 0xe2, 0x11, 0xf4, 0xd6, 0x8a }
+};
+const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD = {
+ 0xf433df69, 0xccbd, 0x482e, { 0xb9, 0xb2, 0x57, 0x16, 0x56, 0x58, 0xc3, 0xb3 }
+};
+
+/**
+ * Convert filter layer GUID to name
+ */
+static char* layer2name(GUID *guid)
+{
+ struct {
+ GUID guid;
+ char *name;
+ } map[] = {
+ { FWPM_LAYER_IPSEC_KM_DEMUX_V4, "IPsec KM demux v4" },
+ { FWPM_LAYER_IPSEC_KM_DEMUX_V6, "IPsec KM demux v6" },
+ { FWPM_LAYER_IPSEC_V4, "IPsec v4" },
+ { FWPM_LAYER_IPSEC_V6, "IPsec v6" },
+ { FWPM_LAYER_IKEEXT_V4, "IKE ext v4" },
+ { FWPM_LAYER_IKEEXT_V6, "IKE ext v6" },
+ { FWPM_LAYER_INBOUND_IPPACKET_V4, "inbound v4" },
+ { FWPM_LAYER_INBOUND_IPPACKET_V4_DISCARD, "inbound v4 dsc" },
+ { FWPM_LAYER_INBOUND_IPPACKET_V6, "inbound v6" },
+ { FWPM_LAYER_INBOUND_IPPACKET_V6_DISCARD, "inbound v6 dsc" },
+ { FWPM_LAYER_OUTBOUND_IPPACKET_V4, "outbound v4" },
+ { FWPM_LAYER_OUTBOUND_IPPACKET_V4_DISCARD, "outbound v4 dsc" },
+ { FWPM_LAYER_OUTBOUND_IPPACKET_V6, "outbound v6" },
+ { FWPM_LAYER_OUTBOUND_IPPACKET_V6_DISCARD, "outbound v6 dsc" },
+ { FWPM_LAYER_IPFORWARD_V4, "forward v4" },
+ { FWPM_LAYER_IPFORWARD_V4_DISCARD, "forward v4 dsc" },
+ { FWPM_LAYER_IPFORWARD_V6, "forward v6" },
+ { FWPM_LAYER_IPFORWARD_V6_DISCARD, "forward v6 discard" },
+ { FWPM_LAYER_INBOUND_TRANSPORT_V4, "inbound transport v4" },
+ { FWPM_LAYER_INBOUND_TRANSPORT_V4_DISCARD, "inbound transport v4 dsc" },
+ { FWPM_LAYER_INBOUND_TRANSPORT_V6, "inbound transport v6" },
+ { FWPM_LAYER_INBOUND_TRANSPORT_V6_DISCARD, "inbound v6 transport dsc" },
+ { FWPM_LAYER_OUTBOUND_TRANSPORT_V4, "outbound transport v4" },
+ { FWPM_LAYER_OUTBOUND_TRANSPORT_V4_DISCARD, "outbound transport v4 dsc" },
+ { FWPM_LAYER_OUTBOUND_TRANSPORT_V6, "outbound transport v6" },
+ { FWPM_LAYER_OUTBOUND_TRANSPORT_V6_DISCARD, "outbound transport v6 dsc" },
+ };
+ int i;
+
+ for (i = 0; i < countof(map); i++)
+ {
+ if (memeq(&map[i].guid, guid, sizeof(GUID)))
+ {
+ return map[i].name;
+ }
+ }
+ return NULL;
+}
+
+/**
+ * Convert filter callout GUID to name
+ */
+static char* callout2name(GUID *guid)
+{
+ struct {
+ GUID guid;
+ char *name;
+ } map[] = {
+ { FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4, "inbound transport v4" },
+ { FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6, "inbound transport v6" },
+ { FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4, "outbound transport v4" },
+ { FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6, "outbound transport v6" },
+ { FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4, "inbound tunnel v4" },
+ { FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6, "inbound tunnel v6" },
+ { FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4, "outbound tunnel v4" },
+ { FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6, "outbound tunnel v6" },
+ { FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4, "forward in tunnel v4" },
+ { FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V6, "forward in tunnel v6" },
+ { FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4, "forward out tunnel v4" },
+ { FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6, "forward out tunnel v6" },
+ };
+ int i;
+
+ for (i = 0; i < countof(map); i++)
+ {
+ if (memeq(&map[i].guid, guid, sizeof(GUID)))
+ {
+ return map[i].name;
+ }
+ }
+ return guid2string(guid);
+}
+
+/**
+ * Print display data with description
+ */
+static void print_display_data(FWPM_DISPLAY_DATA0 *data)
+{
+ char buf[128];
+
+ buf[0] = '\0';
+ if (data->name)
+ {
+ wcstombs(buf, data->name, sizeof(buf));
+ }
+ printf("%s", buf);
+ if (data->description)
+ {
+ buf[0] = '\0';
+ wcstombs(buf, data->description, sizeof(buf));
+ if (strlen(buf))
+ {
+ printf(" (%s)", buf);
+ }
+ }
+}
+
+/**
+ * List installed firewall filters
+ */
+static bool list_filters(HANDLE engine)
+{
+ HANDLE handle;
+ UINT32 returned;
+ DWORD res;
+ FWPM_FILTER0 **entries;
+ char *layer;
+ int i;
+
+ res = FwpmFilterCreateEnumHandle0(engine, NULL, &handle);
+ if (res != ERROR_SUCCESS)
+ {
+ fprintf(stderr, "FwpmFilterCreateEnumHandle0(): 0x%08x\n", res);
+ return FALSE;
+ }
+
+ while (TRUE)
+ {
+ res = FwpmFilterEnum0(engine, handle, 1, &entries, &returned);
+ if (res != ERROR_SUCCESS)
+ {
+ fprintf(stderr, "FwpmFilterEnum0(): 0x%08x\n", res);
+ FwpmFilterDestroyEnumHandle0(engine, handle);
+ return FALSE;
+ }
+ if (returned == 0)
+ {
+ break;
+ }
+
+ layer = layer2name(&entries[0]->layerKey);
+ if (layer)
+ {
+ printf("Filter ID %llu, '", entries[0]->filterId);
+ print_display_data(&entries[0]->displayData);
+ printf("'\n");
+ printf(" %s, ", layer);
+ if (entries[0]->effectiveWeight.type == FWP_UINT64)
+ {
+ printf("weight %016llx, ", *entries[0]->effectiveWeight.uint64);
+ }
+
+ switch (entries[0]->action.type)
+ {
+ case FWP_ACTION_BLOCK:
+ printf("block\n");
+ break;
+ case FWP_ACTION_PERMIT:
+ printf("permit\n");
+ break;
+ case FWP_ACTION_CALLOUT_TERMINATING:
+ printf("callout terminating: %s\n",
+ callout2name(&entries[0]->action.calloutKey));
+ break;
+ case FWP_ACTION_CALLOUT_INSPECTION:
+ printf("callout inspection: %s\n",
+ callout2name(&entries[0]->action.calloutKey));
+ break;
+ case FWP_ACTION_CALLOUT_UNKNOWN:
+ printf("callout unknown: %s\n",
+ callout2name(&entries[0]->action.calloutKey));
+ break;
+ default:
+ printf("(unknown action)\n");
+ break;
+ }
+ for (i = 0; i < entries[0]->numFilterConditions; i++)
+ {
+ list_cond(engine, &entries[0]->filterCondition[i]);
+ }
+ }
+ FwpmFreeMemory0((void**)&entries);
+ }
+ FwpmFilterDestroyEnumHandle0(engine, handle);
+ return TRUE;
+}
+
+/**
+ * ipsecdump main()
+ */
+int main(int argc, char *argv[])
+{
+ FWPM_SESSION0 session = {
+ .displayData = {
+ .name = L"ipsecdump",
+ .description = L"strongSwan SAD/SPD dumper",
+ },
+ };
+ HANDLE engine;
+ DWORD res;
+ int code;
+
+ library_init(NULL, "ipsecdump");
+ atexit(library_deinit);
+
+ res = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engine);
+ if (res != ERROR_SUCCESS)
+ {
+ fprintf(stderr, "FwpmEngineOpen(): 0x%08x\n", res);
+ return 2;
+ }
+ if (argc > 1 && streq(argv[1], "filters"))
+ {
+ code = list_filters(engine) ? 0 : 1;
+ }
+ else
+ {
+ code = list_contexts(engine) ? 0 : 1;
+ }
+ FwpmEngineClose0(engine);
+ return code;
+}
diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c
new file mode 100644
index 000000000..41f85ba5c
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <library.h>
+
+const GUID FWPM_CONDITION_IP_REMOTE_ADDRESS = {
+ 0xb235ae9a, 0x1d64, 0x49b8, { 0xa4,0x4c,0x5f,0xf3,0xd9,0x09,0x50,0x45 }
+};
+const GUID FWPM_CONDITION_IP_LOCAL_ADDRESS = {
+ 0xd9ee00de, 0xc1ef, 0x4617, { 0xbf,0xe3,0xff,0xd8,0xf5,0xa0,0x89,0x57 }
+};
+const GUID FWPM_CONDITION_IP_SOURCE_ADDRESS = {
+ 0xae96897e, 0x2e94, 0x4bc9, { 0xb3,0x13,0xb2,0x7e,0xe8,0x0e,0x57,0x4d }
+};
+const GUID FWPM_CONDITION_IP_DESTINATION_ADDRESS = {
+ 0x2d79133b, 0xb390, 0x45c6, { 0x86,0x99,0xac,0xac,0xea,0xaf,0xed,0x33 }
+};
+const GUID FWPM_CONDITION_IP_LOCAL_PORT = {
+ 0x0c1ba1af, 0x5765, 0x453f, { 0xaf,0x22,0xa8,0xf7,0x91,0xac,0x77,0x5b }
+};
+const GUID FWPM_CONDITION_IP_REMOTE_PORT = {
+ 0xc35a604d, 0xd22b, 0x4e1a, { 0x91,0xb4,0x68,0xf6,0x74,0xee,0x67,0x4b }
+};
+const GUID FWPM_CONDITION_IP_PROTOCOL = {
+ 0x3971ef2b, 0x623e, 0x4f9a, { 0x8c,0xb1,0x6e,0x79,0xb8,0x06,0xb9,0xa7 }
+};
+const GUID FWPM_LAYER_INBOUND_TRANSPORT_V4 = {
+ 0x5926dfc8, 0xe3cf, 0x4426, { 0xa2,0x83,0xdc,0x39,0x3f,0x5d,0x0f,0x9d }
+};
+const GUID FWPM_LAYER_INBOUND_TRANSPORT_V6 = {
+ 0x634a869f, 0xfc23, 0x4b90, { 0xb0,0xc1,0xbf,0x62,0x0a,0x36,0xae,0x6f }
+};
+const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V4 = {
+ 0x09e61aea, 0xd214, 0x46e2, { 0x9b,0x21,0xb2,0x6b,0x0b,0x2f,0x28,0xc8 }
+};
+const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V6 = {
+ 0xe1735bde, 0x013f, 0x4655, { 0xb3,0x51,0xa4,0x9e,0x15,0x76,0x2d,0xf0 }
+};
+const GUID FWPM_LAYER_IPFORWARD_V4 = {
+ 0xa82acc24, 0x4ee1, 0x4ee1, { 0xb4,0x65,0xfd,0x1d,0x25,0xcb,0x10,0xa4}
+};
+const GUID FWPM_LAYER_IPFORWARD_V6 = {
+ 0x7b964818, 0x19c7, 0x493a, { 0xb7,0x1f,0x83,0x2c,0x36,0x84,0xd2,0x8c }
+};
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 = {
+ 0x5132900d, 0x5e84, 0x4b5f, { 0x80,0xe4,0x01,0x74,0x1e,0x81,0xff,0x10 }
+};
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6 = {
+ 0x49d3ac92, 0x2a6c, 0x4dcf, { 0x95,0x5f,0x1c,0x3b,0xe0,0x09,0xdd,0x99 }
+};
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4 = {
+ 0x4b46bf0a, 0x4523, 0x4e57, { 0xaa,0x38,0xa8,0x79,0x87,0xc9,0x10,0xd9 }
+};
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6 = {
+ 0x38d87722, 0xad83, 0x4f11, { 0xa9,0x1f,0xdf,0x0f,0xb0,0x77,0x22,0x5b }
+};
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4 = {
+ 0x191a8a46, 0x0bf8, 0x46cf, { 0xb0,0x45,0x4b,0x45,0xdf,0xa6,0xa3,0x24 }
+};
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6 = {
+ 0x80c342e3, 0x1e53, 0x4d6f, { 0x9b,0x44,0x03,0xdf,0x5a,0xee,0xe1,0x54 }
+};
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4 = {
+ 0x70a4196c, 0x835b, 0x4fb0, { 0x98,0xe8,0x07,0x5f,0x4d,0x97,0x7d,0x46 }
+};
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6 = {
+ 0xf1835363, 0xa6a5, 0x4e62, { 0xb1,0x80,0x23,0xdb,0x78,0x9d,0x8d,0xa6 }
+};
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4 = {
+ 0x28829633, 0xc4f0, 0x4e66, { 0x87,0x3f,0x84,0x4d,0xb2,0xa8,0x99,0xc7 }
+};
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V6 = {
+ 0xaf50bec2, 0xc686, 0x429a, { 0x88,0x4d,0xb7,0x44,0x43,0xe7,0xb0,0xb4 }
+};
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4 = {
+ 0xfb532136, 0x15cb, 0x440b, { 0x93,0x7c,0x17,0x17,0xca,0x32,0x0c,0x40 }
+};
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6 = {
+ 0xdae640cc, 0xe021, 0x4bee, { 0x9e,0xb6,0xa4,0x8b,0x27,0x5c,0x8c,0x1d }
+};
+
+/**
+ * Load a function symbol from a loaded dll
+ */
+static inline void *load_function(char *dll, char *name)
+{
+ HANDLE handle;
+ void *sym = NULL;
+
+ handle = GetModuleHandle(dll);
+ if (!handle)
+ {
+ return NULL;
+ }
+ sym = GetProcAddress(handle, name);
+ return sym;
+}
+
+/**
+ * Macro that defines a stub for a function that calls the same DLL function
+ *
+ * @param dll DLL to find function in
+ * @param ret return type of function
+ * @param name function name
+ * @param size size of all arguments on stack
+ * @param ... arguments of function
+ */
+#define STUB(dll, ret, name, size, ...) \
+ret WINAPI name(__VA_ARGS__) \
+{ \
+ static void (*fun)() = NULL; \
+ if (!fun) \
+ { \
+ fun = load_function(#dll, #name); \
+ } \
+ if (fun) \
+ { \
+ __builtin_return(__builtin_apply(fun, __builtin_apply_args(), size)); \
+ } \
+ return ERROR_NOT_SUPPORTED; \
+}
+
+STUB(fwpuclnt, DWORD, IPsecSaContextCreate1, 40,
+ HANDLE engineHandle, const void *outboundTraffic,
+ const void *virtualIfTunnelInfo, UINT64 *inboundFilterId, UINT64 *id)
+
+STUB(fwpuclnt, DWORD, IPsecSaContextSetSpi0, 32,
+ HANDLE engineHandle, UINT64 id, const void *getSpi, UINT32 inboundSpi)
+
+STUB(fwpuclnt, DWORD, IPsecSaContextGetById1, 24,
+ HANDLE engineHandle, UINT64 id, void **saContext)
+
+STUB(fwpuclnt, DWORD, IPsecSaContextUpdate0, 24,
+ HANDLE engineHandle, UINT32 flags, const void *newValues)
+
+STUB(fwpuclnt, DWORD, IPsecSaContextEnum1, 40,
+ HANDLE engineHandle, HANDLE enumHandle, UINT32 numEntriesRequested,
+ void ***entries, UINT32 *numEntriesReturned)
+
+STUB(fwpuclnt, DWORD, FwpmNetEventSubscribe0, 40,
+ HANDLE engineHandle, const void *subscription, void(*callback)(),
+ void *context, HANDLE *eventsHandle)
+
+STUB(fwpuclnt, DWORD, FwpmNetEventUnsubscribe0, 16,
+ HANDLE engineHandle, HANDLE eventsHandle)
diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h
new file mode 100644
index 000000000..50a89a007
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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_wfp_compat kernel_wfp_compat
+ * @{ @ingroup kernel_wfp
+ */
+
+#ifndef KERNEL_WFP_COMPAT_H_
+#define KERNEL_WFP_COMPAT_H_
+
+#include <winsock2.h>
+#include <windows.h>
+#include <ipsectypes.h>
+
+/* MinGW defines CIPHERs incorrectly starting at 0 */
+#define IPSEC_CIPHER_TYPE_DES 1
+#define IPSEC_CIPHER_TYPE_3DES 2
+#define IPSEC_CIPHER_TYPE_AES_128 3
+#define IPSEC_CIPHER_TYPE_AES_192 4
+#define IPSEC_CIPHER_TYPE_AES_256 5
+#define IPSEC_CIPHER_TYPE_MAX 6
+
+#include <fwpmtypes.h>
+#include <fwpmu.h>
+#undef interface
+
+/* MinGW defines TRANSFORMs incorrectly starting at 0 */
+#define IPSEC_TRANSFORM_AH 1
+#define IPSEC_TRANSFORM_ESP_AUTH 2
+#define IPSEC_TRANSFORM_ESP_CIPHER 3
+#define IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER 4
+#define IPSEC_TRANSFORM_ESP_AUTH_FW 5
+#define IPSEC_TRANSFORM_TYPE_MAX 6
+
+/* missing in MinGW */
+enum {
+ FWPM_TUNNEL_FLAG_POINT_TO_POINT = (1<<0),
+ FWPM_TUNNEL_FLAG_ENABLE_VIRTUAL_IF_TUNNELING = (1<<1),
+};
+
+/* missing in MinGW */
+enum {
+ IPSEC_SA_DETAILS_UPDATE_TRAFFIC = (1<<0),
+ IPSEC_SA_DETAILS_UPDATE_UDP_ENCAPSULATION = (1<<1),
+ IPSEC_SA_BUNDLE_UPDATE_FLAGS = (1<<2),
+ IPSEC_SA_BUNDLE_UPDATE_NAP_CONTEXT = (1<<3),
+ IPSEC_SA_BUNDLE_UPDATE_KEY_MODULE_STATE = (1<<4),
+ IPSEC_SA_BUNDLE_UPDATE_PEER_V4_PRIVATE_ADDRESS = (1<<5),
+ IPSEC_SA_BUNDLE_UPDATE_MM_SA_ID = (1<<6),
+};
+
+/* missing in MinGW */
+enum {
+ FWPM_NET_EVENT_FLAG_IP_PROTOCOL_SET = (1<<0),
+ FWPM_NET_EVENT_FLAG_LOCAL_ADDR_SET = (1<<1),
+ FWPM_NET_EVENT_FLAG_REMOTE_ADDR_SET = (1<<2),
+ FWPM_NET_EVENT_FLAG_LOCAL_PORT_SET = (1<<3),
+ FWPM_NET_EVENT_FLAG_REMOTE_PORT_SET = (1<<4),
+ FWPM_NET_EVENT_FLAG_APP_ID_SET = (1<<5),
+ FWPM_NET_EVENT_FLAG_USER_ID_SET = (1<<6),
+ FWPM_NET_EVENT_FLAG_SCOPE_ID_SET = (1<<7),
+ FWPM_NET_EVENT_FLAG_IP_VERSION_SET = (1<<8),
+ FWPM_NET_EVENT_FLAG_REAUTH_REASON_SET = (1<<9),
+};
+
+/* missing in MinGW */
+enum {
+ FWPM_FILTER_FLAG_PERSISTENT = (1<<0),
+ FWPM_FILTER_FLAG_BOOTTIME = (1<<1),
+ FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT = (1<<2),
+ FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT = (1<<3),
+ FWPM_FILTER_FLAG_PERMIT_IF_CALLOUT_UNREGISTERED = (1<<4),
+ FWPM_FILTER_FLAG_DISABLED = (1<<5),
+};
+
+/* missing in MinGW */
+enum {
+ IPSEC_SA_BUNDLE_FLAG_ND_SECURE = (1<< 0),
+ IPSEC_SA_BUNDLE_FLAG_ND_BOUNDARY = (1<< 1),
+ IPSEC_SA_BUNDLE_FLAG_ND_PEER_NAT_BOUNDARY = (1<< 2),
+ IPSEC_SA_BUNDLE_FLAG_GUARANTEE_ENCRYPTION = (1<< 3),
+ IPSEC_SA_BUNDLE_FLAG_NLB = (1<< 4),
+ IPSEC_SA_BUNDLE_FLAG_NO_MACHINE_LUID_VERIFY = (1<< 5),
+ IPSEC_SA_BUNDLE_FLAG_NO_IMPERSONATION_LUID_VERIFY = (1<< 6),
+ IPSEC_SA_BUNDLE_FLAG_NO_EXPLICIT_CRED_MATCH = (1<< 7),
+ IPSEC_SA_BUNDLE_FLAG_ALLOW_NULL_TARGET_NAME_MATCH = (1<< 9),
+ IPSEC_SA_BUNDLE_FLAG_CLEAR_DF_ON_TUNNEL = (1<<10),
+ IPSEC_SA_BUNDLE_FLAG_ASSUME_UDP_CONTEXT_OUTBOUND = (1<<11),
+ IPSEC_SA_BUNDLE_FLAG_ND_PEER_BOUNDARY = (1<<12),
+ IPSEC_SA_BUNDLE_FLAG_SUPPRESS_DUPLICATE_DELETION = (1<<13),
+ IPSEC_SA_BUNDLE_FLAG_PEER_SUPPORTS_GUARANTEE_ENCRYPTION = (1<<14),
+ IPSEC_SA_BUNDLE_FLAG_FORCE_INBOUND_CONNECTIONS = (1<<15),
+ IPSEC_SA_BUNDLE_FLAG_FORCE_OUTBOUND_CONNECTIONS = (1<<16),
+ IPSEC_SA_BUNDLE_FLAG_FORWARD_PATH_INITIATOR = (1<<17),
+};
+
+/* missing in some MinGW versions */
+const GUID FWPM_CONDITION_IP_REMOTE_ADDRESS;
+const GUID FWPM_CONDITION_IP_LOCAL_ADDRESS;
+const GUID FWPM_CONDITION_IP_SOURCE_ADDRESS;
+const GUID FWPM_CONDITION_IP_DESTINATION_ADDRESS;
+const GUID FWPM_CONDITION_IP_LOCAL_PORT;
+const GUID FWPM_CONDITION_IP_REMOTE_PORT;
+const GUID FWPM_CONDITION_IP_PROTOCOL;
+#ifndef FWPM_CONDITION_ICMP_TYPE
+# define FWPM_CONDITION_ICMP_TYPE FWPM_CONDITION_IP_LOCAL_PORT
+#endif
+#ifndef FWPM_CONDITION_ICMP_CODE
+# define FWPM_CONDITION_ICMP_CODE FWPM_CONDITION_IP_REMOTE_PORT
+#endif
+const GUID FWPM_LAYER_INBOUND_TRANSPORT_V4;
+const GUID FWPM_LAYER_INBOUND_TRANSPORT_V6;
+const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
+const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V6;
+const GUID FWPM_LAYER_IPFORWARD_V4;
+const GUID FWPM_LAYER_IPFORWARD_V6;
+const GUID FWPM_SUBLAYER_IPSEC_TUNNEL;
+const GUID FWPM_SUBLAYER_IPSEC_FORWARD_OUTBOUND_TUNNEL;
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4;
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6;
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4;
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6;
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4;
+const GUID FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6;
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4;
+const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6;
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4;
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V6;
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4;
+const GUID FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6;
+
+/* integrity config, missing in some MinGW versions */
+#ifndef IPSEC_AUTH_CONFIG_HMAC_MD5_96
+enum {
+ IPSEC_AUTH_CONFIG_HMAC_MD5_96 = 0,
+ IPSEC_AUTH_CONFIG_HMAC_SHA_1_96,
+ IPSEC_AUTH_CONFIG_HMAC_SHA_256_128,
+ IPSEC_AUTH_CONFIG_GCM_AES_128,
+ IPSEC_AUTH_CONFIG_GCM_AES_192,
+ IPSEC_AUTH_CONFIG_GCM_AES_256,
+ IPSEC_AUTH_CONFIG_MAX
+};
+#define IPSEC_AUTH_TRANSFORM_ID_HMAC_MD5_96 { \
+ IPSEC_AUTH_MD5, IPSEC_AUTH_CONFIG_HMAC_MD5_96 }
+#define IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96 { \
+ IPSEC_AUTH_SHA_1, IPSEC_AUTH_CONFIG_HMAC_SHA_1_96 }
+#define IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_256_128 { \
+ IPSEC_AUTH_SHA_256, IPSEC_AUTH_CONFIG_HMAC_SHA_256_128 }
+#define IPSEC_AUTH_TRANSFORM_ID_GCM_AES_128 { \
+ IPSEC_AUTH_AES_128, IPSEC_AUTH_CONFIG_GCM_AES_128 }
+#define IPSEC_AUTH_TRANSFORM_ID_GCM_AES_192 { \
+ IPSEC_AUTH_AES_192, IPSEC_AUTH_CONFIG_GCM_AES_192 }
+#define IPSEC_AUTH_TRANSFORM_ID_GCM_AES_256 { \
+ IPSEC_AUTH_AES_256, IPSEC_AUTH_CONFIG_GCM_AES_256 }
+#endif
+
+/* encryption config, missing in some MinGW versions */
+#ifndef IPSEC_CIPHER_CONFIG_CBC_DES
+enum {
+ IPSEC_CIPHER_CONFIG_CBC_DES = 1,
+ IPSEC_CIPHER_CONFIG_CBC_3DES,
+ IPSEC_CIPHER_CONFIG_CBC_AES_128,
+ IPSEC_CIPHER_CONFIG_CBC_AES_192,
+ IPSEC_CIPHER_CONFIG_CBC_AES_256,
+ IPSEC_CIPHER_CONFIG_GCM_AES_128,
+ IPSEC_CIPHER_CONFIG_GCM_AES_192,
+ IPSEC_CIPHER_CONFIG_GCM_AES_256,
+ IPSEC_CIPHER_CONFIG_MAX
+};
+#define IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_128 { \
+ IPSEC_CIPHER_TYPE_AES_128, IPSEC_CIPHER_CONFIG_GCM_AES_128 }
+#define IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_192 { \
+ IPSEC_CIPHER_TYPE_AES_192, IPSEC_CIPHER_CONFIG_GCM_AES_192 }
+#define IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_256 { \
+ IPSEC_CIPHER_TYPE_AES_256, IPSEC_CIPHER_CONFIG_GCM_AES_256 }
+#define IPSEC_CIPHER_TRANSFORM_ID_CBC_DES { \
+ IPSEC_CIPHER_TYPE_DES, IPSEC_CIPHER_CONFIG_CBC_DES }
+#define IPSEC_CIPHER_TRANSFORM_ID_CBC_3DES { \
+ IPSEC_CIPHER_TYPE_3DES, IPSEC_CIPHER_CONFIG_CBC_3DES }
+#define IPSEC_CIPHER_TRANSFORM_ID_AES_128 { \
+ IPSEC_CIPHER_TYPE_AES_128, IPSEC_CIPHER_CONFIG_CBC_AES_128 }
+#define IPSEC_CIPHER_TRANSFORM_ID_AES_192 { \
+ IPSEC_CIPHER_TYPE_AES_192, IPSEC_CIPHER_CONFIG_CBC_AES_192 }
+#define IPSEC_CIPHER_TRANSFORM_ID_AES_256 { \
+ IPSEC_CIPHER_TYPE_AES_256, IPSEC_CIPHER_CONFIG_CBC_AES_256 }
+#endif
+
+DWORD WINAPI FwpmIPsecTunnelAdd0(HANDLE, UINT32,
+ const FWPM_PROVIDER_CONTEXT0*, const FWPM_PROVIDER_CONTEXT0*, UINT32,
+ const FWPM_FILTER_CONDITION0*, PSECURITY_DESCRIPTOR);
+
+#endif /** KERNEL_WFP_COMPAT_H_ @}*/
diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
new file mode 100644
index 000000000..5b44f85b5
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
@@ -0,0 +1,2550 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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.
+ */
+
+/* Windows 7, for some fwpmu.h functionality */
+#define _WIN32_WINNT 0x0601
+
+#include "kernel_wfp_compat.h"
+#include "kernel_wfp_ipsec.h"
+
+#include <daemon.h>
+#include <hydra.h>
+#include <threading/mutex.h>
+#include <collections/array.h>
+#include <collections/hashtable.h>
+#include <processing/jobs/callback_job.h>
+
+
+typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t;
+
+struct private_kernel_wfp_ipsec_t {
+
+ /**
+ * Public interface
+ */
+ kernel_wfp_ipsec_t public;
+
+ /**
+ * Next SPI to allocate
+ */
+ refcount_t nextspi;
+
+ /**
+ * Mix value to distribute SPI allocation randomly
+ */
+ u_int32_t mixspi;
+
+ /**
+ * IKE bypass filters, as UINT64 filter LUID
+ */
+ array_t *bypass;
+
+ /**
+ * Temporary SAD/SPD entries referenced reqid, as uintptr_t => entry_t
+ */
+ hashtable_t *tsas;
+
+ /**
+ * SAD/SPD entries referenced by inbound SA, as sa_entry_t => entry_t
+ */
+ hashtable_t *isas;
+
+ /**
+ * SAD/SPD entries referenced by outbound SA, as sa_entry_t => entry_t
+ */
+ hashtable_t *osas;
+
+ /**
+ * Installed routes, as route_t => route_t
+ */
+ hashtable_t *routes;
+
+ /**
+ * Installed traps, as trap_t => trap_t
+ */
+ hashtable_t *traps;
+
+ /**
+ * Mutex for accessing entries
+ */
+ mutex_t *mutex;
+
+ /**
+ * WFP session handle
+ */
+ HANDLE handle;
+
+ /**
+ * Provider charon registers as
+ */
+ FWPM_PROVIDER0 provider;
+
+ /**
+ * Event handle
+ */
+ HANDLE event;
+};
+
+/**
+ * Security association entry
+ */
+typedef struct {
+ /** SPI for this SA */
+ u_int32_t spi;
+ /** protocol, IPPROTO_ESP/IPPROTO_AH */
+ u_int8_t protocol;
+ /** hard lifetime of SA */
+ u_int32_t lifetime;
+ /** destination host address for this SPI */
+ host_t *dst;
+ struct {
+ /** algorithm */
+ u_int16_t alg;
+ /** key */
+ chunk_t key;
+ } integ, encr;
+} sa_entry_t;
+
+/**
+ * Hash function for sas lookup table
+ */
+static u_int hash_sa(sa_entry_t *key)
+{
+ return chunk_hash_inc(chunk_from_thing(key->spi),
+ chunk_hash(key->dst->get_address(key->dst)));
+}
+
+/**
+ * equals function for sas lookup table
+ */
+static bool equals_sa(sa_entry_t *a, sa_entry_t *b)
+{
+ return a->spi == b->spi && a->dst->ip_equals(a->dst, b->dst);
+}
+
+/**
+ * Security policy entry
+ */
+typedef struct {
+ /** policy source addresses */
+ traffic_selector_t *src;
+ /** policy destinaiton addresses */
+ traffic_selector_t *dst;
+ /** WFP allocated LUID for inbound filter ID */
+ u_int64_t policy_in;
+ /** WFP allocated LUID for outbound filter ID */
+ u_int64_t policy_out;
+ /** WFP allocated LUID for forward inbound filter ID, tunnel mode only */
+ u_int64_t policy_fwd_in;
+ /** WFP allocated LUID for forward outbound filter ID, tunnel mode only */
+ u_int64_t policy_fwd_out;
+ /** have installed a route for it? */
+ bool route;
+} sp_entry_t;
+
+/**
+ * Destroy an SP entry
+ */
+static void sp_entry_destroy(sp_entry_t *sp)
+{
+ sp->src->destroy(sp->src);
+ sp->dst->destroy(sp->dst);
+ free(sp);
+}
+
+/**
+ * Collection of SA/SP database entries for a reqid
+ */
+typedef struct {
+ /** reqid of entry */
+ u_int32_t reqid;
+ /** outer address on local host */
+ host_t *local;
+ /** outer address on remote host */
+ host_t *remote;
+ /** inbound SA entry */
+ sa_entry_t isa;
+ /** outbound SA entry */
+ sa_entry_t osa;
+ /** associated (outbound) policies, as sp_entry_t* */
+ array_t *sps;
+ /** IPsec mode, tunnel|transport */
+ ipsec_mode_t mode;
+ /** UDP encapsulation */
+ bool encap;
+ /** provider context, for tunnel mode only */
+ u_int64_t provider;
+ /** WFP allocated LUID for SA context */
+ u_int64_t sa_id;
+} entry_t;
+
+/**
+ * Installed route
+ */
+typedef struct {
+ /** destination net of route */
+ host_t *dst;
+ /** prefix length of dst */
+ u_int8_t mask;
+ /** source address for route */
+ host_t *src;
+ /** gateway of route, NULL if directly attached */
+ host_t *gtw;
+ /** references for route */
+ u_int refs;
+} route_t;
+
+/**
+ * Destroy a route_t
+ */
+static void destroy_route(route_t *this)
+{
+ this->dst->destroy(this->dst);
+ this->src->destroy(this->src);
+ DESTROY_IF(this->gtw);
+ free(this);
+}
+
+/**
+ * Hashtable equals function for routes
+ */
+static bool equals_route(route_t *a, route_t *b)
+{
+ return a->mask == b->mask &&
+ a->dst->ip_equals(a->dst, b->dst) &&
+ a->src->ip_equals(a->src, b->src);
+}
+
+/**
+ * Hashtable hash function for routes
+ */
+static u_int hash_route(route_t *route)
+{
+ return chunk_hash_inc(route->src->get_address(route->src),
+ chunk_hash_inc(route->dst->get_address(route->dst), route->mask));
+}
+
+/** forward declaration */
+static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry,
+ bool add);
+
+/**
+ * Remove policies associated to an entry from kernel
+ */
+static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry)
+{
+ enumerator_t *enumerator;
+ sp_entry_t *sp;
+
+ if (entry->mode == MODE_TUNNEL)
+ {
+ manage_routes(this, entry, FALSE);
+ }
+
+ enumerator = array_create_enumerator(entry->sps);
+ while (enumerator->enumerate(enumerator, &sp))
+ {
+ if (sp->policy_in)
+ {
+ FwpmFilterDeleteById0(this->handle, sp->policy_in);
+ sp->policy_in = 0;
+ }
+ if (sp->policy_out)
+ {
+ FwpmFilterDeleteById0(this->handle, sp->policy_out);
+ sp->policy_out = 0;
+ }
+ if (sp->policy_fwd_in)
+ {
+ FwpmFilterDeleteById0(this->handle, sp->policy_fwd_in);
+ sp->policy_fwd_in = 0;
+ }
+ if (sp->policy_fwd_out)
+ {
+ FwpmFilterDeleteById0(this->handle, sp->policy_fwd_out);
+ sp->policy_fwd_out = 0;
+ }
+ }
+ enumerator->destroy(enumerator);
+}
+
+/**
+ * Destroy a SA/SP entry set
+ */
+static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry)
+{
+ if (entry->sa_id)
+ {
+ IPsecSaContextDeleteById0(this->handle, entry->sa_id);
+ }
+ if (entry->provider)
+ {
+ FwpmProviderContextDeleteById0(this->handle, entry->provider);
+ }
+ cleanup_policies(this, entry);
+ array_destroy_function(entry->sps, (void*)sp_entry_destroy, NULL);
+ entry->local->destroy(entry->local);
+ entry->remote->destroy(entry->remote);
+ chunk_clear(&entry->isa.integ.key);
+ chunk_clear(&entry->isa.encr.key);
+ chunk_clear(&entry->osa.integ.key);
+ chunk_clear(&entry->osa.encr.key);
+ free(entry);
+}
+
+/**
+ * Append/Realloc a filter condition to an existing condition set
+ */
+static FWPM_FILTER_CONDITION0 *append_condition(FWPM_FILTER_CONDITION0 *conds[],
+ int *count)
+{
+ FWPM_FILTER_CONDITION0 *cond;
+
+ (*count)++;
+ *conds = realloc(*conds, *count * sizeof(*cond));
+ cond = *conds + *count - 1;
+ memset(cond, 0, sizeof(*cond));
+
+ return cond;
+}
+
+/**
+ * Convert an IPv4 prefix to a host order subnet mask
+ */
+static u_int32_t prefix2mask(u_int8_t prefix)
+{
+ u_int8_t netmask[4] = {};
+ int i;
+
+ for (i = 0; i < sizeof(netmask); i++)
+ {
+ if (prefix < 8)
+ {
+ netmask[i] = 0xFF << (8 - prefix);
+ break;
+ }
+ netmask[i] = 0xFF;
+ prefix -= 8;
+ }
+ return untoh32(netmask);
+}
+
+/**
+ * Convert a 16-bit range to a WFP condition
+ */
+static void range2cond(FWPM_FILTER_CONDITION0 *cond,
+ u_int16_t from, u_int16_t to)
+{
+ if (from == to)
+ {
+ cond->matchType = FWP_MATCH_EQUAL;
+ cond->conditionValue.type = FWP_UINT16;
+ cond->conditionValue.uint16 = from;
+ }
+ else
+ {
+ cond->matchType = FWP_MATCH_RANGE;
+ cond->conditionValue.type = FWP_RANGE_TYPE;
+ cond->conditionValue.rangeValue = calloc(1, sizeof(FWP_RANGE0));
+ cond->conditionValue.rangeValue->valueLow.type = FWP_UINT16;
+ cond->conditionValue.rangeValue->valueLow.uint16 = from;
+ cond->conditionValue.rangeValue->valueHigh.type = FWP_UINT16;
+ cond->conditionValue.rangeValue->valueHigh.uint16 = to;
+ }
+}
+
+/**
+ * (Re-)allocate filter conditions for given local or remote traffic selector
+ */
+static bool ts2condition(traffic_selector_t *ts, const GUID *target,
+ FWPM_FILTER_CONDITION0 *conds[], int *count)
+{
+ FWPM_FILTER_CONDITION0 *cond;
+ FWP_BYTE_ARRAY16 *addr;
+ FWP_RANGE0 *range;
+ u_int16_t from_port, to_port;
+ void *from, *to;
+ u_int8_t proto;
+ host_t *net;
+ u_int8_t prefix;
+
+ from = ts->get_from_address(ts).ptr;
+ to = ts->get_to_address(ts).ptr;
+ from_port = ts->get_from_port(ts);
+ to_port = ts->get_to_port(ts);
+
+ cond = append_condition(conds, count);
+ cond->fieldKey = *target;
+ if (ts->is_host(ts, NULL))
+ {
+ cond->matchType = FWP_MATCH_EQUAL;
+ switch (ts->get_type(ts))
+ {
+ case TS_IPV4_ADDR_RANGE:
+ cond->conditionValue.type = FWP_UINT32;
+ cond->conditionValue.uint32 = untoh32(from);
+ break;
+ case TS_IPV6_ADDR_RANGE:
+ cond->conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
+ cond->conditionValue.byteArray16 = addr = malloc(sizeof(*addr));
+ memcpy(addr, from, sizeof(*addr));
+ break;
+ default:
+ return FALSE;
+ }
+ }
+ else if (ts->to_subnet(ts, &net, &prefix))
+ {
+ FWP_V6_ADDR_AND_MASK *m6;
+ FWP_V4_ADDR_AND_MASK *m4;
+
+ cond->matchType = FWP_MATCH_EQUAL;
+ switch (net->get_family(net))
+ {
+ case AF_INET:
+ cond->conditionValue.type = FWP_V4_ADDR_MASK;
+ cond->conditionValue.v4AddrMask = m4 = calloc(1, sizeof(*m4));
+ m4->addr = untoh32(from);
+ m4->mask = prefix2mask(prefix);
+ break;
+ case AF_INET6:
+ cond->conditionValue.type = FWP_V6_ADDR_MASK;
+ cond->conditionValue.v6AddrMask = m6 = calloc(1, sizeof(*m6));
+ memcpy(m6->addr, from, sizeof(m6->addr));
+ m6->prefixLength = prefix;
+ break;
+ default:
+ net->destroy(net);
+ return FALSE;
+ }
+ net->destroy(net);
+ }
+ else
+ {
+ cond->matchType = FWP_MATCH_RANGE;
+ cond->conditionValue.type = FWP_RANGE_TYPE;
+ cond->conditionValue.rangeValue = range = calloc(1, sizeof(*range));
+ switch (ts->get_type(ts))
+ {
+ case TS_IPV4_ADDR_RANGE:
+ range->valueLow.type = FWP_UINT32;
+ range->valueLow.uint32 = untoh32(from);
+ range->valueHigh.type = FWP_UINT32;
+ range->valueHigh.uint32 = untoh32(to);
+ break;
+ case TS_IPV6_ADDR_RANGE:
+ range->valueLow.type = FWP_BYTE_ARRAY16_TYPE;
+ range->valueLow.byteArray16 = addr = malloc(sizeof(*addr));
+ memcpy(addr, from, sizeof(*addr));
+ range->valueHigh.type = FWP_BYTE_ARRAY16_TYPE;
+ range->valueHigh.byteArray16 = addr = malloc(sizeof(*addr));
+ memcpy(addr, to, sizeof(*addr));
+ break;
+ default:
+ return FALSE;
+ }
+ }
+
+ proto = ts->get_protocol(ts);
+ if (proto && target == &FWPM_CONDITION_IP_LOCAL_ADDRESS)
+ {
+ cond = append_condition(conds, count);
+ cond->fieldKey = FWPM_CONDITION_IP_PROTOCOL;
+ cond->matchType = FWP_MATCH_EQUAL;
+ cond->conditionValue.type = FWP_UINT8;
+ cond->conditionValue.uint8 = proto;
+ }
+
+ if (proto == IPPROTO_ICMP)
+ {
+ if (target == &FWPM_CONDITION_IP_LOCAL_ADDRESS)
+ {
+ u_int8_t from_type, to_type, from_code, to_code;
+
+ from_type = traffic_selector_icmp_type(from_port);
+ to_type = traffic_selector_icmp_type(to_port);
+ from_code = traffic_selector_icmp_code(from_port);
+ to_code = traffic_selector_icmp_code(to_port);
+
+ if (from_type != 0 || to_type != 0xFF)
+ {
+ cond = append_condition(conds, count);
+ cond->fieldKey = FWPM_CONDITION_ICMP_TYPE;
+ range2cond(cond, from_type, to_type);
+ }
+ if (from_code != 0 || to_code != 0xFF)
+ {
+ cond = append_condition(conds, count);
+ cond->fieldKey = FWPM_CONDITION_ICMP_CODE;
+ range2cond(cond, from_code, to_code);
+ }
+ }
+ }
+ else if (from_port != 0 || to_port != 0xFFFF)
+ {
+ if (target == &FWPM_CONDITION_IP_LOCAL_ADDRESS)
+ {
+ cond = append_condition(conds, count);
+ cond->fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
+ range2cond(cond, from_port, to_port);
+ }
+ if (target == &FWPM_CONDITION_IP_REMOTE_ADDRESS)
+ {
+ cond = append_condition(conds, count);
+ cond->fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
+ range2cond(cond, from_port, to_port);
+ }
+ }
+ return TRUE;
+}
+
+/**
+ * Free memory associated to a single condition
+ */
+static void free_condition(FWP_DATA_TYPE type, void *value)
+{
+ FWP_RANGE0 *range;
+
+ switch (type)
+ {
+ case FWP_BYTE_ARRAY16_TYPE:
+ case FWP_V4_ADDR_MASK:
+ case FWP_V6_ADDR_MASK:
+ free(value);
+ break;
+ case FWP_RANGE_TYPE:
+ range = value;
+ free_condition(range->valueLow.type, range->valueLow.sd);
+ free_condition(range->valueHigh.type, range->valueHigh.sd);
+ free(range);
+ break;
+ default:
+ break;
+ }
+}
+
+/**
+ * Free memory used by a set of conditions
+ */
+static void free_conditions(FWPM_FILTER_CONDITION0 *conds, int count)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ {
+ free_condition(conds[i].conditionValue.type, conds[i].conditionValue.sd);
+ }
+ free(conds);
+}
+
+/**
+ * Find the callout GUID for given parameters
+ */
+static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward,
+ GUID *layer, GUID *sublayer, GUID *callout)
+{
+ struct {
+ bool tunnel;
+ bool v6;
+ bool inbound;
+ bool forward;
+ const GUID *layer;
+ const GUID *sublayer;
+ const GUID *callout;
+ } map[] = {
+ { 0, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, NULL,
+ &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4 },
+ { 0, 0, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V4, NULL,
+ &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 },
+ { 0, 1, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V6, NULL,
+ &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6 },
+ { 0, 1, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V6, NULL,
+ &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6 },
+ { 1, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V4,
+ &FWPM_SUBLAYER_IPSEC_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4 },
+ { 1, 0, 0, 1, &FWPM_LAYER_IPFORWARD_V4,
+ &FWPM_SUBLAYER_IPSEC_FORWARD_OUTBOUND_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4 },
+ { 1, 0, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V4,
+ &FWPM_SUBLAYER_IPSEC_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4 },
+ { 1, 0, 1, 1, &FWPM_LAYER_IPFORWARD_V4,
+ &FWPM_SUBLAYER_IPSEC_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4 },
+ { 1, 1, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V6,
+ &FWPM_SUBLAYER_IPSEC_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6 },
+ { 1, 1, 0, 1, &FWPM_LAYER_IPFORWARD_V6,
+ &FWPM_SUBLAYER_IPSEC_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6 },
+ { 1, 1, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V6,
+ &FWPM_SUBLAYER_IPSEC_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6 },
+ { 1, 1, 1, 1, &FWPM_LAYER_IPFORWARD_V6,
+ &FWPM_SUBLAYER_IPSEC_TUNNEL,
+ &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V6 },
+ };
+ int i;
+
+ for (i = 0; i < countof(map); i++)
+ {
+ if (tunnel == map[i].tunnel &&
+ v6 == map[i].v6 &&
+ inbound == map[i].inbound &&
+ forward == map[i].forward)
+ {
+ *callout = *map[i].callout;
+ *layer = *map[i].layer;
+ if (map[i].sublayer)
+ {
+ *sublayer = *map[i].sublayer;
+ }
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Install a single policy in to the kernel
+ */
+static bool install_sp(private_kernel_wfp_ipsec_t *this, sp_entry_t *sp,
+ GUID *context, bool inbound, bool fwd, UINT64 *filter_id)
+{
+ FWPM_FILTER_CONDITION0 *conds = NULL;
+ traffic_selector_t *local, *remote;
+ const GUID *ltarget, *rtarget;
+ int count = 0;
+ bool v6;
+ DWORD res;
+ FWPM_FILTER0 filter = {
+ .displayData = {
+ .name = L"charon IPsec policy",
+ },
+ .action = {
+ .type = FWP_ACTION_CALLOUT_TERMINATING,
+ },
+ };
+
+ if (context)
+ {
+ filter.flags |= FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT;
+ filter.providerKey = (GUID*)&this->provider.providerKey;
+ filter.providerContextKey = *context;
+ }
+
+ v6 = sp->src->get_type(sp->src) == TS_IPV6_ADDR_RANGE;
+ if (!find_callout(context != NULL, v6, inbound, fwd,
+ &filter.layerKey, &filter.subLayerKey,
+ &filter.action.calloutKey))
+ {
+ return FALSE;
+ }
+
+ if (inbound && fwd)
+ {
+ local = sp->dst;
+ remote = sp->src;
+ }
+ else
+ {
+ local = sp->src;
+ remote = sp->dst;
+ }
+ if (fwd)
+ {
+ ltarget = &FWPM_CONDITION_IP_SOURCE_ADDRESS;
+ rtarget = &FWPM_CONDITION_IP_DESTINATION_ADDRESS;
+ }
+ else
+ {
+ ltarget = &FWPM_CONDITION_IP_LOCAL_ADDRESS;
+ rtarget = &FWPM_CONDITION_IP_REMOTE_ADDRESS;
+ }
+ if (!ts2condition(local, ltarget, &conds, &count) ||
+ !ts2condition(remote, rtarget, &conds, &count))
+ {
+ free_conditions(conds, count);
+ return FALSE;
+ }
+
+ filter.numFilterConditions = count;
+ filter.filterCondition = conds;
+
+ res = FwpmFilterAdd0(this->handle, &filter, NULL, filter_id);
+ free_conditions(conds, count);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "installing %s%sbound WFP filter failed: 0x%08x",
+ fwd ? "forward " : "", inbound ? "in" : "out", res);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Install a set of policies in to the kernel
+ */
+static bool install_sps(private_kernel_wfp_ipsec_t *this,
+ entry_t *entry, GUID *context)
+{
+ enumerator_t *enumerator;
+ sp_entry_t *sp;
+
+ enumerator = array_create_enumerator(entry->sps);
+ while (enumerator->enumerate(enumerator, &sp))
+ {
+ /* inbound policy */
+ if (!install_sp(this, sp, context, TRUE, FALSE, &sp->policy_in))
+ {
+ enumerator->destroy(enumerator);
+ return FALSE;
+ }
+ /* outbound policy */
+ if (!install_sp(this, sp, context, FALSE, FALSE, &sp->policy_out))
+ {
+ enumerator->destroy(enumerator);
+ return FALSE;
+ }
+ if (context)
+ {
+ if (!sp->src->is_host(sp->src, entry->local) ||
+ !sp->dst->is_host(sp->dst, entry->remote))
+ {
+ /* inbound forward policy, from decapsulation */
+ if (!install_sp(this, sp, context,
+ TRUE, TRUE, &sp->policy_fwd_in))
+ {
+ enumerator->destroy(enumerator);
+ return FALSE;
+ }
+ /* outbound forward policy, to encapsulate */
+ if (!install_sp(this, sp, context,
+ FALSE, TRUE, &sp->policy_fwd_out))
+ {
+ enumerator->destroy(enumerator);
+ return FALSE;
+ }
+ }
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ return TRUE;
+}
+
+/**
+ * Convert a chunk_t to a WFP FWP_BYTE_BLOB
+ */
+static inline FWP_BYTE_BLOB chunk2blob(chunk_t chunk)
+{
+ return (FWP_BYTE_BLOB){
+ .size = chunk.len,
+ .data = chunk.ptr,
+ };
+}
+
+/**
+ * Convert an integrity_algorithm_t to a WFP IPSEC_AUTH_TRANFORM_ID0
+ */
+static bool alg2auth(integrity_algorithm_t alg,
+ IPSEC_SA_AUTH_INFORMATION0 *info)
+{
+ struct {
+ integrity_algorithm_t alg;
+ IPSEC_AUTH_TRANSFORM_ID0 transform;
+ } map[] = {
+ { AUTH_HMAC_MD5_96, IPSEC_AUTH_TRANSFORM_ID_HMAC_MD5_96 },
+ { AUTH_HMAC_SHA1_96, IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96 },
+ { AUTH_HMAC_SHA2_256_128, IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_256_128},
+ { AUTH_AES_128_GMAC, IPSEC_AUTH_TRANSFORM_ID_GCM_AES_128 },
+ { AUTH_AES_192_GMAC, IPSEC_AUTH_TRANSFORM_ID_GCM_AES_192 },
+ { AUTH_AES_256_GMAC, IPSEC_AUTH_TRANSFORM_ID_GCM_AES_256 },
+ };
+ int i;
+
+ for (i = 0; i < countof(map); i++)
+ {
+ if (map[i].alg == alg)
+ {
+ info->authTransform.authTransformId = map[i].transform;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Convert an encryption_algorithm_t to a WFP IPSEC_CIPHER_TRANFORM_ID0
+ */
+static bool alg2cipher(encryption_algorithm_t alg, int keylen,
+ IPSEC_SA_CIPHER_INFORMATION0 *info)
+{
+ struct {
+ encryption_algorithm_t alg;
+ int keylen;
+ IPSEC_CIPHER_TRANSFORM_ID0 transform;
+ } map[] = {
+ { ENCR_DES, 8, IPSEC_CIPHER_TRANSFORM_ID_CBC_DES },
+ { ENCR_3DES, 24, IPSEC_CIPHER_TRANSFORM_ID_CBC_3DES },
+ { ENCR_AES_CBC, 16, IPSEC_CIPHER_TRANSFORM_ID_AES_128 },
+ { ENCR_AES_CBC, 24, IPSEC_CIPHER_TRANSFORM_ID_AES_192 },
+ { ENCR_AES_CBC, 32, IPSEC_CIPHER_TRANSFORM_ID_AES_256 },
+ { ENCR_AES_GCM_ICV16, 20, IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_128 },
+ { ENCR_AES_GCM_ICV16, 28, IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_192 },
+ { ENCR_AES_GCM_ICV16, 36, IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_256 },
+ };
+ int i;
+
+ for (i = 0; i < countof(map); i++)
+ {
+ if (map[i].alg == alg && map[i].keylen == keylen)
+ {
+ info->cipherTransform.cipherTransformId = map[i].transform;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/**
+ * Get the integrity algorithm used for an AEAD transform
+ */
+static integrity_algorithm_t encr2integ(encryption_algorithm_t encr, int keylen)
+{
+ struct {
+ encryption_algorithm_t encr;
+ int keylen;
+ integrity_algorithm_t integ;
+ } map[] = {
+ { ENCR_NULL_AUTH_AES_GMAC, 20, AUTH_AES_128_GMAC },
+ { ENCR_NULL_AUTH_AES_GMAC, 28, AUTH_AES_192_GMAC },
+ { ENCR_NULL_AUTH_AES_GMAC, 36, AUTH_AES_256_GMAC },
+ { ENCR_AES_GCM_ICV16, 20, AUTH_AES_128_GMAC },
+ { ENCR_AES_GCM_ICV16, 28, AUTH_AES_192_GMAC },
+ { ENCR_AES_GCM_ICV16, 36, AUTH_AES_256_GMAC },
+ };
+ int i;
+
+ for (i = 0; i < countof(map); i++)
+ {
+ if (map[i].encr == encr && map[i].keylen == keylen)
+ {
+ return map[i].integ;
+ }
+ }
+ return AUTH_UNDEFINED;
+}
+
+/**
+ * Install a single SA
+ */
+static bool install_sa(private_kernel_wfp_ipsec_t *this, entry_t *entry,
+ bool inbound, sa_entry_t *sa, FWP_IP_VERSION version)
+{
+ IPSEC_SA_AUTH_AND_CIPHER_INFORMATION0 info = {};
+ IPSEC_SA0 ipsec = {
+ .spi = ntohl(sa->spi),
+ };
+ IPSEC_SA_BUNDLE0 bundle = {
+ .lifetime = {
+ .lifetimeSeconds = inbound ? entry->isa.lifetime
+ : entry->osa.lifetime,
+ },
+ .saList = &ipsec,
+ .numSAs = 1,
+ .ipVersion = version,
+ };
+ struct {
+ u_int16_t alg;
+ chunk_t key;
+ } integ = {}, encr = {};
+ DWORD res;
+
+ switch (sa->protocol)
+ {
+ case IPPROTO_AH:
+ ipsec.saTransformType = IPSEC_TRANSFORM_AH;
+ ipsec.ahInformation = &info.saAuthInformation;
+ integ.key = sa->integ.key;
+ integ.alg = sa->integ.alg;
+ break;
+ case IPPROTO_ESP:
+ if (sa->encr.alg == ENCR_NULL ||
+ sa->encr.alg == ENCR_NULL_AUTH_AES_GMAC)
+ {
+ ipsec.saTransformType = IPSEC_TRANSFORM_ESP_AUTH;
+ ipsec.espAuthInformation = &info.saAuthInformation;
+ }
+ else
+ {
+ ipsec.saTransformType = IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER;
+ ipsec.espAuthAndCipherInformation = &info;
+ encr.key = sa->encr.key;
+ encr.alg = sa->encr.alg;
+ }
+ if (encryption_algorithm_is_aead(sa->encr.alg))
+ {
+ integ.alg = encr2integ(sa->encr.alg, sa->encr.key.len);
+ integ.key = sa->encr.key;
+ }
+ else
+ {
+ integ.alg = sa->integ.alg;
+ integ.key = sa->integ.key;
+ }
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (integ.alg)
+ {
+ info.saAuthInformation.authKey = chunk2blob(integ.key);
+ if (!alg2auth(integ.alg, &info.saAuthInformation))
+ {
+ DBG1(DBG_KNL, "integrity algorithm %N not supported by WFP",
+ integrity_algorithm_names, integ.alg);
+ return FALSE;
+ }
+ }
+ if (encr.alg)
+ {
+ info.saCipherInformation.cipherKey = chunk2blob(encr.key);
+ if (!alg2cipher(encr.alg, encr.key.len, &info.saCipherInformation))
+ {
+ DBG1(DBG_KNL, "encryption algorithm %N not supported by WFP",
+ encryption_algorithm_names, encr.alg);
+ return FALSE;
+ }
+ }
+
+ if (inbound)
+ {
+ res = IPsecSaContextAddInbound0(this->handle, entry->sa_id, &bundle);
+ }
+ else
+ {
+ bundle.flags |= IPSEC_SA_BUNDLE_FLAG_ASSUME_UDP_CONTEXT_OUTBOUND;
+ res = IPsecSaContextAddOutbound0(this->handle, entry->sa_id, &bundle);
+ }
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "adding %sbound WFP SA failed: 0x%08x",
+ inbound ? "in" : "out", res);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Convert an IPv6 host address to WFP representation
+ */
+static void host2address6(host_t *host, void *out)
+{
+ u_int32_t *src, *dst = out;
+
+ src = (u_int32_t*)host->get_address(host).ptr;
+
+ dst[0] = untoh32(&src[3]);
+ dst[1] = untoh32(&src[2]);
+ dst[2] = untoh32(&src[1]);
+ dst[3] = untoh32(&src[0]);
+}
+
+/**
+ * Fill in traffic structure from entry addresses
+ */
+static bool hosts2traffic(private_kernel_wfp_ipsec_t *this,
+ host_t *l, host_t *r, IPSEC_TRAFFIC1 *traffic)
+{
+ if (l->get_family(l) != r->get_family(r))
+ {
+ return FALSE;
+ }
+ switch (l->get_family(l))
+ {
+ case AF_INET:
+ traffic->ipVersion = FWP_IP_VERSION_V4;
+ traffic->localV4Address = untoh32(l->get_address(l).ptr);
+ traffic->remoteV4Address = untoh32(r->get_address(r).ptr);
+ return TRUE;
+ case AF_INET6:
+ traffic->ipVersion = FWP_IP_VERSION_V6;
+ host2address6(l, &traffic->localV6Address);
+ host2address6(r, &traffic->remoteV6Address);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * Install SAs to the kernel
+ */
+static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry,
+ IPSEC_TRAFFIC_TYPE type)
+{
+ IPSEC_TRAFFIC1 traffic = {
+ .trafficType = type,
+ };
+ IPSEC_GETSPI1 spi = {
+ .inboundIpsecTraffic = {
+ .trafficType = type,
+ },
+ };
+ enumerator_t *enumerator;
+ sp_entry_t *sp;
+ DWORD res;
+
+ if (type == IPSEC_TRAFFIC_TYPE_TRANSPORT)
+ {
+ enumerator = array_create_enumerator(entry->sps);
+ if (enumerator->enumerate(enumerator, &sp))
+ {
+ traffic.ipsecFilterId = sp->policy_out;
+ spi.inboundIpsecTraffic.ipsecFilterId = sp->policy_in;
+ }
+ else
+ {
+ enumerator->destroy(enumerator);
+ return FALSE;
+ }
+ enumerator->destroy(enumerator);
+ }
+ else
+ {
+ traffic.tunnelPolicyId = entry->provider;
+ spi.inboundIpsecTraffic.tunnelPolicyId = entry->provider;
+ }
+
+ if (!hosts2traffic(this, entry->local, entry->remote, &traffic))
+ {
+ return FALSE;
+ }
+
+ res = IPsecSaContextCreate1(this->handle, &traffic, NULL, NULL,
+ &entry->sa_id);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "creating WFP SA context failed: 0x%08x", res);
+ return FALSE;
+ }
+
+ memcpy(spi.inboundIpsecTraffic.localV6Address, traffic.localV6Address,
+ sizeof(traffic.localV6Address));
+ memcpy(spi.inboundIpsecTraffic.remoteV6Address, traffic.remoteV6Address,
+ sizeof(traffic.remoteV6Address));
+ spi.ipVersion = traffic.ipVersion;
+
+ res = IPsecSaContextSetSpi0(this->handle, entry->sa_id, &spi,
+ ntohl(entry->isa.spi));
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "setting WFP SA SPI failed: 0x%08x", res);
+ IPsecSaContextDeleteById0(this->handle, entry->sa_id);
+ entry->sa_id = 0;
+ return FALSE;
+ }
+
+ if (!install_sa(this, entry, TRUE, &entry->isa, spi.ipVersion) ||
+ !install_sa(this, entry, FALSE, &entry->osa, spi.ipVersion))
+ {
+ IPsecSaContextDeleteById0(this->handle, entry->sa_id);
+ entry->sa_id = 0;
+ return FALSE;
+ }
+
+ if (entry->encap)
+ {
+ IPSEC_V4_UDP_ENCAPSULATION0 encap = {
+ .localUdpEncapPort = entry->local->get_port(entry->local),
+ .remoteUdpEncapPort = entry->remote->get_port(entry->remote),
+ };
+ IPSEC_SA_CONTEXT1 *ctx;
+
+ res = IPsecSaContextGetById1(this->handle, entry->sa_id, &ctx);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "getting WFP SA for UDP encap failed: 0x%08x", res);
+ IPsecSaContextDeleteById0(this->handle, entry->sa_id);
+ entry->sa_id = 0;
+ return FALSE;
+ }
+ ctx->inboundSa->udpEncapsulation = &encap;
+ ctx->outboundSa->udpEncapsulation = &encap;
+
+ res = IPsecSaContextUpdate0(this->handle,
+ IPSEC_SA_DETAILS_UPDATE_UDP_ENCAPSULATION, ctx);
+ FwpmFreeMemory0((void**)&ctx);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "enable WFP UDP encap failed: 0x%08x", res);
+ IPsecSaContextDeleteById0(this->handle, entry->sa_id);
+ entry->sa_id = 0;
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * Install a transport mode SA/SP set to the kernel
+ */
+static bool install_transport(private_kernel_wfp_ipsec_t *this, entry_t *entry)
+{
+ if (install_sps(this, entry, NULL) &&
+ install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TRANSPORT))
+ {
+ return TRUE;
+ }
+ cleanup_policies(this, entry);
+ return FALSE;
+}
+
+/**
+ * Generate a new GUID, random
+ */
+static bool generate_guid(private_kernel_wfp_ipsec_t *this, GUID *guid)
+{
+ bool ok;
+ rng_t *rng;
+
+ rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
+ if (!rng)
+ {
+ return FALSE;
+ }
+ ok = rng->get_bytes(rng, sizeof(GUID), (u_int8_t*)guid);
+ rng->destroy(rng);
+ return ok;
+}
+
+/**
+ * Register a dummy tunnel provider to associate tunnel filters to
+ */
+static bool add_tunnel_provider(private_kernel_wfp_ipsec_t *this,
+ entry_t *entry, GUID *guid, UINT64 *luid)
+{
+ DWORD res;
+
+ IPSEC_AUTH_TRANSFORM0 transform = {
+ /* Create any valid proposal. This is actually not used, as we
+ * don't create an SA from this information. */
+ .authTransformId = IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96,
+ };
+ IPSEC_SA_TRANSFORM0 transforms = {
+ .ipsecTransformType = IPSEC_TRANSFORM_ESP_AUTH,
+ .espAuthTransform = &transform,
+ };
+ IPSEC_PROPOSAL0 proposal = {
+ .lifetime = {
+ /* We need a valid lifetime, even if we don't create any SA
+ * from these values. Pick some values accepted. */
+ .lifetimeSeconds = 0xFFFF,
+ .lifetimeKilobytes = 0xFFFFFFFF,
+ .lifetimePackets = 0xFFFFFFFF,
+ },
+ .numSaTransforms = 1,
+ .saTransforms = &transforms,
+ };
+ IPSEC_TUNNEL_POLICY0 policy = {
+ .numIpsecProposals = 1,
+ .ipsecProposals = &proposal,
+ .saIdleTimeout = {
+ /* not used, set to lifetime for maximum */
+ .idleTimeoutSeconds = proposal.lifetime.lifetimeSeconds,
+ .idleTimeoutSecondsFailOver = proposal.lifetime.lifetimeSeconds,
+ },
+ };
+ FWPM_PROVIDER_CONTEXT0 qm = {
+ .displayData = {
+ .name = L"charon tunnel provider",
+ },
+ .providerKey = (GUID*)&this->provider.providerKey,
+ .type = FWPM_IPSEC_IKE_QM_TUNNEL_CONTEXT,
+ .ikeQmTunnelPolicy = &policy,
+ };
+
+ switch (entry->local->get_family(entry->local))
+ {
+ case AF_INET:
+ policy.tunnelEndpoints.ipVersion = FWP_IP_VERSION_V4;
+ policy.tunnelEndpoints.localV4Address =
+ untoh32(entry->local->get_address(entry->local).ptr);
+ policy.tunnelEndpoints.remoteV4Address =
+ untoh32(entry->remote->get_address(entry->remote).ptr);
+ break;
+ case AF_INET6:
+ policy.tunnelEndpoints.ipVersion = FWP_IP_VERSION_V6;
+ host2address6(entry->local, &policy.tunnelEndpoints.localV6Address);
+ host2address6(entry->remote, &policy.tunnelEndpoints.remoteV6Address);
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!generate_guid(this, &qm.providerContextKey))
+ {
+ return FALSE;
+ }
+
+ res = FwpmProviderContextAdd0(this->handle, &qm, NULL, luid);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "adding provider context failed: 0x%08x", res);
+ return FALSE;
+ }
+ *guid = qm.providerContextKey;
+ return TRUE;
+}
+
+/**
+ * Install tunnel mode SPs to the kernel
+ */
+static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry)
+{
+ GUID guid;
+
+ if (!add_tunnel_provider(this, entry, &guid, &entry->provider))
+ {
+ return FALSE;
+ }
+ if (!install_sps(this, entry, &guid))
+ {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Reduce refcount, or uninstall a route if all refs gone
+ */
+static bool uninstall_route(private_kernel_wfp_ipsec_t *this,
+ host_t *dst, u_int8_t mask, host_t *src, host_t *gtw)
+{
+ route_t *route, key = {
+ .dst = dst,
+ .mask = mask,
+ .src = src,
+ };
+ char *name;
+ bool res = FALSE;
+
+ this->mutex->lock(this->mutex);
+ route = this->routes->get(this->routes, &key);
+ if (route)
+ {
+ if (--route->refs == 0)
+ {
+ if (hydra->kernel_interface->get_interface(hydra->kernel_interface,
+ src, &name))
+ {
+ res = hydra->kernel_interface->del_route(hydra->kernel_interface,
+ dst->get_address(dst), mask, gtw, src, name) == SUCCESS;
+ free(name);
+ }
+ route = this->routes->remove(this->routes, route);
+ if (route)
+ {
+ destroy_route(route);
+ }
+ }
+ else
+ {
+ res = TRUE;
+ }
+ }
+ this->mutex->unlock(this->mutex);
+
+ return res;
+}
+
+/**
+ * Install a single route, or refcount if exists
+ */
+static bool install_route(private_kernel_wfp_ipsec_t *this,
+ host_t *dst, u_int8_t mask, host_t *src, host_t *gtw)
+{
+ route_t *route, key = {
+ .dst = dst,
+ .mask = mask,
+ .src = src,
+ };
+ char *name;
+ bool res = FALSE;
+
+ this->mutex->lock(this->mutex);
+ route = this->routes->get(this->routes, &key);
+ if (route)
+ {
+ route->refs++;
+ res = TRUE;
+ }
+ else
+ {
+ if (hydra->kernel_interface->get_interface(hydra->kernel_interface,
+ src, &name))
+ {
+ if (hydra->kernel_interface->add_route(hydra->kernel_interface,
+ dst->get_address(dst), mask, gtw, src, name) == SUCCESS)
+ {
+ INIT(route,
+ .dst = dst->clone(dst),
+ .mask = mask,
+ .src = src->clone(src),
+ .gtw = gtw ? gtw->clone(gtw) : NULL,
+ .refs = 1,
+ );
+ route = this->routes->put(this->routes, route, route);
+ if (route)
+ {
+ destroy_route(route);
+ }
+ res = TRUE;
+ }
+ free(name);
+ }
+ }
+ this->mutex->unlock(this->mutex);
+
+ return res;
+}
+
+/**
+ * (Un)-install a single route
+ */
+static bool manage_route(private_kernel_wfp_ipsec_t *this,
+ host_t *local, host_t *remote,
+ traffic_selector_t *src_ts, traffic_selector_t *dst_ts,
+ bool add)
+{
+ host_t *src, *dst, *gtw;
+ u_int8_t mask;
+ bool done;
+
+ if (!dst_ts->to_subnet(dst_ts, &dst, &mask))
+ {
+ return FALSE;
+ }
+ if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface,
+ src_ts, &src, NULL) != SUCCESS)
+ {
+ dst->destroy(dst);
+ return FALSE;
+ }
+ gtw = hydra->kernel_interface->get_nexthop(hydra->kernel_interface,
+ remote, local);
+ if (add)
+ {
+ done = install_route(this, dst, mask, src, gtw);
+ }
+ else
+ {
+ done = uninstall_route(this, dst, mask, src, gtw);
+ }
+ dst->destroy(dst);
+ src->destroy(src);
+ DESTROY_IF(gtw);
+
+ if (!done)
+ {
+ DBG1(DBG_KNL, "%sinstalling route for policy %R === %R failed",
+ add ? "" : "un", src_ts, dst_ts);
+ }
+ return done;
+}
+
+/**
+ * (Un)-install routes for IPsec policies
+ */
+static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry,
+ bool add)
+{
+ enumerator_t *enumerator;
+ sp_entry_t *sp;
+
+ enumerator = array_create_enumerator(entry->sps);
+ while (enumerator->enumerate(enumerator, &sp))
+ {
+ if (add && sp->route)
+ {
+ continue;
+ }
+ if (!add && !sp->route)
+ {
+ continue;
+ }
+ if (manage_route(this, entry->local, entry->remote,
+ sp->src, sp->dst, add))
+ {
+ sp->route = add;
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ return TRUE;
+}
+
+/**
+ * Install a tunnel mode SA/SP set to the kernel
+ */
+static bool install_tunnel(private_kernel_wfp_ipsec_t *this, entry_t *entry)
+{
+ if (install_tunnel_sps(this, entry) &&
+ manage_routes(this, entry, TRUE) &&
+ install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TUNNEL))
+ {
+ return TRUE;
+ }
+ cleanup_policies(this, entry);
+ return FALSE;
+}
+
+/**
+ * Install a SA/SP set to the kernel
+ */
+static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry)
+{
+ switch (entry->mode)
+ {
+ case MODE_TRANSPORT:
+ return install_transport(this, entry);
+ case MODE_TUNNEL:
+ return install_tunnel(this, entry);
+ case MODE_BEET:
+ default:
+ return FALSE;
+ }
+}
+
+/**
+ * Installed trap entry
+ */
+typedef struct {
+ /** reqid this trap is installed for */
+ u_int32_t reqid;
+ /** is this a forward policy trap for tunnel mode? */
+ bool fwd;
+ /** do we have installed a route for this trap policy? */
+ bool route;
+ /** local address of associated route */
+ host_t *local;
+ /** remote address of associated route */
+ host_t *remote;
+ /** src traffic selector */
+ traffic_selector_t *src;
+ /** dst traffic selector */
+ traffic_selector_t *dst;
+ /** LUID of installed tunnel policy filter */
+ UINT64 filter_id;
+} trap_t;
+
+/**
+ * Destroy a trap entry
+ */
+static void destroy_trap(trap_t *this)
+{
+ this->local->destroy(this->local);
+ this->remote->destroy(this->remote);
+ this->src->destroy(this->src);
+ this->dst->destroy(this->dst);
+ free(this);
+}
+
+/**
+ * Hashtable equals function for traps
+ */
+static bool equals_trap(trap_t *a, trap_t *b)
+{
+ return a->filter_id == b->filter_id;
+}
+
+/**
+ * Hashtable hash function for traps
+ */
+static u_int hash_trap(trap_t *trap)
+{
+ return chunk_hash(chunk_from_thing(trap->filter_id));
+}
+
+/**
+ * Send an acquire for an installed trap filter
+ */
+static void acquire(private_kernel_wfp_ipsec_t *this, UINT64 filter_id,
+ traffic_selector_t *src, traffic_selector_t *dst)
+{
+ u_int32_t reqid = 0;
+ trap_t *trap, key = {
+ .filter_id = filter_id,
+ };
+
+ this->mutex->lock(this->mutex);
+ trap = this->traps->get(this->traps, &key);
+ if (trap)
+ {
+ reqid = trap->reqid;
+ }
+ this->mutex->unlock(this->mutex);
+
+ if (reqid)
+ {
+ src = src ? src->clone(src) : NULL;
+ dst = dst ? dst->clone(dst) : NULL;
+ hydra->kernel_interface->acquire(hydra->kernel_interface, reqid,
+ src, dst);
+ }
+}
+
+/**
+ * Create a single host traffic selector from an FWP address definition
+ */
+static traffic_selector_t *addr2ts(FWP_IP_VERSION version, void *data,
+ u_int8_t protocol, u_int16_t from_port, u_int16_t to_port)
+{
+ ts_type_t type;
+ UINT32 ints[4];
+ chunk_t addr;
+
+ switch (version)
+ {
+ case FWP_IP_VERSION_V4:
+ ints[0] = untoh32(data);
+ addr = chunk_from_thing(ints[0]);
+ type = TS_IPV4_ADDR_RANGE;
+ break;
+ case FWP_IP_VERSION_V6:
+ ints[3] = untoh32(data);
+ ints[2] = untoh32(data + 4);
+ ints[1] = untoh32(data + 8);
+ ints[0] = untoh32(data + 12);
+ addr = chunk_from_thing(ints);
+ type = TS_IPV6_ADDR_RANGE;
+ break;
+ default:
+ return NULL;
+ }
+ return traffic_selector_create_from_bytes(protocol, type, addr, from_port,
+ addr, to_port);
+}
+
+/**
+ * FwpmNetEventSubscribe0() callback
+ */
+static void event_callback(private_kernel_wfp_ipsec_t *this,
+ const FWPM_NET_EVENT1 *event)
+{
+ traffic_selector_t *local = NULL, *remote = NULL;
+ u_int8_t protocol = 0;
+ u_int16_t from_local = 0, to_local = 65535;
+ u_int16_t from_remote = 0, to_remote = 65535;
+
+ if ((event->header.flags & FWPM_NET_EVENT_FLAG_LOCAL_ADDR_SET) &&
+ (event->header.flags & FWPM_NET_EVENT_FLAG_REMOTE_ADDR_SET))
+ {
+ if (event->header.flags & FWPM_NET_EVENT_FLAG_LOCAL_PORT_SET)
+ {
+ from_local = to_local = event->header.localPort;
+ }
+ if (event->header.flags & FWPM_NET_EVENT_FLAG_LOCAL_PORT_SET)
+ {
+ from_remote = to_remote = event->header.remotePort;
+ }
+ if (event->header.flags & FWPM_NET_EVENT_FLAG_IP_PROTOCOL_SET)
+ {
+ protocol = event->header.ipProtocol;
+ }
+
+ local = addr2ts(event->header.ipVersion,
+ (void*)&event->header.localAddrV6,
+ protocol, from_local, to_local);
+ remote = addr2ts(event->header.ipVersion,
+ (void*)&event->header.remoteAddrV6,
+ protocol, from_remote, to_remote);
+ }
+
+ switch (event->type)
+ {
+ case FWPM_NET_EVENT_TYPE_CLASSIFY_DROP:
+ acquire(this, event->classifyDrop->filterId, local, remote);
+ break;
+ case FWPM_NET_EVENT_TYPE_IKEEXT_MM_FAILURE:
+ case FWPM_NET_EVENT_TYPE_IKEEXT_QM_FAILURE:
+ case FWPM_NET_EVENT_TYPE_IKEEXT_EM_FAILURE:
+ case FWPM_NET_EVENT_TYPE_IPSEC_KERNEL_DROP:
+ DBG1(DBG_KNL, "IPsec kernel drop: %R === %R, error 0x%08x, "
+ "SPI 0x%08x, %s filterId %llu", local, remote,
+ event->ipsecDrop->failureStatus, event->ipsecDrop->spi,
+ event->ipsecDrop->direction ? "in" : "out",
+ event->ipsecDrop->filterId);
+ break;
+ case FWPM_NET_EVENT_TYPE_IPSEC_DOSP_DROP:
+ default:
+ break;
+ }
+
+ DESTROY_IF(local);
+ DESTROY_IF(remote);
+}
+
+/**
+ * Register for net events
+ */
+static bool register_events(private_kernel_wfp_ipsec_t *this)
+{
+ FWPM_NET_EVENT_SUBSCRIPTION0 subscription = {};
+ DWORD res;
+
+ res = FwpmNetEventSubscribe0(this->handle, &subscription,
+ (void*)event_callback, this, &this->event);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "registering for WFP events failed: 0x%08x", res);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Install a trap policy to kernel
+ */
+static bool install_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap)
+{
+ FWPM_FILTER_CONDITION0 *conds = NULL;
+ int count = 0;
+ DWORD res;
+ const GUID *starget, *dtarget;
+ UINT64 weight = 0x000000000000ff00;
+ FWPM_FILTER0 filter = {
+ .displayData = {
+ .name = L"charon IPsec trap",
+ },
+ .action = {
+ .type = FWP_ACTION_BLOCK,
+ },
+ .weight = {
+ .type = FWP_UINT64,
+ .uint64 = &weight,
+ },
+ };
+
+ if (trap->fwd)
+ {
+ if (trap->src->get_type(trap->src) == TS_IPV4_ADDR_RANGE)
+ {
+ filter.layerKey = FWPM_LAYER_IPFORWARD_V4;
+ }
+ else
+ {
+ filter.layerKey = FWPM_LAYER_IPFORWARD_V6;
+ }
+ starget = &FWPM_CONDITION_IP_SOURCE_ADDRESS;
+ dtarget = &FWPM_CONDITION_IP_DESTINATION_ADDRESS;
+ }
+ else
+ {
+ if (trap->src->get_type(trap->src) == TS_IPV4_ADDR_RANGE)
+ {
+ filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
+ }
+ else
+ {
+ filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V6;
+ }
+ starget = &FWPM_CONDITION_IP_LOCAL_ADDRESS;
+ dtarget = &FWPM_CONDITION_IP_REMOTE_ADDRESS;
+ }
+
+ if (!ts2condition(trap->src, starget, &conds, &count) ||
+ !ts2condition(trap->dst, dtarget, &conds, &count))
+ {
+ free_conditions(conds, count);
+ return FALSE;
+ }
+
+ filter.numFilterConditions = count;
+ filter.filterCondition = conds;
+
+ res = FwpmFilterAdd0(this->handle, &filter, NULL, &trap->filter_id);
+ free_conditions(conds, count);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "installing WFP trap filter failed: 0x%08x", res);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Uninstall a trap policy from kernel
+ */
+static bool uninstall_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap)
+{
+ DWORD res;
+
+ res = FwpmFilterDeleteById0(this->handle, trap->filter_id);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "uninstalling WFP trap filter failed: 0x%08x", res);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * Create and install a new trap entry
+ */
+static bool add_trap(private_kernel_wfp_ipsec_t *this,
+ u_int32_t reqid, bool fwd, host_t *local, host_t *remote,
+ traffic_selector_t *src, traffic_selector_t *dst)
+{
+ trap_t *trap;
+
+ INIT(trap,
+ .reqid = reqid,
+ .fwd = fwd,
+ .src = src->clone(src),
+ .dst = dst->clone(dst),
+ .local = local->clone(local),
+ .remote = remote->clone(remote),
+ );
+
+ if (!install_trap(this, trap))
+ {
+ destroy_trap(trap);
+ return FALSE;
+ }
+
+ trap->route = manage_route(this, local, remote, src, dst, TRUE);
+
+ this->mutex->lock(this->mutex);
+ this->traps->put(this->traps, trap, trap);
+ this->mutex->unlock(this->mutex);
+ return TRUE;
+}
+
+/**
+ * Uninstall and remove a new trap entry
+ */
+static bool remove_trap(private_kernel_wfp_ipsec_t *this,
+ u_int32_t reqid, bool fwd,
+ traffic_selector_t *src, traffic_selector_t *dst)
+{
+ enumerator_t *enumerator;
+ trap_t *trap, *found = NULL;
+
+ this->mutex->lock(this->mutex);
+ enumerator = this->traps->create_enumerator(this->traps);
+ while (enumerator->enumerate(enumerator, NULL, &trap))
+ {
+ if (reqid == trap->reqid &&
+ fwd == trap->fwd &&
+ src->equals(src, trap->src) &&
+ dst->equals(dst, trap->dst))
+ {
+ this->traps->remove_at(this->traps, enumerator);
+ found = trap;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->mutex->unlock(this->mutex);
+
+ if (found)
+ {
+ if (trap->route)
+ {
+ trap->route = !manage_route(this, trap->local, trap->remote,
+ src, dst, FALSE);
+ }
+ uninstall_trap(this, found);
+ destroy_trap(found);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
+ private_kernel_wfp_ipsec_t *this)
+{
+ return KERNEL_ESP_V3_TFC | KERNEL_NO_POLICY_UPDATES;
+}
+
+/**
+ * Initialize seeds for SPI generation
+ */
+static bool init_spi(private_kernel_wfp_ipsec_t *this)
+{
+ bool ok = TRUE;
+ rng_t *rng;
+
+ rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
+ if (!rng)
+ {
+ return FALSE;
+ }
+ ok = rng->get_bytes(rng, sizeof(this->nextspi), (u_int8_t*)&this->nextspi);
+ if (ok)
+ {
+ ok = rng->get_bytes(rng, sizeof(this->mixspi), (u_int8_t*)&this->mixspi);
+ }
+ rng->destroy(rng);
+ return ok;
+}
+
+/**
+ * Map an integer x with a one-to-one function using quadratic residues.
+ */
+static u_int permute(u_int x, u_int p)
+{
+ u_int qr;
+
+ x = x % p;
+ qr = ((u_int64_t)x * x) % p;
+ if (x <= p / 2)
+ {
+ return qr;
+ }
+ return p - qr;
+}
+
+METHOD(kernel_ipsec_t, get_spi, status_t,
+ private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
+ u_int8_t protocol, u_int32_t reqid, u_int32_t *spi)
+{
+ /* To avoid sequencial SPIs, we use a one-to-one permuation function on
+ * an incrementing counter, that is a full period PRNG for the range we
+ * allocate SPIs in. We add some randomness using a fixed XOR and start
+ * the counter at random position. This is not cryptographically safe,
+ * but that is actually not required.
+ * The selected prime should be smaller than the range we allocate SPIs
+ * in, and it must satisfy p % 4 == 3 to map x > p/2 using p - qr. */
+ static const u_int p = 268435399, offset = 0xc0000000;
+
+ *spi = htonl(offset + permute(ref_get(&this->nextspi) ^ this->mixspi, p));
+ return SUCCESS;
+}
+
+METHOD(kernel_ipsec_t, get_cpi, status_t,
+ private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
+ u_int32_t reqid, u_int16_t *cpi)
+{
+ return NOT_SUPPORTED;
+}
+
+/**
+ * Data for an expire callback job
+ */
+typedef struct {
+ /* backref to kernel backend */
+ private_kernel_wfp_ipsec_t *this;
+ /* SPI of expiring SA */
+ u_int32_t spi;
+ /* destination address of expiring SA */
+ host_t *dst;
+ /* is this a hard expire, or a rekey request? */
+ bool hard;
+} expire_data_t;
+
+/**
+ * Clean up expire data
+ */
+static void expire_data_destroy(expire_data_t *data)
+{
+ data->dst->destroy(data->dst);
+ free(data);
+}
+
+/**
+ * Callback job for SA expiration
+ */
+static job_requeue_t expire_job(expire_data_t *data)
+{
+ private_kernel_wfp_ipsec_t *this = data->this;
+ u_int32_t reqid = 0;
+ u_int8_t protocol;
+ entry_t *entry;
+ sa_entry_t key = {
+ .spi = data->spi,
+ .dst = data->dst,
+ };
+
+ if (data->hard)
+ {
+ this->mutex->lock(this->mutex);
+ entry = this->isas->remove(this->isas, &key);
+ this->mutex->unlock(this->mutex);
+ if (entry)
+ {
+ protocol = entry->isa.protocol;
+ reqid = entry->reqid;
+ if (entry->osa.dst)
+ {
+ key.dst = entry->osa.dst;
+ key.spi = entry->osa.spi;
+ this->osas->remove(this->osas, &key);
+ }
+ entry_destroy(this, entry);
+ }
+ }
+ else
+ {
+ this->mutex->lock(this->mutex);
+ entry = this->isas->get(this->isas, &key);
+ if (entry)
+ {
+ protocol = entry->isa.protocol;
+ reqid = entry->reqid;
+ }
+ this->mutex->unlock(this->mutex);
+ }
+
+ if (reqid)
+ {
+ hydra->kernel_interface->expire(hydra->kernel_interface,
+ reqid, protocol, data->spi, data->hard);
+ }
+
+ return JOB_REQUEUE_NONE;
+}
+
+/**
+ * Schedule an expire event for an SA
+ */
+static void schedule_expire(private_kernel_wfp_ipsec_t *this, u_int32_t spi,
+ host_t *dst, u_int32_t lifetime, bool hard)
+{
+ expire_data_t *data;
+
+ INIT(data,
+ .this = this,
+ .spi = spi,
+ .dst = dst->clone(dst),
+ .hard = hard,
+ );
+
+ lib->scheduler->schedule_job(lib->scheduler, (job_t*)
+ callback_job_create((void*)expire_job, data,
+ (void*)expire_data_destroy, NULL),
+ lifetime);
+}
+
+METHOD(kernel_ipsec_t, add_sa, status_t,
+ private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
+ u_int32_t spi, u_int8_t protocol, u_int32_t reqid, mark_t mark,
+ u_int32_t tfc, lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key,
+ u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, u_int16_t ipcomp,
+ u_int16_t cpi, bool initiator, bool encap, bool esn, bool inbound,
+ traffic_selector_t *src_ts, traffic_selector_t *dst_ts)
+{
+ host_t *local, *remote;
+ entry_t *entry;
+
+ if (inbound)
+ {
+ /* comes first, create new entry */
+ local = dst->clone(dst);
+ remote = src->clone(src);
+
+ INIT(entry,
+ .reqid = reqid,
+ .isa = {
+ .spi = spi,
+ .dst = local,
+ .protocol = protocol,
+ .lifetime = lifetime->time.life,
+ .encr = {
+ .alg = enc_alg,
+ .key = chunk_clone(enc_key),
+ },
+ .integ = {
+ .alg = int_alg,
+ .key = chunk_clone(int_key),
+ },
+ },
+ .sps = array_create(0, 0),
+ .local = local,
+ .remote = remote,
+ .mode = mode,
+ .encap = encap,
+ );
+
+ if (lifetime->time.life)
+ {
+ schedule_expire(this, spi, local, lifetime->time.life, TRUE);
+ }
+ if (lifetime->time.rekey && lifetime->time.rekey != lifetime->time.life)
+ {
+ schedule_expire(this, spi, local, lifetime->time.rekey, FALSE);
+ }
+
+ this->mutex->lock(this->mutex);
+ this->tsas->put(this->tsas, (void*)(uintptr_t)reqid, entry);
+ this->isas->put(this->isas, &entry->isa, entry);
+ this->mutex->unlock(this->mutex);
+ }
+ else
+ {
+ /* comes after inbound, update entry */
+ this->mutex->lock(this->mutex);
+ entry = this->tsas->remove(this->tsas, (void*)(uintptr_t)reqid);
+ this->mutex->unlock(this->mutex);
+
+ if (!entry)
+ {
+ DBG1(DBG_KNL, "adding outbound SA failed, no inbound SA found "
+ "for reqid %u ", reqid);
+ return NOT_FOUND;
+ }
+ /* TODO: should we check for local/remote, mode etc.? */
+
+ entry->osa = (sa_entry_t){
+ .spi = spi,
+ .dst = entry->remote,
+ .protocol = protocol,
+ .lifetime = lifetime->time.life,
+ .encr = {
+ .alg = enc_alg,
+ .key = chunk_clone(enc_key),
+ },
+ .integ = {
+ .alg = int_alg,
+ .key = chunk_clone(int_key),
+ },
+ };
+
+ this->mutex->lock(this->mutex);
+ this->osas->put(this->osas, &entry->osa, entry);
+ this->mutex->unlock(this->mutex);
+ }
+
+ return SUCCESS;
+}
+
+METHOD(kernel_ipsec_t, update_sa, status_t,
+ private_kernel_wfp_ipsec_t *this, u_int32_t spi, u_int8_t protocol,
+ u_int16_t cpi, host_t *src, host_t *dst, host_t *new_src, host_t *new_dst,
+ bool encap, bool new_encap, mark_t mark)
+{
+ entry_t *entry;
+ sa_entry_t key = {
+ .dst = dst,
+ .spi = spi,
+ };
+ UINT64 sa_id = 0;
+ IPSEC_SA_CONTEXT1 *ctx;
+ IPSEC_V4_UDP_ENCAPSULATION0 ports;
+ UINT32 flags = IPSEC_SA_DETAILS_UPDATE_TRAFFIC;
+ DWORD res;
+
+ this->mutex->lock(this->mutex);
+ entry = this->osas->get(this->osas, &key);
+ this->mutex->unlock(this->mutex);
+
+ if (entry)
+ {
+ /* outbound entry, nothing to do */
+ return SUCCESS;
+ }
+
+ this->mutex->lock(this->mutex);
+ entry = this->isas->get(this->isas, &key);
+ if (entry)
+ {
+ /* inbound entry, do update */
+ sa_id = entry->sa_id;
+ ports.localUdpEncapPort = entry->local->get_port(entry->local);
+ ports.remoteUdpEncapPort = entry->remote->get_port(entry->remote);
+ }
+ this->mutex->unlock(this->mutex);
+
+ if (!sa_id)
+ {
+ return NOT_FOUND;
+ }
+
+ res = IPsecSaContextGetById1(this->handle, sa_id, &ctx);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "getting WFP SA context for updated failed: 0x%08x", res);
+ return FAILED;
+ }
+ if (!hosts2traffic(this, new_dst, new_src, &ctx->inboundSa->traffic) ||
+ !hosts2traffic(this, new_dst, new_src, &ctx->outboundSa->traffic))
+ {
+ FwpmFreeMemory0((void**)&ctx);
+ return FAILED;
+ }
+
+ if (new_encap != encap)
+ {
+ if (new_encap)
+ {
+ ctx->inboundSa->udpEncapsulation = &ports;
+ ctx->outboundSa->udpEncapsulation = &ports;
+ }
+ else
+ {
+ ctx->inboundSa->udpEncapsulation = NULL;
+ ctx->outboundSa->udpEncapsulation = NULL;
+ }
+ flags |= IPSEC_SA_DETAILS_UPDATE_UDP_ENCAPSULATION;
+ }
+
+ res = IPsecSaContextUpdate0(this->handle, flags, ctx);
+ FwpmFreeMemory0((void**)&ctx);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "updating WFP SA context failed: 0x%08x", res);
+ return FAILED;
+ }
+
+ this->mutex->lock(this->mutex);
+ entry = this->isas->remove(this->isas, &key);
+ if (entry)
+ {
+ key.spi = entry->osa.spi;
+ key.dst = entry->osa.dst;
+ this->osas->remove(this->osas, &key);
+
+ entry->local->destroy(entry->local);
+ entry->remote->destroy(entry->remote);
+ entry->local = new_dst->clone(new_dst);
+ entry->remote = new_src->clone(new_src);
+ entry->isa.dst = entry->local;
+ entry->osa.dst = entry->remote;
+
+ this->isas->put(this->isas, &entry->isa, entry);
+ this->osas->put(this->osas, &entry->osa, entry);
+
+ manage_routes(this, entry, FALSE);
+ manage_routes(this, entry, TRUE);
+ }
+ this->mutex->unlock(this->mutex);
+
+ return SUCCESS;
+}
+
+METHOD(kernel_ipsec_t, query_sa, status_t,
+ private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
+ u_int32_t spi, u_int8_t protocol, mark_t mark, u_int64_t *bytes,
+ u_int64_t *packets, time_t *time)
+{
+ /* It does not seem that WFP provides any means of getting per-SA traffic
+ * statistics. IPsecGetStatistics0/1() provides global stats, and
+ * IPsecSaContextEnum0/1() and IPsecSaEnum0/1() return the configured
+ * values only. */
+ return NOT_SUPPORTED;
+}
+
+METHOD(kernel_ipsec_t, del_sa, status_t,
+ private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
+ u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark)
+{
+ entry_t *entry;
+ sa_entry_t key = {
+ .dst = dst,
+ .spi = spi,
+ };
+
+ this->mutex->lock(this->mutex);
+ entry = this->isas->remove(this->isas, &key);
+ this->mutex->unlock(this->mutex);
+
+ if (entry)
+ {
+ /* keep entry until removal of outbound */
+ return SUCCESS;
+ }
+
+ this->mutex->lock(this->mutex);
+ entry = this->osas->remove(this->osas, &key);
+ this->mutex->unlock(this->mutex);
+
+ if (entry)
+ {
+ entry_destroy(this, entry);
+ return SUCCESS;
+ }
+
+ return NOT_FOUND;
+}
+
+METHOD(kernel_ipsec_t, flush_sas, status_t,
+ private_kernel_wfp_ipsec_t *this)
+{
+ return NOT_SUPPORTED;
+}
+
+METHOD(kernel_ipsec_t, add_policy, status_t,
+ private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
+ traffic_selector_t *src_ts, traffic_selector_t *dst_ts,
+ policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark,
+ policy_priority_t priority)
+{
+ status_t status = SUCCESS;
+ entry_t *entry;
+ sp_entry_t *sp;
+ sa_entry_t key = {
+ .spi = sa->esp.use ? sa->esp.spi : sa->ah.spi,
+ .dst = dst,
+ };
+
+ if (sa->esp.use && sa->ah.use)
+ {
+ return NOT_SUPPORTED;
+ }
+
+ switch (type)
+ {
+ case POLICY_IPSEC:
+ break;
+ case POLICY_PASS:
+ case POLICY_DROP:
+ return NOT_SUPPORTED;
+ }
+
+ switch (direction)
+ {
+ case POLICY_OUT:
+ break;
+ case POLICY_IN:
+ case POLICY_FWD:
+ /* not required */
+ return SUCCESS;
+ default:
+ return NOT_SUPPORTED;
+ }
+
+ switch (priority)
+ {
+ case POLICY_PRIORITY_DEFAULT:
+ break;
+ case POLICY_PRIORITY_ROUTED:
+ if (!add_trap(this, sa->reqid, FALSE, src, dst, src_ts, dst_ts))
+ {
+ return FAILED;
+ }
+ if (sa->mode == MODE_TUNNEL)
+ {
+ if (!add_trap(this, sa->reqid, TRUE, src, dst, src_ts, dst_ts))
+ {
+ return FAILED;
+ }
+ }
+ return SUCCESS;
+ case POLICY_PRIORITY_FALLBACK:
+ default:
+ return NOT_SUPPORTED;
+ }
+
+ this->mutex->lock(this->mutex);
+ entry = this->osas->get(this->osas, &key);
+ if (entry)
+ {
+ if (sa->mode == MODE_TUNNEL || array_count(entry->sps) == 0)
+ {
+ INIT(sp,
+ .src = src_ts->clone(src_ts),
+ .dst = dst_ts->clone(dst_ts),
+ );
+ array_insert(entry->sps, -1, sp);
+ if (array_count(entry->sps) == sa->policy_count)
+ {
+ if (!install(this, entry))
+ {
+ status = FAILED;
+ }
+ }
+ }
+ else
+ {
+ /* TODO: reinstall with a filter using multiple TS?
+ * Filters are ANDed for a match, but we could install a filter
+ * with the inverse TS set using NOT-matches... */
+ DBG1(DBG_KNL, "multiple transport mode traffic selectors not "
+ "supported by WFP");
+ status = NOT_SUPPORTED;
+ }
+ }
+ else
+ {
+ DBG1(DBG_KNL, "adding SP failed, no SA found for SPI 0x%08x", key.spi);
+ status = FAILED;
+ }
+ this->mutex->unlock(this->mutex);
+
+ return status;
+}
+
+METHOD(kernel_ipsec_t, query_policy, status_t,
+ private_kernel_wfp_ipsec_t *this, traffic_selector_t *src_ts,
+ traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark,
+ time_t *use_time)
+{
+ /* see query_sa() for some notes */
+ return NOT_SUPPORTED;
+}
+
+METHOD(kernel_ipsec_t, del_policy, status_t,
+ private_kernel_wfp_ipsec_t *this, traffic_selector_t *src_ts,
+ traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid,
+ mark_t mark, policy_priority_t priority)
+{
+ if (direction == POLICY_OUT && priority == POLICY_PRIORITY_ROUTED)
+ {
+ if (remove_trap(this, reqid, FALSE, src_ts, dst_ts))
+ {
+ remove_trap(this, reqid, TRUE, src_ts, dst_ts);
+ return SUCCESS;
+ }
+ return NOT_FOUND;
+ }
+ /* not required, as we delete the whole SA/SP set during del_sa() */
+ return SUCCESS;
+}
+
+METHOD(kernel_ipsec_t, flush_policies, status_t,
+ private_kernel_wfp_ipsec_t *this)
+{
+ return NOT_SUPPORTED;
+}
+
+/**
+ * Add a bypass policy for a specific UDP port
+ */
+static bool add_bypass(private_kernel_wfp_ipsec_t *this,
+ int family, u_int16_t port, bool inbound, UINT64 *luid)
+{
+ FWPM_FILTER_CONDITION0 *cond, *conds = NULL;
+ int count = 0;
+ DWORD res;
+ UINT64 weight = 0xff00000000000000;
+ FWPM_FILTER0 filter = {
+ .displayData = {
+ .name = L"charon IKE bypass",
+ },
+ .action = {
+ .type = FWP_ACTION_PERMIT,
+ },
+ .weight = {
+ .type = FWP_UINT64,
+ .uint64 = &weight,
+ },
+ };
+
+ switch (family)
+ {
+ case AF_INET:
+ filter.layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V4
+ : FWPM_LAYER_OUTBOUND_TRANSPORT_V4;
+ break;
+ case AF_INET6:
+ filter.layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V6
+ : FWPM_LAYER_OUTBOUND_TRANSPORT_V6;
+ break;
+ default:
+ return FALSE;
+ }
+
+ cond = append_condition(&conds, &count);
+ cond->fieldKey = FWPM_CONDITION_IP_PROTOCOL;
+ cond->matchType = FWP_MATCH_EQUAL;
+ cond->conditionValue.type = FWP_UINT8;
+ cond->conditionValue.uint8 = IPPROTO_UDP;
+
+ cond = append_condition(&conds, &count);
+ cond->fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
+ cond->matchType = FWP_MATCH_EQUAL;
+ cond->conditionValue.type = FWP_UINT16;
+ cond->conditionValue.uint16 = port;
+
+ filter.numFilterConditions = count;
+ filter.filterCondition = conds;
+
+ res = FwpmFilterAdd0(this->handle, &filter, NULL, luid);
+ free_conditions(conds, count);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "installing WFP bypass filter failed: 0x%08x", res);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+METHOD(kernel_ipsec_t, bypass_socket, bool,
+ private_kernel_wfp_ipsec_t *this, int fd, int family)
+{
+ union {
+ struct sockaddr sa;
+ SOCKADDR_IN in;
+ SOCKADDR_IN6 in6;
+ } saddr;
+ int addrlen = sizeof(saddr);
+ UINT64 filter_out, filter_in = 0;
+ u_int16_t port;
+
+ if (getsockname(fd, &saddr.sa, &addrlen) == SOCKET_ERROR)
+ {
+ return FALSE;
+ }
+ switch (family)
+ {
+ case AF_INET:
+ port = ntohs(saddr.in.sin_port);
+ break;
+ case AF_INET6:
+ port = ntohs(saddr.in6.sin6_port);
+ break;
+ default:
+ return FALSE;
+ }
+
+ if (!add_bypass(this, family, port, TRUE, &filter_in) ||
+ !add_bypass(this, family, port, FALSE, &filter_out))
+ {
+ if (filter_in)
+ {
+ FwpmFilterDeleteById0(this->handle, filter_in);
+ }
+ return FALSE;
+ }
+
+ this->mutex->lock(this->mutex);
+ array_insert(this->bypass, ARRAY_TAIL, &filter_in);
+ array_insert(this->bypass, ARRAY_TAIL, &filter_out);
+ this->mutex->unlock(this->mutex);
+
+ return TRUE;
+}
+
+METHOD(kernel_ipsec_t, enable_udp_decap, bool,
+ private_kernel_wfp_ipsec_t *this, int fd, int family, u_int16_t port)
+{
+ return FALSE;
+}
+
+METHOD(kernel_ipsec_t, destroy, void,
+ private_kernel_wfp_ipsec_t *this)
+{
+ UINT64 filter;
+
+ while (array_remove(this->bypass, ARRAY_TAIL, &filter))
+ {
+ FwpmFilterDeleteById0(this->handle, filter);
+ }
+ if (this->handle)
+ {
+ if (this->event)
+ {
+ FwpmNetEventUnsubscribe0(this->handle, this->event);
+ }
+ FwpmProviderDeleteByKey0(this->handle, &this->provider.providerKey);
+ FwpmEngineClose0(this->handle);
+ }
+ array_destroy(this->bypass);
+ this->tsas->destroy(this->tsas);
+ this->isas->destroy(this->isas);
+ this->osas->destroy(this->osas);
+ this->routes->destroy(this->routes);
+ this->traps->destroy(this->traps);
+ this->mutex->destroy(this->mutex);
+ free(this);
+}
+
+/*
+ * Described in header.
+ */
+kernel_wfp_ipsec_t *kernel_wfp_ipsec_create()
+{
+ private_kernel_wfp_ipsec_t *this;
+ DWORD res;
+ FWPM_SESSION0 session = {
+ .displayData = {
+ .name = L"charon",
+ .description = L"strongSwan IKE kernel-wfp backend",
+ },
+ };
+
+ INIT(this,
+ .public = {
+ .interface = {
+ .get_features = _get_features,
+ .get_spi = _get_spi,
+ .get_cpi = _get_cpi,
+ .add_sa = _add_sa,
+ .update_sa = _update_sa,
+ .query_sa = _query_sa,
+ .del_sa = _del_sa,
+ .flush_sas = _flush_sas,
+ .add_policy = _add_policy,
+ .query_policy = _query_policy,
+ .del_policy = _del_policy,
+ .flush_policies = _flush_policies,
+ .bypass_socket = _bypass_socket,
+ .enable_udp_decap = _enable_udp_decap,
+ .destroy = _destroy,
+ },
+ },
+ .provider = {
+ .displayData = {
+ .name = L"charon",
+ .description = L"strongSwan IKE kernel-wfp backend",
+ },
+ .providerKey = { 0x59cdae2e, 0xf6bb, 0x4c09,
+ { 0xa9,0x59,0x9d,0x91,0xac,0xaf,0xf9,0x19 }},
+ },
+ .mutex = mutex_create(MUTEX_TYPE_RECURSIVE),
+ .bypass = array_create(sizeof(UINT64), 2),
+ .tsas = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4),
+ .isas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4),
+ .osas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4),
+ .routes = hashtable_create((void*)hash_route, (void*)equals_route, 4),
+ .traps = hashtable_create((void*)hash_trap, (void*)equals_trap, 4),
+ );
+
+ if (!init_spi(this))
+ {
+ destroy(this);
+ return NULL;
+ }
+
+ res = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session,
+ &this->handle);
+ if (res != ERROR_SUCCESS)
+ {
+ DBG1(DBG_KNL, "opening WFP engine failed: 0x%08x", res);
+ destroy(this);
+ return NULL;
+ }
+
+ res = FwpmProviderAdd0(this->handle, &this->provider, NULL);
+ if (res != ERROR_SUCCESS && res != FWP_E_ALREADY_EXISTS)
+ {
+ DBG1(DBG_KNL, "registering WFP provider failed: 0x%08x", res);
+ destroy(this);
+ return NULL;
+ }
+
+ if (!register_events(this))
+ {
+ destroy(this);
+ return NULL;
+ }
+
+ return &this->public;
+}
diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.h
new file mode 100644
index 000000000..d61c230e4
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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_wfp_ipsec kernel_wfp_ipsec
+ * @{ @ingroup kernel_wfp
+ */
+
+#ifndef KERNEL_WFP_IPSEC_H_
+#define KERNEL_WFP_IPSEC_H_
+
+#include <library.h>
+#include <kernel/kernel_ipsec.h>
+
+typedef struct kernel_wfp_ipsec_t kernel_wfp_ipsec_t;
+
+/**
+ * Windows Filter Platform based IPsec kernel backend.
+ */
+struct kernel_wfp_ipsec_t {
+
+ /**
+ * Implements kernel_ipsec_t interface
+ */
+ kernel_ipsec_t interface;
+};
+
+/**
+ * Create WFP kernel interface instance.
+ *
+ * @return kernel_wfp_ipsec_t instance
+ */
+kernel_wfp_ipsec_t *kernel_wfp_ipsec_create();
+
+#endif /** KERNEL_WFP_IPSEC_H_ @}*/
diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c
new file mode 100644
index 000000000..e465b0a76
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec AG
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "kernel_wfp_plugin.h"
+#include "kernel_wfp_ipsec.h"
+
+#include <daemon.h>
+
+typedef struct private_kernel_wfp_plugin_t private_kernel_wfp_plugin_t;
+
+/**
+ * Private data of kernel-wfp plugin
+ */
+struct private_kernel_wfp_plugin_t {
+
+ /**
+ * Implements plugin interface
+ */
+ kernel_wfp_plugin_t public;
+};
+
+METHOD(plugin_t, get_name, char*,
+ private_kernel_wfp_plugin_t *this)
+{
+ return "kernel-wfp";
+}
+
+METHOD(plugin_t, get_features, int,
+ private_kernel_wfp_plugin_t *this, plugin_feature_t *features[])
+{
+ static plugin_feature_t f[] = {
+ PLUGIN_CALLBACK(kernel_ipsec_register, kernel_wfp_ipsec_create),
+ PLUGIN_PROVIDE(CUSTOM, "kernel-ipsec"),
+ PLUGIN_DEPENDS(RNG, RNG_WEAK),
+ PLUGIN_DEPENDS(RNG, RNG_STRONG),
+ };
+ *features = f;
+ return countof(f);
+}
+
+METHOD(plugin_t, destroy, void,
+ private_kernel_wfp_plugin_t *this)
+{
+ free(this);
+}
+
+/*
+ * see header file
+ */
+plugin_t *kernel_wfp_plugin_create()
+{
+ private_kernel_wfp_plugin_t *this;
+
+ INIT(this,
+ .public = {
+ .plugin = {
+ .get_name = _get_name,
+ .get_features = _get_features,
+ .destroy = _destroy,
+ },
+ },
+ );
+
+ return &this->public.plugin;
+}
diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.h
new file mode 100644
index 000000000..a538e34a1
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2013 Martin Willi
+ * Copyright (C) 2013 revosec 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_wfp kernel_wfp
+ * @ingroup cplugins
+ *
+ * @defgroup kernel_wfp_plugin kernel_wfp_plugin
+ * @{ @ingroup kernel_wfp
+ */
+
+#ifndef KERNEL_WFP_PLUGIN_H_
+#define KERNEL_WFP_PLUGIN_H_
+
+#include <library.h>
+#include <plugins/plugin.h>
+
+typedef struct kernel_wfp_plugin_t kernel_wfp_plugin_t;
+
+/**
+ * Windows Filter Platform based IPsec backend plugin.
+ */
+struct kernel_wfp_plugin_t {
+
+ /**
+ * implements plugin interface
+ */
+ plugin_t plugin;
+};
+
+#endif /** KERNEL_WFP_PLUGIN_H_ @}*/
diff --git a/src/libcharon/plugins/kernel_wfp/mingw-w64-4.8.1.diff b/src/libcharon/plugins/kernel_wfp/mingw-w64-4.8.1.diff
new file mode 100644
index 000000000..c72b94c07
--- /dev/null
+++ b/src/libcharon/plugins/kernel_wfp/mingw-w64-4.8.1.diff
@@ -0,0 +1,26 @@
+diff -Naur /mingw-orig/x86_64-w64-mingw32/include/fwptypes.h /mingw/x86_64-w64-mingw32/include/fwptypes.h
+--- /mingw-orig/x86_64-w64-mingw32/include/fwptypes.h 2013-08-30 07:15:40 +0200
++++ /mingw/x86_64-w64-mingw32/include/fwptypes.h 2014-01-02 16:32:26 +0100
+@@ -333,11 +333,6 @@
+ } __C89_NAMELESSUNIONNAME;
+ } FWP_CONDITION_VALUE0;
+
+-typedef struct FWPM_DISPLAY_DATA0_ {
+- wchar_t *name;
+- wchar_t *description;
+-} FWPM_DISPLAY_DATA0;
+-
+ #endif /* WINAPI_PARTITION_DESKTOP. */
+ /* Begin additional prototypes for all interfaces */
+
+diff -Naur /mingw-orig/x86_64-w64-mingw32/include/iketypes.h /mingw/x86_64-w64-mingw32/include/iketypes.h
+--- /mingw-orig/x86_64-w64-mingw32/include/iketypes.h 2013-08-30 07:15:40 +0200
++++ /mingw/x86_64-w64-mingw32/include/iketypes.h 2014-01-02 16:31:12 +0100
+@@ -212,7 +212,6 @@
+ FWP_BYTE_BLOB presharedKey;
+ UINT32 flags;
+ } IKEEXT_PRESHARED_KEY_AUTHENTICATION1;
+-#endif
+
+ typedef struct IKEEXT_CERTIFICATE_AUTHENTICATION0_ {
+ IKEEXT_CERT_CONFIG_TYPE inboundConfigType;
diff --git a/src/libcharon/plugins/socket_win/socket_win_plugin.c b/src/libcharon/plugins/socket_win/socket_win_plugin.c
index 4c5ffe03c..a0ef0858a 100644
--- a/src/libcharon/plugins/socket_win/socket_win_plugin.c
+++ b/src/libcharon/plugins/socket_win/socket_win_plugin.c
@@ -49,6 +49,7 @@ METHOD(plugin_t, get_features, int,
static plugin_feature_t f[] = {
PLUGIN_CALLBACK(socket_register, socket_win_socket_create),
PLUGIN_PROVIDE(CUSTOM, "socket"),
+ PLUGIN_DEPENDS(CUSTOM, "kernel-ipsec"),
};
*features = f;
return countof(f);
diff --git a/src/libcharon/plugins/socket_win/socket_win_socket.c b/src/libcharon/plugins/socket_win/socket_win_socket.c
index 473b05313..2cd951d8f 100644
--- a/src/libcharon/plugins/socket_win/socket_win_socket.c
+++ b/src/libcharon/plugins/socket_win/socket_win_socket.c
@@ -19,6 +19,7 @@
#include "socket_win_socket.h"
#include <library.h>
+#include <hydra.h>
#include <threading/thread.h>
#include <daemon.h>
@@ -424,6 +425,16 @@ static SOCKET open_socket(private_socket_win_socket_t *this, int i)
closesocket(s);
return INVALID_SOCKET;
}
+ if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface,
+ s, AF_INET))
+ {
+ DBG1(DBG_NET, "installing IPv4 IKE bypass policy failed");
+ }
+ if (!hydra->kernel_interface->bypass_socket(hydra->kernel_interface,
+ s, AF_INET6))
+ {
+ DBG1(DBG_NET, "installing IPv6 IKE bypass policy failed");
+ }
return s;
}
diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c
index 847cfc78f..a7d7b7305 100644
--- a/src/libcharon/sa/child_sa.c
+++ b/src/libcharon/sa/child_sa.c
@@ -731,6 +731,17 @@ METHOD(child_sa_t, install, status_t,
}
/**
+ * Check kernel interface if policy updates are required
+ */
+static bool require_policy_update()
+{
+ kernel_feature_t f;
+
+ f = hydra->kernel_interface->get_features(hydra->kernel_interface);
+ return !(f & KERNEL_NO_POLICY_UPDATES);
+}
+
+/**
* Install 3 policies: out, in and forward
*/
static status_t install_policies_internal(private_child_sa_t *this,
@@ -836,13 +847,21 @@ METHOD(child_sa_t, add_policies, status_t,
priority = this->trap ? POLICY_PRIORITY_ROUTED
: POLICY_PRIORITY_DEFAULT;
+ enumerator = create_policy_enumerator(this);
+ while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
+ {
+ my_sa.policy_count++;
+ other_sa.policy_count++;
+ }
+ enumerator->destroy(enumerator);
+
/* enumerate pairs of traffic selectors */
enumerator = create_policy_enumerator(this);
while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
{
/* install outbound drop policy to avoid packets leaving unencrypted
* when updating policies */
- if (priority == POLICY_PRIORITY_DEFAULT)
+ if (priority == POLICY_PRIORITY_DEFAULT && require_policy_update())
{
status |= install_policies_internal(this, this->my_addr,
this->other_addr, my_ts, other_ts,
@@ -936,7 +955,7 @@ METHOD(child_sa_t, update, status_t,
}
}
- if (this->config->install_policy(this->config))
+ if (this->config->install_policy(this->config) && require_policy_update())
{
ipsec_sa_cfg_t my_sa = {
.mode = this->mode,
@@ -1075,7 +1094,7 @@ METHOD(child_sa_t, destroy, void,
while (enumerator->enumerate(enumerator, &my_ts, &other_ts))
{
del_policies_internal(this, my_ts, other_ts, priority);
- if (priority == POLICY_PRIORITY_DEFAULT)
+ if (priority == POLICY_PRIORITY_DEFAULT && require_policy_update())
{
del_policies_internal(this, my_ts, other_ts,
POLICY_PRIORITY_FALLBACK);
diff --git a/src/libhydra/kernel/kernel_interface.h b/src/libhydra/kernel/kernel_interface.h
index cc47d3c4a..3b1010d24 100644
--- a/src/libhydra/kernel/kernel_interface.h
+++ b/src/libhydra/kernel/kernel_interface.h
@@ -69,6 +69,8 @@ enum kernel_feature_t {
KERNEL_REQUIRE_EXCLUDE_ROUTE = (1<<1),
/** IPsec implementation requires UDP encapsulation of ESP packets */
KERNEL_REQUIRE_UDP_ENCAPSULATION = (1<<2),
+ /** IPsec backend does not require a policy reinstall on SA updates */
+ KERNEL_NO_POLICY_UPDATES = (1<<3),
};
/**
diff --git a/src/libstrongswan/ipsec/ipsec_types.h b/src/libstrongswan/ipsec/ipsec_types.h
index 6851f916a..1a4656b04 100644
--- a/src/libstrongswan/ipsec/ipsec_types.h
+++ b/src/libstrongswan/ipsec/ipsec_types.h
@@ -122,6 +122,8 @@ struct ipsec_sa_cfg_t {
ipsec_mode_t mode;
/** unique ID */
u_int32_t reqid;
+ /** number of policies of the same kind (in/out/fwd) attached to SA */
+ u_int32_t policy_count;
/** details about ESP/AH */
struct {
/** TRUE if this protocol is used */