From 8d91eee3fcc3c170ff97801ac758d430db8ae8b3 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 31 Oct 2013 14:58:43 +0100 Subject: [PATCH 01/50] kernel-wfp: Add a stub for a Windows Filtering Platform based IPsec backend --- configure.ac | 4 + src/libcharon/Makefile.am | 7 + src/libcharon/plugins/kernel_wfp/Makefile.am | 20 +++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 169 ++++++++++++++++++ .../plugins/kernel_wfp/kernel_wfp_ipsec.h | 47 +++++ .../plugins/kernel_wfp/kernel_wfp_plugin.c | 75 ++++++++ .../plugins/kernel_wfp/kernel_wfp_plugin.h | 43 +++++ 7 files changed, 365 insertions(+) create mode 100644 src/libcharon/plugins/kernel_wfp/Makefile.am create mode 100644 src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c create mode 100644 src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.h create mode 100644 src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c create mode 100644 src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.h diff --git a/configure.ac b/configure.ac index 2085ae1b3..82d6fd286 100644 --- a/configure.ac +++ b/configure.ac @@ -204,6 +204,7 @@ 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-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 +1210,7 @@ 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-pfkey], [h charon starter nm cmd]) ADD_PLUGIN([kernel-pfroute], [h charon starter nm cmd]) ADD_PLUGIN([kernel-klips], [h charon starter]) @@ -1368,6 +1370,7 @@ 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_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 +1665,7 @@ 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/whitelist/Makefile src/libcharon/plugins/lookip/Makefile src/libcharon/plugins/error_notify/Makefile diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index 52d20b315..907f65932 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -489,6 +489,13 @@ 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_WHITELIST SUBDIRS += plugins/whitelist if MONOLITHIC diff --git a/src/libcharon/plugins/kernel_wfp/Makefile.am b/src/libcharon/plugins/kernel_wfp/Makefile.am new file mode 100644 index 000000000..1a2f83ee2 --- /dev/null +++ b/src/libcharon/plugins/kernel_wfp/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-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_ipsec.h kernel_wfp_ipsec.c + +libstrongswan_kernel_wfp_la_LDFLAGS = -module -avoid-version +libstrongswan_kernel_wfp_la_LIBADD = -lfwpuclnt 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..ac37cb66e --- /dev/null +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -0,0 +1,169 @@ +/* + * 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 . + * + * 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_ipsec.h" + +#include + +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; +}; + +METHOD(kernel_ipsec_t, get_features, kernel_feature_t, + private_kernel_wfp_ipsec_t *this) +{ + return KERNEL_ESP_V3_TFC; +} + +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) +{ + return NOT_SUPPORTED; +} + +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; +} + +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) +{ + return NOT_SUPPORTED; +} + +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) +{ + return NOT_SUPPORTED; +} + +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) +{ + 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) +{ + return NOT_SUPPORTED; +} + +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) +{ + return NOT_SUPPORTED; +} + +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) +{ + 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) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, flush_policies, status_t, + private_kernel_wfp_ipsec_t *this) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, bypass_socket, bool, + private_kernel_wfp_ipsec_t *this, int fd, int family) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, enable_udp_decap, bool, + private_kernel_wfp_ipsec_t *this, int fd, int family, u_int16_t port) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_ipsec_t, destroy, void, + private_kernel_wfp_ipsec_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() +{ + private_kernel_wfp_ipsec_t *this; + + 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, + }, + }, + ); + + 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 . + * + * 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 +#include + +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..3260c7eed --- /dev/null +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c @@ -0,0 +1,75 @@ +/* + * 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 . + * + * 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 + +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"), + }; + *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 . + * + * 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 +#include + +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_ @}*/ From 96ab7a8022341997cdf74459e2864c56be31af55 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 1 Nov 2013 10:54:38 +0100 Subject: [PATCH 02/50] kernel-wfp: Create userland state for SAs/policies to install in kernel --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 369 +++++++++++++++++- 1 file changed, 364 insertions(+), 5 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index ac37cb66e..35bf8cc8a 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -16,6 +16,9 @@ #include "kernel_wfp_ipsec.h" #include +#include +#include +#include typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t; @@ -25,8 +28,176 @@ struct private_kernel_wfp_ipsec_t { * Public interface */ kernel_wfp_ipsec_t public; + + /** + * Next SPI to allocate + */ + refcount_t nextspi; + + /** + * SAD/SPD entries, as reqid => entry_t + */ + hashtable_t *entries; + + /** + * SAD entry lookup, as sa_entry_t => entry_t + */ + hashtable_t *sas; + + /** + * Mutex for accessing entries + */ + mutex_t *mutex; }; +/** + * Security association entry + */ +typedef struct { + /** SPI for this SA */ + u_int32_t spi; + /** destination host address for this SPI */ + host_t *dst; + /** inbound or outbound SA? */ + bool inbound; + struct { + /** algorithm */ + u_int16_t alg; + /** key */ + chunk_t key; + } integ, encr; +} sa_entry_t; + +/** + * Destroy an SA entry + */ +static void sa_entry_destroy(sa_entry_t *sa) +{ + chunk_clear(&sa->integ.key); + chunk_clear(&sa->encr.key); + free(sa); +} + +/** + * 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; + /** direction of policy, in|out */ + policy_dir_t direction; +} 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; + /** associated security associations, as sa_entry_t* */ + array_t *sas; + /** associated policies, as sp_entry_t* */ + array_t *sps; + /** IPsec protocol, ESP|AH */ + u_int8_t protocol; + /** IPsec mode, tunnel|transport */ + ipsec_mode_t mode; + /** UDP encapsulation */ + bool encap; +} entry_t; + +/** + * Create a SA/SP entry set + */ +static entry_t *entry_create(u_int32_t reqid, host_t *local, host_t *remote, + u_int8_t protocol, ipsec_mode_t mode, bool encap) +{ + entry_t *entry; + + INIT(entry, + .reqid = reqid, + .sas = array_create(0, 0), + .sps = array_create(0, 0), + .local = local->clone(local), + .remote = remote->clone(remote), + .protocol = protocol, + .mode = mode, + .encap = encap, + ); + return entry; +} + +/** + * Destroy a SA/SP entry set + */ +static void entry_destroy(entry_t *entry) +{ + array_destroy(entry->sas); + array_destroy(entry->sps); + entry->local->destroy(entry->local); + entry->remote->destroy(entry->remote); + free(entry); +} + +/** + * Get an entry, create if not exists. May fail if non-matching entry found + */ +static entry_t *get_or_create_entry(private_kernel_wfp_ipsec_t *this, + u_int32_t reqid, host_t *local, host_t *remote, + u_int8_t protocol, ipsec_mode_t mode, bool encap) +{ + entry_t *entry; + + entry = this->entries->get(this->entries, (void*)(uintptr_t)reqid); + if (!entry) + { + entry = entry_create(reqid, local, remote, protocol, mode, encap); + this->entries->put(this->entries, (void*)(uintptr_t)reqid, entry); + return entry; + } + if (entry->protocol == protocol && + entry->mode == mode && + entry->local->ip_equals(entry->local, local) && + entry->remote->ip_equals(entry->remote, remote)) + { + return entry; + } + return NULL; +} + METHOD(kernel_ipsec_t, get_features, kernel_feature_t, private_kernel_wfp_ipsec_t *this) { @@ -37,7 +208,8 @@ 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) { - return NOT_SUPPORTED; + *spi = ref_get(&this->nextspi); + return SUCCESS; } METHOD(kernel_ipsec_t, get_cpi, status_t, @@ -55,7 +227,52 @@ METHOD(kernel_ipsec_t, add_sa, status_t, u_int16_t cpi, bool initiator, bool encap, bool esn, bool inbound, traffic_selector_t *src_ts, traffic_selector_t *dst_ts) { - return NOT_SUPPORTED; + status_t status = SUCCESS; + host_t *local, *remote; + entry_t *entry; + sa_entry_t *sa; + + if (inbound) + { + local = dst; + remote = src; + } + else + { + local = src; + remote = dst; + } + + this->mutex->lock(this->mutex); + entry = get_or_create_entry(this, reqid, local, remote, + protocol, mode, encap); + if (entry) + { + INIT(sa, + .spi = spi, + .inbound = inbound, + .dst = inbound ? entry->local : entry->remote, + .encr = { + .alg = enc_alg, + .key = chunk_clone(enc_key), + }, + .integ = { + .alg = int_alg, + .key = chunk_clone(int_key), + }, + ); + array_insert(entry->sas, -1, sa); + this->sas->put(this->sas, sa, entry); + } + else + { + DBG1(DBG_KNL, "adding SA failed, a different SA with reqid %u exists", + reqid); + status = FAILED; + } + this->mutex->unlock(this->mutex); + + return status; } METHOD(kernel_ipsec_t, update_sa, status_t, @@ -78,7 +295,61 @@ 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) { - return NOT_SUPPORTED; + status_t status = NOT_FOUND; + entry_t *entry; + host_t *local, *remote; + enumerator_t *enumerator; + sa_entry_t *sa, key = { + .dst = dst, + .spi = spi, + }; + + this->mutex->lock(this->mutex); + + entry = this->sas->get(this->sas, &key); + if (entry) + { + enumerator = array_create_enumerator(entry->sas); + while (enumerator->enumerate(enumerator, &sa)) + { + if (sa->inbound) + { + local = dst; + remote = src; + } + else + { + local = src; + remote = dst; + } + if (sa->spi == spi && entry->protocol == protocol && + local->ip_equals(local, entry->local) && + remote->ip_equals(remote, entry->remote)) + { + array_remove_at(entry->sas, enumerator); + this->sas->remove(this->sas, sa); + /* TODO: uninstall SA from kernel */ + sa_entry_destroy(sa); + status = SUCCESS; + break; + } + } + enumerator->destroy(enumerator); + + if (!array_count(entry->sas) && !array_count(entry->sps)) + { + entry = this->entries->remove(this->entries, + (void*)(uintptr_t)entry->reqid); + if (entry) + { + entry_destroy(entry); + } + } + } + + this->mutex->unlock(this->mutex); + + return status; } METHOD(kernel_ipsec_t, flush_sas, status_t, @@ -93,7 +364,49 @@ METHOD(kernel_ipsec_t, add_policy, status_t, policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark, policy_priority_t priority) { - return NOT_SUPPORTED; + status_t status = SUCCESS; + host_t *local, *remote; + entry_t *entry; + sp_entry_t *sp; + + if (direction == POLICY_FWD || priority != POLICY_PRIORITY_DEFAULT) + { + return SUCCESS; + } + + if (direction == POLICY_IN) + { + local = dst; + remote = src; + } + else + { + local = src; + remote = dst; + } + + this->mutex->lock(this->mutex); + entry = get_or_create_entry(this, sa->reqid, local, remote, + sa->esp.use ? IPPROTO_ESP : IPPROTO_AH, + sa->mode, FALSE); + if (entry) + { + INIT(sp, + .src = src_ts->clone(src_ts), + .dst = dst_ts->clone(dst_ts), + .direction = direction, + ); + array_insert(entry->sps, -1, sp); + } + else + { + DBG1(DBG_KNL, "adding SP failed, a different SP with reqid %u exists", + sa->reqid); + status = FAILED; + } + this->mutex->unlock(this->mutex); + + return status; } METHOD(kernel_ipsec_t, query_policy, status_t, @@ -109,7 +422,46 @@ METHOD(kernel_ipsec_t, del_policy, status_t, traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, mark_t mark, policy_priority_t priority) { - return NOT_SUPPORTED; + status_t status = NOT_FOUND; + entry_t *entry; + sp_entry_t *sp; + enumerator_t *enumerator; + + this->mutex->lock(this->mutex); + + entry = this->entries->get(this->entries, (void*)(uintptr_t)reqid); + if (entry) + { + enumerator = array_create_enumerator(entry->sps); + while (enumerator->enumerate(enumerator, &sp)) + { + if (sp->direction == direction && + src_ts->equals(src_ts, sp->src) && + dst_ts->equals(dst_ts, sp->dst)) + { + array_remove_at(entry->sps, enumerator); + /* TODO: uninstall SP from kernel */ + sp_entry_destroy(sp); + status = SUCCESS; + break; + } + } + enumerator->destroy(enumerator); + + if (!array_count(entry->sas) && !array_count(entry->sps)) + { + entry = this->entries->remove(this->entries, + (void*)(uintptr_t)reqid); + if (entry) + { + entry_destroy(entry); + } + } + } + + this->mutex->unlock(this->mutex); + + return status; } METHOD(kernel_ipsec_t, flush_policies, status_t, @@ -133,6 +485,9 @@ METHOD(kernel_ipsec_t, enable_udp_decap, bool, METHOD(kernel_ipsec_t, destroy, void, private_kernel_wfp_ipsec_t *this) { + this->entries->destroy(this->entries); + this->sas->destroy(this->sas); + this->mutex->destroy(this->mutex); free(this); } @@ -163,6 +518,10 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .destroy = _destroy, }, }, + .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .entries = hashtable_create(hashtable_hash_ptr, + hashtable_equals_ptr, 4), + .sas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4), ); return &this->public; From ebb9362d85a5822c039b29673092267186a79cb9 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 7 Nov 2013 15:50:46 +0100 Subject: [PATCH 03/50] kernel-wfp: Open and close a WFP engine --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 35bf8cc8a..dd80ea617 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -13,6 +13,9 @@ * for more details. */ +/* Windows 7, for some fwpmu.h functionality */ +#define _WIN32_WINNT 0x0601 + #include "kernel_wfp_ipsec.h" #include @@ -20,6 +23,10 @@ #include #include +#include +#include +#undef interface + typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t; struct private_kernel_wfp_ipsec_t { @@ -48,6 +55,11 @@ struct private_kernel_wfp_ipsec_t { * Mutex for accessing entries */ mutex_t *mutex; + + /** + * WFP session handle + */ + HANDLE handle; }; /** @@ -485,6 +497,10 @@ METHOD(kernel_ipsec_t, enable_udp_decap, bool, METHOD(kernel_ipsec_t, destroy, void, private_kernel_wfp_ipsec_t *this) { + if (this->handle) + { + FwpmEngineClose0(this->handle); + } this->entries->destroy(this->entries); this->sas->destroy(this->sas); this->mutex->destroy(this->mutex); @@ -497,6 +513,13 @@ METHOD(kernel_ipsec_t, destroy, void, 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 = { @@ -524,5 +547,14 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .sas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4), ); + 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; + } + return &this->public; -}; +} From b1ba0a666c4a46740bae63fd3cc005b7abfbe86d Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 15 Nov 2013 12:09:46 +0100 Subject: [PATCH 04/50] kernel-wfp: Fix/Complete some fwpuclnt functionality in MinGW While MinGW declares all the required symbols, some of them are missing in the library files. We provide missing variables locally, functions get a stub that call the GetProcAddress()ed function from the DLL. Also some MinGW headers define some enum values incorrectly, we overload these using defines. --- src/libcharon/plugins/kernel_wfp/Makefile.am | 1 + .../plugins/kernel_wfp/kernel_wfp_compat.c | 83 +++++++++++++++++++ .../plugins/kernel_wfp/kernel_wfp_compat.h | 48 +++++++++++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 4 +- 4 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c create mode 100644 src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h diff --git a/src/libcharon/plugins/kernel_wfp/Makefile.am b/src/libcharon/plugins/kernel_wfp/Makefile.am index 1a2f83ee2..405e581e4 100644 --- a/src/libcharon/plugins/kernel_wfp/Makefile.am +++ b/src/libcharon/plugins/kernel_wfp/Makefile.am @@ -14,6 +14,7 @@ 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 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..1b20d8990 --- /dev/null +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -0,0 +1,83 @@ +/* + * 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 . + * + * 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 + +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_LAYER_INBOUND_TRANSPORT_V4 = { + 0x5926dfc8, 0xe3cf, 0x4426, { 0xa2,0x83,0xdc,0x39,0x3f,0x5d,0x0f,0x9d } +}; +const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V4 = { + 0x09e61aea, 0xd214, 0x46e2, { 0x9b,0x21,0xb2,0x6b,0x0b,0x2f,0x28,0xc8 } +}; +const GUID FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 = { + 0x5132900d, 0x5e84, 0x4b5f, { 0x80,0xe4,0x01,0x74,0x1e,0x81,0xff,0x10 } +}; +const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4 = { + 0x4b46bf0a, 0x4523, 0x4e57, { 0xaa,0x38,0xa8,0x79,0x87,0xc9,0x10,0xd9 } +}; + +/** + * 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) 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..59d4fa166 --- /dev/null +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -0,0 +1,48 @@ +/* + * 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 . + * + * 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 +#include +#include + +/* 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 +#include +#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 + +#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 index dd80ea617..caf955d55 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -16,6 +16,7 @@ /* Windows 7, for some fwpmu.h functionality */ #define _WIN32_WINNT 0x0601 +#include "kernel_wfp_compat.h" #include "kernel_wfp_ipsec.h" #include @@ -23,9 +24,6 @@ #include #include -#include -#include -#undef interface typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t; From 149fc48e030981aa41ab5f282691a865449504e7 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 18 Nov 2013 12:57:36 +0100 Subject: [PATCH 05/50] kernel-wfp: Preliminary support for transport mode connections --- .../plugins/kernel_wfp/kernel_wfp_compat.c | 9 + .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 692 +++++++++++++++++- 2 files changed, 698 insertions(+), 3 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c index 1b20d8990..a687c4371 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -21,6 +21,15 @@ const GUID FWPM_CONDITION_IP_REMOTE_ADDRESS = { const GUID FWPM_CONDITION_IP_LOCAL_ADDRESS = { 0xd9ee00de, 0xc1ef, 0x4617, { 0xbf,0xe3,0xff,0xd8,0xf5,0xa0,0x89,0x57 } }; +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 } }; diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index caf955d55..0910efc9b 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -147,6 +147,12 @@ typedef struct { ipsec_mode_t mode; /** UDP encapsulation */ bool encap; + /** WFP allocated LUID for inbound filter/tunnel policy ID */ + u_int64_t policy_in; + /** WFP allocated LUID for outbound filter/tunnel policy ID */ + u_int64_t policy_out; + /** WFP allocated LUID for SA context */ + u_int64_t sa_id; } entry_t; /** @@ -173,8 +179,20 @@ static entry_t *entry_create(u_int32_t reqid, host_t *local, host_t *remote, /** * Destroy a SA/SP entry set */ -static void entry_destroy(entry_t *entry) +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->policy_in) + { + FwpmFilterDeleteById0(this->handle, entry->policy_in); + } + if (entry->policy_out) + { + FwpmFilterDeleteById0(this->handle, entry->policy_out); + } array_destroy(entry->sas); array_destroy(entry->sps); entry->local->destroy(entry->local); @@ -208,6 +226,666 @@ static entry_t *get_or_create_entry(private_kernel_wfp_ipsec_t *this, return NULL; } +/** + * 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, bool local, + 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); + if (local) + { + cond->fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS; + } + else + { + cond->fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS; + } + 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 && local) + { + 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 (local) + { + 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) + { + cond = append_condition(conds, count); + if (local) + { + cond->fieldKey = FWPM_CONDITION_IP_LOCAL_PORT; + } + else + { + 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); +} + +/** + * Install transport mode SP to the kernel + */ +static bool install_transport_sp(private_kernel_wfp_ipsec_t *this, + entry_t *entry, bool inbound) +{ + FWPM_FILTER_CONDITION0 *conds = NULL; + int count = 0; + enumerator_t *enumerator; + traffic_selector_t *local, *remote; + sp_entry_t *sp; + DWORD res; + FWPM_FILTER0 filter = { + .displayData = { + .name = L"charon IPsec transport", + }, + .action = { + .type = FWP_ACTION_CALLOUT_TERMINATING, + .calloutKey = inbound ? FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 : + FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4, + }, + .layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V4 : + FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + }; + + enumerator = array_create_enumerator(entry->sps); + while (enumerator->enumerate(enumerator, &sp)) + { + if (inbound) + { + if (sp->direction != POLICY_IN) + { + continue; + } + local = sp->dst; + remote = sp->src; + } + else + { + if (sp->direction != POLICY_OUT) + { + continue; + } + local = sp->src; + remote = sp->dst; + } + + if (!ts2condition(local, TRUE, &conds, &count) || + !ts2condition(remote, FALSE, &conds, &count)) + { + free_conditions(conds, count); + enumerator->destroy(enumerator); + return FALSE; + } + } + enumerator->destroy(enumerator); + + filter.numFilterConditions = count; + filter.filterCondition = conds; + + if (inbound) + { + res = FwpmFilterAdd0(this->handle, &filter, NULL, &entry->policy_in); + } + else + { + res = FwpmFilterAdd0(this->handle, &filter, NULL, &entry->policy_out); + } + free_conditions(conds, count); + if (res != ERROR_SUCCESS) + { + DBG1(DBG_KNL, "installing inbound FWP filter failed: 0x%08x", res); + return FALSE; + } + 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 transport mode SA + */ +static bool install_transport_sa(private_kernel_wfp_ipsec_t *this, + entry_t *entry, 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 = { + .saList = &ipsec, + .numSAs = 1, + .ipVersion = version, + }; + struct { + u_int16_t alg; + chunk_t key; + } integ = {}, encr = {}; + DWORD res; + + switch (entry->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 (sa->inbound) + { + res = IPsecSaContextAddInbound0(this->handle, entry->sa_id, &bundle); + } + else + { + res = IPsecSaContextAddOutbound0(this->handle, entry->sa_id, &bundle); + } + if (res != ERROR_SUCCESS) + { + DBG1(DBG_KNL, "adding %sbound WFP SA failed: 0x%08x", + sa->inbound ? "in" : "out", res); + return FALSE; + } + return TRUE; +} + +/** + * Install transport mode SAs to the kernel + */ +static bool install_transport_sas(private_kernel_wfp_ipsec_t *this, + entry_t *entry) +{ + IPSEC_TRAFFIC0 traffic = { + .trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT, + }; + IPSEC_GETSPI1 spi = { + .inboundIpsecTraffic = { + .trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT, + .ipsecFilterId = entry->policy_in, + }, + }; + sa_entry_t *sa; + IPSEC_SA_SPI inbound_spi = 0; + enumerator_t *enumerator; + DWORD res; + + switch (entry->local->get_family(entry->local)) + { + case AF_INET: + traffic.ipVersion = FWP_IP_VERSION_V4; + traffic.localV4Address = + untoh32(entry->local->get_address(entry->local).ptr); + traffic.remoteV4Address = + untoh32(entry->remote->get_address(entry->remote).ptr); + break; + case AF_INET6: + traffic.ipVersion = FWP_IP_VERSION_V6; + memcpy(&traffic.localV6Address, + entry->local->get_address(entry->local).ptr, 16); + memcpy(&traffic.remoteV6Address, + entry->remote->get_address(entry->remote).ptr, 16); + break; + default: + return FALSE; + } + + traffic.ipsecFilterId = entry->policy_out; + res = IPsecSaContextCreate0(this->handle, &traffic, NULL, &entry->sa_id); + if (res != ERROR_SUCCESS) + { + DBG1(DBG_KNL, "creating WFP SA context failed: 0x%08x", res); + return FALSE; + } + + enumerator = array_create_enumerator(entry->sas); + while (enumerator->enumerate(enumerator, &sa)) + { + if (sa->inbound) + { + inbound_spi = ntohl(sa->spi); + break; + } + } + enumerator->destroy(enumerator); + if (!inbound_spi) + { + 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, inbound_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; + } + + enumerator = array_create_enumerator(entry->sas); + while (enumerator->enumerate(enumerator, &sa)) + { + if (!install_transport_sa(this, entry, sa, spi.ipVersion)) + { + enumerator->destroy(enumerator); + IPsecSaContextDeleteById0(this->handle, entry->sa_id); + entry->sa_id = 0; + return FALSE; + } + } + enumerator->destroy(enumerator); + + 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_transport_sp(this, entry, TRUE) && + install_transport_sp(this, entry, FALSE) && + install_transport_sas(this, entry)) + { + return TRUE; + } + if (entry->policy_in) + { + FwpmFilterDeleteById0(this->handle, entry->policy_in); + entry->policy_in = 0; + } + if (entry->policy_out) + { + FwpmFilterDeleteById0(this->handle, entry->policy_out); + entry->policy_out = 0; + } + 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: + case MODE_BEET: + default: + return FALSE; + } +} + METHOD(kernel_ipsec_t, get_features, kernel_feature_t, private_kernel_wfp_ipsec_t *this) { @@ -352,7 +1030,7 @@ METHOD(kernel_ipsec_t, del_sa, status_t, (void*)(uintptr_t)entry->reqid); if (entry) { - entry_destroy(entry); + entry_destroy(this, entry); } } } @@ -407,6 +1085,13 @@ METHOD(kernel_ipsec_t, add_policy, status_t, .direction = direction, ); array_insert(entry->sps, -1, sp); + if (array_count(entry->sps) > 1) + { + if (!install(this, entry)) + { + status = FAILED; + } + } } else { @@ -464,7 +1149,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, (void*)(uintptr_t)reqid); if (entry) { - entry_destroy(entry); + entry_destroy(this, entry); } } } @@ -539,6 +1224,7 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .destroy = _destroy, }, }, + .nextspi = htonl(0xc0000001), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), From f5ddda7f5769d8ab46ab5959bb0c9847982d7054 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 5 Dec 2013 14:29:05 +0100 Subject: [PATCH 06/50] kernel-wfp: Register a WFP provider to manage IPsec tunnels --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 0910efc9b..dd77b3a7c 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -58,6 +58,11 @@ struct private_kernel_wfp_ipsec_t { * WFP session handle */ HANDLE handle; + + /** + * Provider charon registers as + */ + FWPM_PROVIDER0 provider; }; /** @@ -1182,6 +1187,7 @@ METHOD(kernel_ipsec_t, destroy, void, { if (this->handle) { + FwpmProviderDeleteByKey0(this->handle, &this->provider.providerKey); FwpmEngineClose0(this->handle); } this->entries->destroy(this->entries); @@ -1224,6 +1230,14 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .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 }}, + }, .nextspi = htonl(0xc0000001), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .entries = hashtable_create(hashtable_hash_ptr, @@ -1240,5 +1254,13 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() 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; + } + return &this->public; } From 4a8b85684ffa3365dd0bd698aa2fb9f5a8130051 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 13:16:49 +0100 Subject: [PATCH 07/50] kernel-wfp: Add support for tunnel mode connections --- .../plugins/kernel_wfp/kernel_wfp_compat.h | 10 + .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 234 ++++++++++++++++-- 2 files changed, 217 insertions(+), 27 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h index 59d4fa166..efccd4c45 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -45,4 +45,14 @@ #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), +}; + +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 index dd77b3a7c..402f4a398 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -154,7 +154,7 @@ typedef struct { bool encap; /** WFP allocated LUID for inbound filter/tunnel policy ID */ u_int64_t policy_in; - /** WFP allocated LUID for outbound filter/tunnel policy ID */ + /** WFP allocated LUID for outbound filter ID, unused for tunnel mode */ u_int64_t policy_out; /** WFP allocated LUID for SA context */ u_int64_t sa_id; @@ -181,6 +181,46 @@ static entry_t *entry_create(u_int32_t reqid, host_t *local, host_t *remote, return entry; } +/** + * Remove a transport or tunnel policy from kernel + */ +static void cleanup_policy(private_kernel_wfp_ipsec_t *this, bool transport, + u_int64_t policy) +{ + if (transport) + { + FwpmFilterDeleteById0(this->handle, policy); + } + else + { + FWPM_PROVIDER_CONTEXT0 *ctx; + + if (FwpmProviderContextGetById0(this->handle, policy, + &ctx) == ERROR_SUCCESS) + { + FwpmIPsecTunnelDeleteByKey0(this->handle, &ctx->providerContextKey); + FwpmFreeMemory0((void**)&ctx); + } + } +} + +/** + * Remove policies associated to an entry from kernel + */ +static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry) +{ + if (entry->policy_in) + { + cleanup_policy(this, entry->mode == MODE_TRANSPORT, entry->policy_in); + entry->policy_in = 0; + } + if (entry->policy_out) + { + cleanup_policy(this, entry->mode == MODE_TRANSPORT, entry->policy_out); + entry->policy_out = 0; + } +} + /** * Destroy a SA/SP entry set */ @@ -190,14 +230,7 @@ static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry) { IPsecSaContextDeleteById0(this->handle, entry->sa_id); } - if (entry->policy_in) - { - FwpmFilterDeleteById0(this->handle, entry->policy_in); - } - if (entry->policy_out) - { - FwpmFilterDeleteById0(this->handle, entry->policy_out); - } + cleanup_policies(this, entry); array_destroy(entry->sas); array_destroy(entry->sps); entry->local->destroy(entry->local); @@ -662,10 +695,10 @@ static integrity_algorithm_t encr2integ(encryption_algorithm_t encr, int keylen) } /** - * Install a single transport mode SA + * Install a single SA */ -static bool install_transport_sa(private_kernel_wfp_ipsec_t *this, - entry_t *entry, sa_entry_t *sa, FWP_IP_VERSION version) +static bool install_sa(private_kernel_wfp_ipsec_t *this, + entry_t *entry, sa_entry_t *sa, FWP_IP_VERSION version) { IPSEC_SA_AUTH_AND_CIPHER_INFORMATION0 info = {}; IPSEC_SA0 ipsec = { @@ -758,18 +791,17 @@ static bool install_transport_sa(private_kernel_wfp_ipsec_t *this, } /** - * Install transport mode SAs to the kernel + * Install SAs to the kernel */ -static bool install_transport_sas(private_kernel_wfp_ipsec_t *this, - entry_t *entry) +static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, + IPSEC_TRAFFIC_TYPE type) { IPSEC_TRAFFIC0 traffic = { - .trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT, + .trafficType = type, }; IPSEC_GETSPI1 spi = { .inboundIpsecTraffic = { - .trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT, - .ipsecFilterId = entry->policy_in, + .trafficType = type, }, }; sa_entry_t *sa; @@ -777,6 +809,17 @@ static bool install_transport_sas(private_kernel_wfp_ipsec_t *this, enumerator_t *enumerator; DWORD res; + if (type == IPSEC_TRAFFIC_TYPE_TRANSPORT) + { + traffic.ipsecFilterId = entry->policy_out; + spi.inboundIpsecTraffic.ipsecFilterId = entry->policy_in; + } + else + { + traffic.tunnelPolicyId = entry->policy_in; + spi.inboundIpsecTraffic.tunnelPolicyId = entry->policy_in; + } + switch (entry->local->get_family(entry->local)) { case AF_INET: @@ -797,7 +840,6 @@ static bool install_transport_sas(private_kernel_wfp_ipsec_t *this, return FALSE; } - traffic.ipsecFilterId = entry->policy_out; res = IPsecSaContextCreate0(this->handle, &traffic, NULL, &entry->sa_id); if (res != ERROR_SUCCESS) { @@ -838,7 +880,7 @@ static bool install_transport_sas(private_kernel_wfp_ipsec_t *this, enumerator = array_create_enumerator(entry->sas); while (enumerator->enumerate(enumerator, &sa)) { - if (!install_transport_sa(this, entry, sa, spi.ipVersion)) + if (!install_sa(this, entry, sa, spi.ipVersion)) { enumerator->destroy(enumerator); IPsecSaContextDeleteById0(this->handle, entry->sa_id); @@ -858,20 +900,157 @@ static bool install_transport(private_kernel_wfp_ipsec_t *this, entry_t *entry) { if (install_transport_sp(this, entry, TRUE) && install_transport_sp(this, entry, FALSE) && - install_transport_sas(this, entry)) + install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TRANSPORT)) { return TRUE; } - if (entry->policy_in) + 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) { - FwpmFilterDeleteById0(this->handle, entry->policy_in); - entry->policy_in = 0; + return FALSE; } - if (entry->policy_out) + ok = rng->get_bytes(rng, sizeof(GUID), (u_int8_t*)guid); + rng->destroy(rng); + return ok; +} + +/** + * Install tunnel mode SPs to the kernel + */ +static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) +{ + FWPM_FILTER_CONDITION0 *conds = NULL; + int count = 0; + enumerator_t *enumerator; + sp_entry_t *sp; + 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 *ctx, qm = { + .displayData = { + .name = L"charon tunnel provider context", + }, + .providerKey = (GUID*)&this->provider.providerKey, + .type = FWPM_IPSEC_IKE_QM_TUNNEL_CONTEXT, + .ikeQmTunnelPolicy = &policy, + }; + + switch (entry->local->get_family(entry->local)) { - FwpmFilterDeleteById0(this->handle, entry->policy_out); - entry->policy_out = 0; + 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; + memcpy(&policy.tunnelEndpoints.localV6Address, + entry->local->get_address(entry->local).ptr, 16); + memcpy(&policy.tunnelEndpoints.remoteV6Address, + entry->remote->get_address(entry->remote).ptr, 16); + break; + default: + return FALSE; } + + if (!generate_guid(this, &qm.providerContextKey)) + { + return FALSE; + } + + enumerator = array_create_enumerator(entry->sps); + while (enumerator->enumerate(enumerator, &sp)) + { + if (sp->direction != POLICY_IN) + { + continue; + } + /* TODO: verify OUT/FORWARD matches to IN? */ + if (!ts2condition(sp->dst, TRUE, &conds, &count) || + !ts2condition(sp->src, FALSE, &conds, &count)) + { + free_conditions(conds, count); + enumerator->destroy(enumerator); + return FALSE; + } + } + enumerator->destroy(enumerator); + + res = FwpmIPsecTunnelAdd0(this->handle, 0, NULL, &qm, count, conds, NULL); + free_conditions(conds, count); + if (res != ERROR_SUCCESS) + { + DBG1(DBG_KNL, "installing FWP tunnel policy failed: 0x%08x", res); + return FALSE; + } + + /* to get the tunnelPolicyId LUID we have to query the context */ + res = FwpmProviderContextGetByKey0(this->handle, &qm.providerContextKey, + &ctx); + if (res != ERROR_SUCCESS) + { + DBG1(DBG_KNL, "getting FWP tunnel policy context failed: 0x%08x", res); + return FALSE; + } + entry->policy_in = ctx->providerContextId; + FwpmFreeMemory0((void**)&ctx); + + 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) && + install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TUNNEL)) + { + return TRUE; + } + cleanup_policies(this, entry); return FALSE; } @@ -885,6 +1064,7 @@ static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry) case MODE_TRANSPORT: return install_transport(this, entry); case MODE_TUNNEL: + return install_tunnel(this, entry); case MODE_BEET: default: return FALSE; From f351d9ef7d70ca5d62a768fef0ce14a2d934267e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 10 Dec 2013 18:15:41 +0100 Subject: [PATCH 08/50] kernel-wfp: Reference SA/SP sets by SPI and destination, not reqid This allows us to have multiple CHILD_SAs for the same reqid, and brings rekeying support. --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 416 +++++++----------- 1 file changed, 155 insertions(+), 261 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 402f4a398..a131b8b32 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -40,14 +40,19 @@ struct private_kernel_wfp_ipsec_t { refcount_t nextspi; /** - * SAD/SPD entries, as reqid => entry_t + * Temporary SAD/SPD entries referenced reqid, as uintptr_t => entry_t */ - hashtable_t *entries; + hashtable_t *tsas; /** - * SAD entry lookup, as sa_entry_t => entry_t + * SAD/SPD entries referenced by inbound SA, as sa_entry_t => entry_t */ - hashtable_t *sas; + hashtable_t *isas; + + /** + * SAD/SPD entries referenced by outbound SA, as sa_entry_t => entry_t + */ + hashtable_t *osas; /** * Mutex for accessing entries @@ -71,10 +76,10 @@ struct private_kernel_wfp_ipsec_t { typedef struct { /** SPI for this SA */ u_int32_t spi; + /** protocol, IPPROTO_ESP/IPPROTO_AH */ + u_int8_t protocol; /** destination host address for this SPI */ host_t *dst; - /** inbound or outbound SA? */ - bool inbound; struct { /** algorithm */ u_int16_t alg; @@ -83,16 +88,6 @@ typedef struct { } integ, encr; } sa_entry_t; -/** - * Destroy an SA entry - */ -static void sa_entry_destroy(sa_entry_t *sa) -{ - chunk_clear(&sa->integ.key); - chunk_clear(&sa->encr.key); - free(sa); -} - /** * Hash function for sas lookup table */ @@ -118,8 +113,6 @@ typedef struct { traffic_selector_t *src; /** policy destinaiton addresses */ traffic_selector_t *dst; - /** direction of policy, in|out */ - policy_dir_t direction; } sp_entry_t; /** @@ -142,12 +135,12 @@ typedef struct { host_t *local; /** outer address on remote host */ host_t *remote; - /** associated security associations, as sa_entry_t* */ - array_t *sas; - /** associated policies, as sp_entry_t* */ + /** 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 protocol, ESP|AH */ - u_int8_t protocol; /** IPsec mode, tunnel|transport */ ipsec_mode_t mode; /** UDP encapsulation */ @@ -160,27 +153,6 @@ typedef struct { u_int64_t sa_id; } entry_t; -/** - * Create a SA/SP entry set - */ -static entry_t *entry_create(u_int32_t reqid, host_t *local, host_t *remote, - u_int8_t protocol, ipsec_mode_t mode, bool encap) -{ - entry_t *entry; - - INIT(entry, - .reqid = reqid, - .sas = array_create(0, 0), - .sps = array_create(0, 0), - .local = local->clone(local), - .remote = remote->clone(remote), - .protocol = protocol, - .mode = mode, - .encap = encap, - ); - return entry; -} - /** * Remove a transport or tunnel policy from kernel */ @@ -231,39 +203,16 @@ static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry) IPsecSaContextDeleteById0(this->handle, entry->sa_id); } cleanup_policies(this, entry); - array_destroy(entry->sas); - array_destroy(entry->sps); + 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); } -/** - * Get an entry, create if not exists. May fail if non-matching entry found - */ -static entry_t *get_or_create_entry(private_kernel_wfp_ipsec_t *this, - u_int32_t reqid, host_t *local, host_t *remote, - u_int8_t protocol, ipsec_mode_t mode, bool encap) -{ - entry_t *entry; - - entry = this->entries->get(this->entries, (void*)(uintptr_t)reqid); - if (!entry) - { - entry = entry_create(reqid, local, remote, protocol, mode, encap); - this->entries->put(this->entries, (void*)(uintptr_t)reqid, entry); - return entry; - } - if (entry->protocol == protocol && - entry->mode == mode && - entry->local->ip_equals(entry->local, local) && - entry->remote->ip_equals(entry->remote, remote)) - { - return entry; - } - return NULL; -} - /** * Append/Realloc a filter condition to an existing condition set */ @@ -544,19 +493,11 @@ static bool install_transport_sp(private_kernel_wfp_ipsec_t *this, { if (inbound) { - if (sp->direction != POLICY_IN) - { - continue; - } local = sp->dst; remote = sp->src; } else { - if (sp->direction != POLICY_OUT) - { - continue; - } local = sp->src; remote = sp->dst; } @@ -697,8 +638,8 @@ static integrity_algorithm_t encr2integ(encryption_algorithm_t encr, int keylen) /** * Install a single SA */ -static bool install_sa(private_kernel_wfp_ipsec_t *this, - entry_t *entry, sa_entry_t *sa, FWP_IP_VERSION version) +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 = { @@ -715,7 +656,7 @@ static bool install_sa(private_kernel_wfp_ipsec_t *this, } integ = {}, encr = {}; DWORD res; - switch (entry->protocol) + switch (sa->protocol) { case IPPROTO_AH: ipsec.saTransformType = IPSEC_TRANSFORM_AH; @@ -773,7 +714,7 @@ static bool install_sa(private_kernel_wfp_ipsec_t *this, } } - if (sa->inbound) + if (inbound) { res = IPsecSaContextAddInbound0(this->handle, entry->sa_id, &bundle); } @@ -784,7 +725,7 @@ static bool install_sa(private_kernel_wfp_ipsec_t *this, if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "adding %sbound WFP SA failed: 0x%08x", - sa->inbound ? "in" : "out", res); + inbound ? "in" : "out", res); return FALSE; } return TRUE; @@ -804,9 +745,6 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, .trafficType = type, }, }; - sa_entry_t *sa; - IPSEC_SA_SPI inbound_spi = 0; - enumerator_t *enumerator; DWORD res; if (type == IPSEC_TRAFFIC_TYPE_TRANSPORT) @@ -847,28 +785,14 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, return FALSE; } - enumerator = array_create_enumerator(entry->sas); - while (enumerator->enumerate(enumerator, &sa)) - { - if (sa->inbound) - { - inbound_spi = ntohl(sa->spi); - break; - } - } - enumerator->destroy(enumerator); - if (!inbound_spi) - { - 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, inbound_spi); + 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); @@ -877,18 +801,13 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, return FALSE; } - enumerator = array_create_enumerator(entry->sas); - while (enumerator->enumerate(enumerator, &sa)) + if (!install_sa(this, entry, TRUE, &entry->isa, spi.ipVersion) || + !install_sa(this, entry, FALSE, &entry->osa, spi.ipVersion)) { - if (!install_sa(this, entry, sa, spi.ipVersion)) - { - enumerator->destroy(enumerator); - IPsecSaContextDeleteById0(this->handle, entry->sa_id); - entry->sa_id = 0; - return FALSE; - } + IPsecSaContextDeleteById0(this->handle, entry->sa_id); + entry->sa_id = 0; + return FALSE; } - enumerator->destroy(enumerator); return TRUE; } @@ -1003,13 +922,8 @@ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) { - if (sp->direction != POLICY_IN) - { - continue; - } - /* TODO: verify OUT/FORWARD matches to IN? */ - if (!ts2condition(sp->dst, TRUE, &conds, &count) || - !ts2condition(sp->src, FALSE, &conds, &count)) + if (!ts2condition(sp->src, TRUE, &conds, &count) || + !ts2condition(sp->dst, FALSE, &conds, &count)) { free_conditions(conds, count); enumerator->destroy(enumerator); @@ -1100,31 +1014,61 @@ METHOD(kernel_ipsec_t, add_sa, status_t, u_int16_t cpi, bool initiator, bool encap, bool esn, bool inbound, traffic_selector_t *src_ts, traffic_selector_t *dst_ts) { - status_t status = SUCCESS; host_t *local, *remote; entry_t *entry; - sa_entry_t *sa; if (inbound) { - local = dst; - remote = src; + /* comes first, create new entry */ + local = dst->clone(dst); + remote = src->clone(src); + + INIT(entry, + .reqid = reqid, + .isa = { + .spi = spi, + .dst = local, + .protocol = protocol, + .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, + ); + + 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 { - local = src; - remote = dst; - } + /* 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); - this->mutex->lock(this->mutex); - entry = get_or_create_entry(this, reqid, local, remote, - protocol, mode, encap); - if (entry) - { - INIT(sa, + 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, - .inbound = inbound, - .dst = inbound ? entry->local : entry->remote, + .dst = entry->remote, + .protocol = protocol, .encr = { .alg = enc_alg, .key = chunk_clone(enc_key), @@ -1133,19 +1077,14 @@ METHOD(kernel_ipsec_t, add_sa, status_t, .alg = int_alg, .key = chunk_clone(int_key), }, - ); - array_insert(entry->sas, -1, sa); - this->sas->put(this->sas, sa, entry); - } - else - { - DBG1(DBG_KNL, "adding SA failed, a different SA with reqid %u exists", - reqid); - status = FAILED; - } - this->mutex->unlock(this->mutex); + }; - return status; + 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, @@ -1168,61 +1107,33 @@ 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) { - status_t status = NOT_FOUND; entry_t *entry; - host_t *local, *remote; - enumerator_t *enumerator; - sa_entry_t *sa, key = { + sa_entry_t key = { .dst = dst, .spi = spi, }; this->mutex->lock(this->mutex); - - entry = this->sas->get(this->sas, &key); - if (entry) - { - enumerator = array_create_enumerator(entry->sas); - while (enumerator->enumerate(enumerator, &sa)) - { - if (sa->inbound) - { - local = dst; - remote = src; - } - else - { - local = src; - remote = dst; - } - if (sa->spi == spi && entry->protocol == protocol && - local->ip_equals(local, entry->local) && - remote->ip_equals(remote, entry->remote)) - { - array_remove_at(entry->sas, enumerator); - this->sas->remove(this->sas, sa); - /* TODO: uninstall SA from kernel */ - sa_entry_destroy(sa); - status = SUCCESS; - break; - } - } - enumerator->destroy(enumerator); - - if (!array_count(entry->sas) && !array_count(entry->sps)) - { - entry = this->entries->remove(this->entries, - (void*)(uintptr_t)entry->reqid); - if (entry) - { - entry_destroy(this, entry); - } - } - } - + entry = this->isas->remove(this->isas, &key); this->mutex->unlock(this->mutex); - return status; + 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, @@ -1238,50 +1149,70 @@ METHOD(kernel_ipsec_t, add_policy, status_t, policy_priority_t priority) { status_t status = SUCCESS; - host_t *local, *remote; entry_t *entry; sp_entry_t *sp; + sa_entry_t key = { + .spi = sa->esp.use ? sa->esp.spi : sa->ah.spi, + .dst = dst, + }; - if (direction == POLICY_FWD || priority != POLICY_PRIORITY_DEFAULT) + if (sa->esp.use && sa->ah.use) { - return SUCCESS; + return NOT_SUPPORTED; } - if (direction == POLICY_IN) + switch (direction) { - local = dst; - remote = src; + case POLICY_OUT: + break; + case POLICY_IN: + case POLICY_FWD: + /* not required */ + return SUCCESS; + default: + return NOT_SUPPORTED; } - else + + switch (priority) { - local = src; - remote = dst; + case POLICY_PRIORITY_DEFAULT: + break; + case POLICY_PRIORITY_FALLBACK: + /* TODO: install fallback policy? */ + return SUCCESS; + case POLICY_PRIORITY_ROUTED: + /* TODO: install trap policy with low prio */ + default: + return NOT_SUPPORTED; } this->mutex->lock(this->mutex); - entry = get_or_create_entry(this, sa->reqid, local, remote, - sa->esp.use ? IPPROTO_ESP : IPPROTO_AH, - sa->mode, FALSE); + entry = this->osas->get(this->osas, &key); if (entry) { - INIT(sp, - .src = src_ts->clone(src_ts), - .dst = dst_ts->clone(dst_ts), - .direction = direction, - ); - array_insert(entry->sps, -1, sp); - if (array_count(entry->sps) > 1) + if (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 (!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... */ + status = NOT_SUPPORTED; + } } else { - DBG1(DBG_KNL, "adding SP failed, a different SP with reqid %u exists", - sa->reqid); + DBG1(DBG_KNL, "adding SP failed, no SA found for SPI 0x%08x", key.spi); status = FAILED; } this->mutex->unlock(this->mutex); @@ -1302,46 +1233,8 @@ METHOD(kernel_ipsec_t, del_policy, status_t, traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid, mark_t mark, policy_priority_t priority) { - status_t status = NOT_FOUND; - entry_t *entry; - sp_entry_t *sp; - enumerator_t *enumerator; - - this->mutex->lock(this->mutex); - - entry = this->entries->get(this->entries, (void*)(uintptr_t)reqid); - if (entry) - { - enumerator = array_create_enumerator(entry->sps); - while (enumerator->enumerate(enumerator, &sp)) - { - if (sp->direction == direction && - src_ts->equals(src_ts, sp->src) && - dst_ts->equals(dst_ts, sp->dst)) - { - array_remove_at(entry->sps, enumerator); - /* TODO: uninstall SP from kernel */ - sp_entry_destroy(sp); - status = SUCCESS; - break; - } - } - enumerator->destroy(enumerator); - - if (!array_count(entry->sas) && !array_count(entry->sps)) - { - entry = this->entries->remove(this->entries, - (void*)(uintptr_t)reqid); - if (entry) - { - entry_destroy(this, entry); - } - } - } - - this->mutex->unlock(this->mutex); - - return status; + /* not required, as we delete the whole SA/SP set during del_sa() */ + return SUCCESS; } METHOD(kernel_ipsec_t, flush_policies, status_t, @@ -1370,8 +1263,9 @@ METHOD(kernel_ipsec_t, destroy, void, FwpmProviderDeleteByKey0(this->handle, &this->provider.providerKey); FwpmEngineClose0(this->handle); } - this->entries->destroy(this->entries); - this->sas->destroy(this->sas); + this->tsas->destroy(this->tsas); + this->isas->destroy(this->isas); + this->osas->destroy(this->osas); this->mutex->destroy(this->mutex); free(this); } @@ -1420,9 +1314,9 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() }, .nextspi = htonl(0xc0000001), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), - .entries = hashtable_create(hashtable_hash_ptr, - hashtable_equals_ptr, 4), - .sas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4), + .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), ); res = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, From b50d486e78a387bc06f7f60d5e74dd08d48ea9a7 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 14:36:21 +0100 Subject: [PATCH 09/50] kernel-wfp: Add some notes about query_sa/policy() support --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index a131b8b32..fd27b4ffe 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1100,6 +1100,10 @@ METHOD(kernel_ipsec_t, query_sa, status_t, 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; } @@ -1225,6 +1229,7 @@ METHOD(kernel_ipsec_t, query_policy, status_t, 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; } From b3f90915f9f76e3c756b377e4e78144faae0063c Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 14:42:56 +0100 Subject: [PATCH 10/50] kernel-wfp: Enforce hard lifetimes of SAs --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index fd27b4ffe..0b5f27e8c 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -78,6 +78,8 @@ typedef struct { 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 { @@ -646,6 +648,10 @@ static bool install_sa(private_kernel_wfp_ipsec_t *this, entry_t *entry, .spi = ntohl(sa->spi), }; IPSEC_SA_BUNDLE0 bundle = { + .lifetime = { + .lifetimeSeconds = inbound ? entry->isa.lifetime + : entry->osa.lifetime, + }, .saList = &ipsec, .numSAs = 1, .ipVersion = version, @@ -1029,6 +1035,7 @@ METHOD(kernel_ipsec_t, add_sa, status_t, .spi = spi, .dst = local, .protocol = protocol, + .lifetime = lifetime->time.life, .encr = { .alg = enc_alg, .key = chunk_clone(enc_key), @@ -1069,6 +1076,7 @@ METHOD(kernel_ipsec_t, add_sa, status_t, .spi = spi, .dst = entry->remote, .protocol = protocol, + .lifetime = lifetime->time.life, .encr = { .alg = enc_alg, .key = chunk_clone(enc_key), From af098b500812db1384dd580d693f7a624679ecd8 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 15:14:55 +0100 Subject: [PATCH 11/50] kernel-wfp: Triggering expire events for SAs to rekey/delete --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 0b5f27e8c..e8ef277dd 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -20,9 +20,11 @@ #include "kernel_wfp_ipsec.h" #include +#include #include #include #include +#include typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t; @@ -1012,6 +1014,103 @@ METHOD(kernel_ipsec_t, get_cpi, status_t, 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, @@ -1052,6 +1151,15 @@ METHOD(kernel_ipsec_t, add_sa, status_t, .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); From cd88f818fa48da9797910653c97bdb4ffc9fd0c5 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 15:38:28 +0100 Subject: [PATCH 12/50] kernel-wfp: Increment SPIs properly, that is while in host order --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index e8ef277dd..9073dec33 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1003,7 +1003,7 @@ 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) { - *spi = ref_get(&this->nextspi); + *spi = htonl(ref_get(&this->nextspi)); return SUCCESS; } @@ -1433,7 +1433,7 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .providerKey = { 0x59cdae2e, 0xf6bb, 0x4c09, { 0xa9,0x59,0x9d,0x91,0xac,0xaf,0xf9,0x19 }}, }, - .nextspi = htonl(0xc0000001), + .nextspi = 0xc0000001, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .tsas = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), .isas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4), From 7452adfad38f36f2996057237e5588c75dba0766 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 15:57:46 +0100 Subject: [PATCH 13/50] kernel-interface: Add a flag to indicate no policy updates required --- src/libcharon/sa/child_sa.c | 17 ++++++++++++++--- src/libhydra/kernel/kernel_interface.h | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index 847cfc78f..bbf6259c2 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -730,6 +730,17 @@ METHOD(child_sa_t, install, status_t, return status; } +/** + * 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 */ @@ -842,7 +853,7 @@ METHOD(child_sa_t, add_policies, status_t, { /* 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 +947,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 +1086,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), }; /** From b93492980489fa4ef0664c0ac4669592537ce1c2 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 16:02:11 +0100 Subject: [PATCH 14/50] kernel-wfp: Disable IPsec policy updates It seems that WFP requires an update of the SA context only, but not for the filters. This allows us to omit support for (fallback) drop policies. --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 9073dec33..a7d8a9839 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -996,7 +996,7 @@ static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry) METHOD(kernel_ipsec_t, get_features, kernel_feature_t, private_kernel_wfp_ipsec_t *this) { - return KERNEL_ESP_V3_TFC; + return KERNEL_ESP_V3_TFC | KERNEL_NO_POLICY_UPDATES; } METHOD(kernel_ipsec_t, get_spi, status_t, @@ -1281,6 +1281,15 @@ METHOD(kernel_ipsec_t, add_policy, status_t, return NOT_SUPPORTED; } + switch (type) + { + case POLICY_IPSEC: + break; + case POLICY_PASS: + case POLICY_DROP: + return NOT_SUPPORTED; + } + switch (direction) { case POLICY_OUT: @@ -1297,11 +1306,9 @@ METHOD(kernel_ipsec_t, add_policy, status_t, { case POLICY_PRIORITY_DEFAULT: break; - case POLICY_PRIORITY_FALLBACK: - /* TODO: install fallback policy? */ - return SUCCESS; case POLICY_PRIORITY_ROUTED: /* TODO: install trap policy with low prio */ + case POLICY_PRIORITY_FALLBACK: default: return NOT_SUPPORTED; } From 00780f0238c7e598269fc05dec4c4d7813e73cbb Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 12 Dec 2013 09:35:36 +0100 Subject: [PATCH 15/50] kernel-iph: Add a stub for a Windows IP Helper based networking backend --- configure.ac | 4 + src/libcharon/Makefile.am | 7 ++ src/libcharon/plugins/kernel_iph/Makefile.am | 20 +++ .../plugins/kernel_iph/kernel_iph_net.c | 115 ++++++++++++++++++ .../plugins/kernel_iph/kernel_iph_net.h | 46 +++++++ .../plugins/kernel_iph/kernel_iph_plugin.c | 76 ++++++++++++ .../plugins/kernel_iph/kernel_iph_plugin.h | 42 +++++++ 7 files changed, 310 insertions(+) create mode 100644 src/libcharon/plugins/kernel_iph/Makefile.am create mode 100644 src/libcharon/plugins/kernel_iph/kernel_iph_net.c create mode 100644 src/libcharon/plugins/kernel_iph/kernel_iph_net.h create mode 100644 src/libcharon/plugins/kernel_iph/kernel_iph_plugin.c create mode 100644 src/libcharon/plugins/kernel_iph/kernel_iph_plugin.h diff --git a/configure.ac b/configure.ac index 82d6fd286..736f097c1 100644 --- a/configure.ac +++ b/configure.ac @@ -204,6 +204,7 @@ 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]) @@ -1211,6 +1212,7 @@ 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]) @@ -1371,6 +1373,7 @@ 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) @@ -1666,6 +1669,7 @@ AC_CONFIG_FILES([ 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/src/libcharon/Makefile.am b/src/libcharon/Makefile.am index 907f65932..e81c42405 100644 --- a/src/libcharon/Makefile.am +++ b/src/libcharon/Makefile.am @@ -496,6 +496,13 @@ if MONOLITHIC 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..8cb4aec2b --- /dev/null +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -0,0 +1,115 @@ +/* + * 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 . + * + * 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_net.h" + +#include + +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; +}; + +METHOD(kernel_net_t, get_interface_name, bool, + private_kernel_iph_net_t *this, host_t* ip, char **name) +{ + return FALSE; +} + +METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, + private_kernel_iph_net_t *this, kernel_address_type_t which) +{ + return enumerator_create_empty(); +} + +METHOD(kernel_net_t, get_source_addr, host_t*, + private_kernel_iph_net_t *this, host_t *dest, host_t *src) +{ + return NULL; +} + +METHOD(kernel_net_t, get_nexthop, host_t*, + private_kernel_iph_net_t *this, host_t *dest, host_t *src) +{ + 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; +} + +METHOD(kernel_net_t, add_route, status_t, + private_kernel_iph_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, del_route, status_t, + private_kernel_iph_net_t *this, chunk_t dst_net, u_int8_t prefixlen, + host_t *gateway, host_t *src_ip, char *if_name) +{ + return NOT_SUPPORTED; +} + +METHOD(kernel_net_t, destroy, void, + private_kernel_iph_net_t *this) +{ + free(this); +} + +/* + * Described in header. + */ +kernel_iph_net_t *kernel_iph_net_create() +{ + private_kernel_iph_net_t *this; + + 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, + }, + }, + ); + + 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 . + * + * 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 + +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 . + * + * 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 + +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 . + * + * 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 + +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_ @}*/ From 96f1978d0e5decb5280cdbf757d6a787a12162c2 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 12 Dec 2013 15:37:21 +0100 Subject: [PATCH 16/50] kernel-iph: Create and maintain a cache of interfaces and associated addresses --- .../plugins/kernel_iph/kernel_iph_net.c | 328 ++++++++++++++++++ 1 file changed, 328 insertions(+) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 8cb4aec2b..28c110b35 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -13,9 +13,21 @@ * for more details. */ +/* Windows 7, for some iphlpapi.h functionality */ +#define _WIN32_WINNT 0x0601 +#include +#include +#include +#include +#include +#include + #include "kernel_iph_net.h" #include +#include +#include + typedef struct private_kernel_iph_net_t private_kernel_iph_net_t; @@ -28,8 +40,305 @@ struct private_kernel_iph_net_t { * Public interface. */ kernel_iph_net_t public; + + /** + * NotifyIpInterfaceChange() handle + */ + HANDLE changes; + + /** + * Mutex to access interface list + */ + mutex_t *mutex; + + /** + * Known interfaces, as iface_t + */ + linked_list_t *ifaces; }; +/** + * 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", +); + +/** + * 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; + + 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); + } + } + } + + while (list->remove_first(list, (void**)&old) == SUCCESS) + { + if (log) + { + DBG1(DBG_KNL, "%H disappeared from interface %u '%s'", + old, entry->ifindex, entry->ifdesc); + } + old->destroy(old); + } + list->destroy(list); +} + +/** + * 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); + } + } + 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; + } + 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); + } + } +} + METHOD(kernel_net_t, get_interface_name, bool, private_kernel_iph_net_t *this, host_t* ip, char **name) { @@ -85,6 +394,12 @@ METHOD(kernel_net_t, del_route, status_t, METHOD(kernel_net_t, destroy, void, private_kernel_iph_net_t *this) { + if (this->changes) + { + CancelMibChangeNotify2(this->changes); + } + this->mutex->destroy(this->mutex); + this->ifaces->destroy_function(this->ifaces, (void*)iface_destroy); free(this); } @@ -94,6 +409,7 @@ METHOD(kernel_net_t, destroy, void, kernel_iph_net_t *kernel_iph_net_create() { private_kernel_iph_net_t *this; + ULONG res; INIT(this, .public = { @@ -109,7 +425,19 @@ kernel_iph_net_t *kernel_iph_net_create() .destroy = _destroy, }, }, + .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; } From 322c341f901f4370c4607268ad0b5d4c36b5df66 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 12 Dec 2013 15:41:39 +0100 Subject: [PATCH 17/50] kernel-iph: Implement get_interface() method --- .../plugins/kernel_iph/kernel_iph_net.c | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 28c110b35..4c870608c 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -339,10 +339,47 @@ static void change_interface(private_kernel_iph_net_t *this, } } +/** + * 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) { - return FALSE; + 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; } METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, From f9e6200d066f210e7c5a49048f5c7ec5c002ff7a Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 12 Dec 2013 16:29:31 +0100 Subject: [PATCH 18/50] kernel-iph: Implement address enumeration --- .../plugins/kernel_iph/kernel_iph_net.c | 80 ++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 4c870608c..3d7fd7b88 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -382,10 +382,88 @@ METHOD(kernel_net_t, get_interface_name, bool, 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) { - return enumerator_create_empty(); + 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*, From 0cefd9400734fea388b86d8d2a1075907f96d769 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 12 Dec 2013 17:28:51 +0100 Subject: [PATCH 19/50] kernel-iph: Implement get_source_addr() --- .../plugins/kernel_iph/kernel_iph_net.c | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 3d7fd7b88..56c458fdb 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -469,7 +469,29 @@ METHOD(kernel_net_t, create_address_enumerator, enumerator_t*, METHOD(kernel_net_t, get_source_addr, host_t*, private_kernel_iph_net_t *this, host_t *dest, host_t *src) { - return NULL; + 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*, From 13e18cb2fc52661152c570a5be3e7cddce388902 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 12 Dec 2013 17:34:06 +0100 Subject: [PATCH 20/50] kernel-iph: Implement get_nexthop() --- .../plugins/kernel_iph/kernel_iph_net.c | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 56c458fdb..36e3c4c56 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -497,6 +497,38 @@ METHOD(kernel_net_t, get_source_addr, host_t*, 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; } From 0ef0493b4aae7b19ce5f81a9e376566546ee49f6 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 12:52:06 +0100 Subject: [PATCH 21/50] kernel-iph: Implement add/del_route() --- .../plugins/kernel_iph/kernel_iph_net.c | 88 +++++++++++++++++-- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 36e3c4c56..e0652ce6f 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -546,18 +546,92 @@ METHOD(kernel_net_t, del_ip, status_t, return NOT_SUPPORTED; } -METHOD(kernel_net_t, add_route, status_t, - private_kernel_iph_net_t *this, chunk_t dst_net, u_int8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) +/** + * 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) { - return NOT_SUPPORTED; + 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; + } + 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_net, u_int8_t prefixlen, - host_t *gateway, host_t *src_ip, char *if_name) + private_kernel_iph_net_t *this, chunk_t dst, u_int8_t prefixlen, + host_t *gateway, host_t *src, char *name) { - return NOT_SUPPORTED; + return manage_route(this, FALSE, dst, prefixlen, gateway, name); } METHOD(kernel_net_t, destroy, void, From b714746ef051ccd4ea899d1bf0fe167485312183 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 15:33:42 +0100 Subject: [PATCH 22/50] kernel-wfp: Install appropriate routes for tunnel mode policies --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 209 +++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index a7d8a9839..0f78ef665 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -56,6 +56,11 @@ struct private_kernel_wfp_ipsec_t { */ hashtable_t *osas; + /** + * Installed routes, as route_t => route_t + */ + hashtable_t *routes; + /** * Mutex for accessing entries */ @@ -157,6 +162,56 @@ typedef struct { 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 a transport or tunnel policy from kernel */ @@ -195,6 +250,10 @@ static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry) cleanup_policy(this, entry->mode == MODE_TRANSPORT, entry->policy_out); entry->policy_out = 0; } + if (entry->mode == MODE_TUNNEL) + { + manage_routes(this, entry, FALSE); + } } /** @@ -962,12 +1021,158 @@ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) 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 routes for IPsec policies + */ +static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, + bool add) +{ + enumerator_t *enumerator; + host_t *src, *dst, *gtw; + sp_entry_t *sp; + u_int8_t mask; + + enumerator = array_create_enumerator(entry->sps); + while (enumerator->enumerate(enumerator, &sp)) + { + if (!sp->dst->to_subnet(sp->dst, &dst, &mask)) + { + continue; + } + if (hydra->kernel_interface->get_address_by_ts( hydra->kernel_interface, + sp->src, &src, NULL) != SUCCESS) + { + dst->destroy(dst); + continue; + } + gtw = hydra->kernel_interface->get_nexthop(hydra->kernel_interface, + entry->remote, entry->local); + if (add) + { + if (!install_route(this, dst, mask, src, gtw)) + { + DBG1(DBG_KNL, "installing route for policy %R === %R failed", + sp->src, sp->dst); + } + } + else + { + if (!uninstall_route(this, dst, mask, src, gtw)) + { + DBG1(DBG_KNL, "uninstalling route for policy %R === %R failed", + sp->src, sp->dst); + } + } + dst->destroy(dst); + src->destroy(src); + DESTROY_IF(gtw); + } + 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; @@ -1394,6 +1599,7 @@ METHOD(kernel_ipsec_t, destroy, void, this->tsas->destroy(this->tsas); this->isas->destroy(this->isas); this->osas->destroy(this->osas); + this->routes->destroy(this->routes); this->mutex->destroy(this->mutex); free(this); } @@ -1441,10 +1647,11 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() { 0xa9,0x59,0x9d,0x91,0xac,0xaf,0xf9,0x19 }}, }, .nextspi = 0xc0000001, - .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .mutex = mutex_create(MUTEX_TYPE_RECURSIVE), .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), ); res = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, From bbe42a1fa545af0131fdfdce6040a95b562d7c09 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 15:34:13 +0100 Subject: [PATCH 23/50] kernel-wfp: Allocate SPIs pseudo-randomly using a 0xc prefix --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 0f78ef665..83248889b 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -41,6 +41,11 @@ struct private_kernel_wfp_ipsec_t { */ refcount_t nextspi; + /** + * Mix value to distribute SPI allocation randomly + */ + u_int32_t mixspi; + /** * Temporary SAD/SPD entries referenced reqid, as uintptr_t => entry_t */ @@ -1204,11 +1209,58 @@ METHOD(kernel_ipsec_t, get_features, kernel_feature_t, 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) { - *spi = htonl(ref_get(&this->nextspi)); + /* 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; } @@ -1646,7 +1698,6 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .providerKey = { 0x59cdae2e, 0xf6bb, 0x4c09, { 0xa9,0x59,0x9d,0x91,0xac,0xaf,0xf9,0x19 }}, }, - .nextspi = 0xc0000001, .mutex = mutex_create(MUTEX_TYPE_RECURSIVE), .tsas = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), .isas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4), @@ -1654,6 +1705,12 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .routes = hashtable_create((void*)hash_route, (void*)equals_route, 4), ); + if (!init_spi(this)) + { + destroy(this); + return NULL; + } + res = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &this->handle); if (res != ERROR_SUCCESS) From 3551fdbbdfcd4a97415d87a8e14912a562bcf4f9 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 17:12:38 +0100 Subject: [PATCH 24/50] kernel-iph: Fire roam events for detected address changes --- .../plugins/kernel_iph/kernel_iph_net.c | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index e0652ce6f..71c595ba7 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -27,8 +27,12 @@ #include #include #include +#include +/** delay before firing roam events (ms) */ +#define ROAM_DELAY 500 + typedef struct private_kernel_iph_net_t private_kernel_iph_net_t; /** @@ -55,6 +59,16 @@ struct private_kernel_iph_net_t { * 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; }; /** @@ -99,6 +113,42 @@ ENUM(if_oper_names, IfOperStatusUp, IfOperStatusLowerLayerDown, "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 */ @@ -109,6 +159,7 @@ static void update_addrs(private_kernel_iph_net_t *this, iface_t *entry, enumerator_t *enumerator; linked_list_t *list; host_t *host, *old; + bool changes = FALSE; list = entry->addrs; entry->addrs = linked_list_create(); @@ -149,6 +200,7 @@ static void update_addrs(private_kernel_iph_net_t *this, iface_t *entry, { DBG1(DBG_KNL, "%H appeared on interface %u '%s'", host, entry->ifindex, entry->ifdesc); + changes = TRUE; } } } @@ -159,10 +211,16 @@ static void update_addrs(private_kernel_iph_net_t *this, iface_t *entry, { 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); + } } /** @@ -234,6 +292,7 @@ static void remove_interface(private_kernel_iph_net_t *this, NET_IFINDEX index) DBG1(DBG_KNL, "interface %u '%s' disappeared", entry->ifindex, entry->ifdesc); iface_destroy(entry); + fire_roam_event(this, TRUE); } } enumerator->destroy(enumerator); @@ -261,6 +320,7 @@ static void update_interface(private_kernel_iph_net_t *this, 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); } From 9b5c95648f2b1a94bf0a9e2b1cf9efe819958dfd Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 17:13:39 +0100 Subject: [PATCH 25/50] kernel-wfp: Refactor SA context construction, and use IPsecSaContextCreate1() --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 83248889b..05798fdee 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -803,13 +803,40 @@ static bool install_sa(private_kernel_wfp_ipsec_t *this, entry_t *entry, return TRUE; } +/** + * 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; + memcpy(&traffic->localV6Address, l->get_address(l).ptr, 16); + memcpy(&traffic->remoteV6Address, r->get_address(r).ptr, 16); + 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_TRAFFIC0 traffic = { + IPSEC_TRAFFIC1 traffic = { .trafficType = type, }; IPSEC_GETSPI1 spi = { @@ -830,27 +857,13 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, spi.inboundIpsecTraffic.tunnelPolicyId = entry->policy_in; } - switch (entry->local->get_family(entry->local)) + if (!hosts2traffic(this, entry->local, entry->remote, &traffic)) { - case AF_INET: - traffic.ipVersion = FWP_IP_VERSION_V4; - traffic.localV4Address = - untoh32(entry->local->get_address(entry->local).ptr); - traffic.remoteV4Address = - untoh32(entry->remote->get_address(entry->remote).ptr); - break; - case AF_INET6: - traffic.ipVersion = FWP_IP_VERSION_V6; - memcpy(&traffic.localV6Address, - entry->local->get_address(entry->local).ptr, 16); - memcpy(&traffic.remoteV6Address, - entry->remote->get_address(entry->remote).ptr, 16); - break; - default: - return FALSE; + return FALSE; } - res = IPsecSaContextCreate0(this->handle, &traffic, NULL, &entry->sa_id); + 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); From 1987b709895dbefb7e8c3298b0c5808731f57b93 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 16 Dec 2013 12:13:39 +0100 Subject: [PATCH 26/50] kernel-wfp: Configure ports for SAs using UDP encapsulation --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 05798fdee..f26b60a92 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -894,6 +894,37 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, 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; } From 5a5b9925f87136fe0ca1c39448205018e69b6248 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 17:14:26 +0100 Subject: [PATCH 27/50] kernel-wfp: Implement update_sa() --- .../plugins/kernel_wfp/kernel_wfp_compat.c | 6 ++ .../plugins/kernel_wfp/kernel_wfp_compat.h | 11 +++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 98 ++++++++++++++++++- 3 files changed, 114 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c index a687c4371..6b32f3192 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -90,3 +90,9 @@ STUB(fwpuclnt, DWORD, IPsecSaContextCreate1, 40, 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) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h index efccd4c45..76a6c85b8 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -51,6 +51,17 @@ enum { 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), +}; + DWORD WINAPI FwpmIPsecTunnelAdd0(HANDLE, UINT32, const FWPM_PROVIDER_CONTEXT0*, const FWPM_PROVIDER_CONTEXT0*, UINT32, const FWPM_FILTER_CONDITION0*, PSECURITY_DESCRIPTOR); diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index f26b60a92..3dbbb30a6 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1509,7 +1509,103 @@ METHOD(kernel_ipsec_t, update_sa, status_t, 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) { - return NOT_SUPPORTED; + 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, From 9c974c329d0ad6f4b303236f1b2c29f0c8e1b39f Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 16 Dec 2013 12:12:57 +0100 Subject: [PATCH 28/50] kernel-wfp: Depend on used RNG plugin features --- src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c index 3260c7eed..e465b0a76 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_plugin.c @@ -43,6 +43,8 @@ METHOD(plugin_t, get_features, int, 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); From 489a4f2192a4cc246869fe8e129e7f31196ccc77 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 16 Dec 2013 17:13:03 +0100 Subject: [PATCH 29/50] kernel-wfp: Add an ipsecdump utility to show installed SAs/SPs on Windows --- src/libcharon/plugins/kernel_wfp/Makefile.am | 9 + src/libcharon/plugins/kernel_wfp/ipsecdump.c | 419 ++++++++++++++++++ .../plugins/kernel_wfp/kernel_wfp_compat.c | 4 + 3 files changed, 432 insertions(+) create mode 100644 src/libcharon/plugins/kernel_wfp/ipsecdump.c diff --git a/src/libcharon/plugins/kernel_wfp/Makefile.am b/src/libcharon/plugins/kernel_wfp/Makefile.am index 405e581e4..5ff93da4b 100644 --- a/src/libcharon/plugins/kernel_wfp/Makefile.am +++ b/src/libcharon/plugins/kernel_wfp/Makefile.am @@ -19,3 +19,12 @@ libstrongswan_kernel_wfp_la_SOURCES = \ 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 diff --git a/src/libcharon/plugins/kernel_wfp/ipsecdump.c b/src/libcharon/plugins/kernel_wfp/ipsecdump.c new file mode 100644 index 000000000..af3bd044c --- /dev/null +++ b/src/libcharon/plugins/kernel_wfp/ipsecdump.c @@ -0,0 +1,419 @@ +/* + * 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 . + * + * 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 + +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; +} + +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 } +}; + +/** + * 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; +} + +/** + * 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; + } + 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 index 6b32f3192..05659399d 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -96,3 +96,7 @@ STUB(fwpuclnt, DWORD, IPsecSaContextGetById1, 24, 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) From 288dc68596f6c5b52d061356e3599ba50087e355 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 18 Dec 2013 11:57:36 +0100 Subject: [PATCH 30/50] kernel-wfp: Add an ipsecdump "filters" command to print IPsec related filters --- src/libcharon/plugins/kernel_wfp/ipsecdump.c | 283 ++++++++++++++++++- 1 file changed, 282 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_wfp/ipsecdump.c b/src/libcharon/plugins/kernel_wfp/ipsecdump.c index af3bd044c..d2aa26182 100644 --- a/src/libcharon/plugins/kernel_wfp/ipsecdump.c +++ b/src/libcharon/plugins/kernel_wfp/ipsecdump.c @@ -389,6 +389,280 @@ static bool list_contexts(HANDLE engine) 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 = { + 0xa82acc24, 0x4ee1, 0x4ee1, { 0xb4, 0x65, 0xfd, 0x1d, 0x25, 0xcb, 0x10, 0xa4 } +}; +const GUID FWPM_LAYER_IPFORWARD_V4_DISCARD = { + 0x9e9ea773, 0x2fae, 0x4210, { 0x8f, 0x17, 0x34, 0x12, 0x9e, 0xf3, 0x69, 0xeb } +}; +const GUID FWPM_LAYER_IPFORWARD_V6 = { + 0x7b964818, 0x19c7, 0x493a, { 0xb7, 0x1f, 0x83, 0x2c, 0x36, 0x84, 0xd2, 0x8c } +}; +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; +} + +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 } +}; + +/** + * 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); + + 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() */ @@ -413,7 +687,14 @@ int main(int argc, char *argv[]) fprintf(stderr, "FwpmEngineOpen(): 0x%08x\n", res); return 2; } - code = list_contexts(engine) ? 0 : 1; + if (argc > 1 && streq(argv[1], "filters")) + { + code = list_filters(engine) ? 0 : 1; + } + else + { + code = list_contexts(engine) ? 0 : 1; + } FwpmEngineClose0(engine); return code; } From 6aaa432741c6675493c7a07bc5e9c5ea69eaf21e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 18 Dec 2013 11:56:36 +0100 Subject: [PATCH 31/50] kernel-wfp: Add some missing IPv6 GUIDs, fix IPv6 host conversion --- .../plugins/kernel_wfp/kernel_wfp_compat.c | 12 ++++++++++++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c index 05659399d..2be369159 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -33,15 +33,27 @@ const GUID FWPM_CONDITION_IP_PROTOCOL = { 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_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 } +}; /** * Load a function symbol from a loaded dll diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 3dbbb30a6..a658fd150 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -803,6 +803,21 @@ static bool install_sa(private_kernel_wfp_ipsec_t *this, entry_t *entry, 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 */ @@ -822,8 +837,8 @@ static bool hosts2traffic(private_kernel_wfp_ipsec_t *this, return TRUE; case AF_INET6: traffic->ipVersion = FWP_IP_VERSION_V6; - memcpy(&traffic->localV6Address, l->get_address(l).ptr, 16); - memcpy(&traffic->remoteV6Address, r->get_address(r).ptr, 16); + host2address6(l, &traffic->localV6Address); + host2address6(r, &traffic->remoteV6Address); return TRUE; default: return FALSE; From 28683140282493fb6b9dbf825961154d0ff02504 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 09:48:43 +0100 Subject: [PATCH 32/50] kernel-wfp: Register for WFP Net events --- .../plugins/kernel_wfp/kernel_wfp_compat.c | 7 ++++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 41 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c index 2be369159..4296e147c 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -112,3 +112,10 @@ STUB(fwpuclnt, DWORD, IPsecSaContextUpdate0, 24, 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_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index a658fd150..da5e3cea9 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -80,6 +80,11 @@ struct private_kernel_wfp_ipsec_t { * Provider charon registers as */ FWPM_PROVIDER0 provider; + + /** + * Event handle + */ + HANDLE event; }; /** @@ -1262,6 +1267,32 @@ static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry) } } +/** + * FwpmNetEventSubscribe0() callback + */ +static void event_callback(private_kernel_wfp_ipsec_t *this, + const FWPM_NET_EVENT1 *event) +{ +} + +/** + * 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; +} + METHOD(kernel_ipsec_t, get_features, kernel_feature_t, private_kernel_wfp_ipsec_t *this) { @@ -1800,6 +1831,10 @@ METHOD(kernel_ipsec_t, destroy, void, { if (this->handle) { + if (this->event) + { + FwpmNetEventUnsubscribe0(this->handle, this->event); + } FwpmProviderDeleteByKey0(this->handle, &this->provider.providerKey); FwpmEngineClose0(this->handle); } @@ -1883,5 +1918,11 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() return NULL; } + if (!register_events(this)) + { + destroy(this); + return NULL; + } + return &this->public; } From f206e069f132488508ada60daff2ef1cdd3b2afc Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 14:13:06 +0100 Subject: [PATCH 33/50] kernel-wfp: Implement bypass_socket() using dedicated filter rules --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 119 +++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index da5e3cea9..68d75c2ad 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -46,6 +46,11 @@ struct private_kernel_wfp_ipsec_t { */ 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 */ @@ -1814,21 +1819,129 @@ METHOD(kernel_ipsec_t, flush_policies, status_t, 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 FWP trap 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) { - return NOT_SUPPORTED; + 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 NOT_SUPPORTED; + 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) @@ -1838,6 +1951,7 @@ METHOD(kernel_ipsec_t, destroy, void, 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); @@ -1889,6 +2003,7 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() { 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), From 11e7d0677c3ec6f241ba387d5aadba113b56e9be Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 14:14:37 +0100 Subject: [PATCH 34/50] socket-win: Install IKE bypass policies using bypass_socket() --- src/libcharon/plugins/socket_win/socket_win_plugin.c | 1 + src/libcharon/plugins/socket_win/socket_win_socket.c | 11 +++++++++++ 2 files changed, 12 insertions(+) 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 +#include #include #include @@ -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; } From c6f189e4489c07c5bbc18fca677dc4d7812b4e23 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 14:22:00 +0100 Subject: [PATCH 35/50] kernel-wfp: Add support for trap policies and acquires --- .../plugins/kernel_wfp/kernel_wfp_compat.h | 14 + .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 291 +++++++++++++++++- 2 files changed, 304 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h index 76a6c85b8..92c90c617 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -62,6 +62,20 @@ enum { 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), +}; + DWORD WINAPI FwpmIPsecTunnelAdd0(HANDLE, UINT32, const FWPM_PROVIDER_CONTEXT0*, const FWPM_PROVIDER_CONTEXT0*, UINT32, const FWPM_FILTER_CONDITION0*, PSECURITY_DESCRIPTOR); diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 68d75c2ad..ac038e16b 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -71,6 +71,11 @@ struct private_kernel_wfp_ipsec_t { */ hashtable_t *routes; + /** + * Installed traps, as trap_t => trap_t + */ + hashtable_t *traps; + /** * Mutex for accessing entries */ @@ -1272,12 +1277,155 @@ static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry) } } +/** + * Installed trap entry + */ +typedef struct { + /** reqid this trap is installed for */ + u_int32_t reqid; + /** local traffic selector */ + traffic_selector_t *local; + /** remote traffic selector */ + traffic_selector_t *remote; + /** 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); + 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) + { + hydra->kernel_interface->acquire(hydra->kernel_interface, reqid, + src->clone(src), dst->clone(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: + case FWPM_NET_EVENT_TYPE_IPSEC_DOSP_DROP: + default: + break; + } + + DESTROY_IF(local); + DESTROY_IF(remote); } /** @@ -1298,6 +1446,133 @@ static bool register_events(private_kernel_wfp_ipsec_t *this) 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; + UINT64 weight = 0x000000000000ff00; + FWPM_FILTER0 filter = { + .displayData = { + .name = L"charon IPsec trap", + }, + .action = { + .type = FWP_ACTION_BLOCK, + }, + .weight = { + .type = FWP_UINT64, + .uint64 = &weight, + }, + }; + + switch (trap->local->get_type(trap->local)) + { + case TS_IPV4_ADDR_RANGE: + filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4; + break; + case TS_IPV6_ADDR_RANGE: + filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V6; + break; + } + + if (!ts2condition(trap->local, TRUE, &conds, &count) || + !ts2condition(trap->remote, FALSE, &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 FWP 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 FWP 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, + traffic_selector_t *local, traffic_selector_t *remote) +{ + trap_t *trap; + + INIT(trap, + .reqid = reqid, + .local = local->clone(local), + .remote = remote->clone(remote), + ); + + if (!install_trap(this, trap)) + { + destroy_trap(trap); + return FALSE; + } + 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, + traffic_selector_t *local, traffic_selector_t *remote) +{ + 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 && + local->equals(local, trap->local) && + remote->equals(remote, trap->remote)) + { + this->traps->remove_at(this->traps, enumerator); + found = trap; + break; + } + } + enumerator->destroy(enumerator); + this->mutex->unlock(this->mutex); + + if (found) + { + 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) { @@ -1755,7 +2030,11 @@ METHOD(kernel_ipsec_t, add_policy, status_t, case POLICY_PRIORITY_DEFAULT: break; case POLICY_PRIORITY_ROUTED: - /* TODO: install trap policy with low prio */ + if (add_trap(this, sa->reqid, src_ts, dst_ts)) + { + return SUCCESS; + } + return FAILED; case POLICY_PRIORITY_FALLBACK: default: return NOT_SUPPORTED; @@ -1809,6 +2088,14 @@ METHOD(kernel_ipsec_t, del_policy, status_t, 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, src_ts, dst_ts)) + { + return SUCCESS; + } + return NOT_FOUND; + } /* not required, as we delete the whole SA/SP set during del_sa() */ return SUCCESS; } @@ -1956,6 +2243,7 @@ METHOD(kernel_ipsec_t, destroy, void, 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); } @@ -2008,6 +2296,7 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() .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)) From 1ca2b1615ac633f2608c2a4e9107685ab083f42a Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 14:23:08 +0100 Subject: [PATCH 36/50] kernel-wfp: Print filter weight in "ipsecdump filters" --- src/libcharon/plugins/kernel_wfp/ipsecdump.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/ipsecdump.c b/src/libcharon/plugins/kernel_wfp/ipsecdump.c index d2aa26182..ec3c78ea3 100644 --- a/src/libcharon/plugins/kernel_wfp/ipsecdump.c +++ b/src/libcharon/plugins/kernel_wfp/ipsecdump.c @@ -627,6 +627,10 @@ static bool list_filters(HANDLE engine) 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) { From 1678f0a999bed5e486b56824381332a85c505033 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 16:55:43 +0100 Subject: [PATCH 37/50] kernel-wfp: Manually create a ProviderContext to attach individual filters This gives us more flexibility than using the intransparent FwpmIPsecTunnelAdd, and fixes the issues we have seen with trap policies. Forward filters are still missing, but required for site-to-site tunnels. --- src/libcharon/plugins/kernel_wfp/ipsecdump.c | 12 -- .../plugins/kernel_wfp/kernel_wfp_compat.c | 12 ++ .../plugins/kernel_wfp/kernel_wfp_compat.h | 10 ++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 118 ++++++++---------- 4 files changed, 73 insertions(+), 79 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/ipsecdump.c b/src/libcharon/plugins/kernel_wfp/ipsecdump.c index ec3c78ea3..55e64fe93 100644 --- a/src/libcharon/plugins/kernel_wfp/ipsecdump.c +++ b/src/libcharon/plugins/kernel_wfp/ipsecdump.c @@ -504,18 +504,6 @@ static char* layer2name(GUID *guid) return NULL; } -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 } }; diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c index 4296e147c..3660ac9d6 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -54,6 +54,18 @@ const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4 = { 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 } +}; /** * Load a function symbol from a loaded dll diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h index 92c90c617..9c9addbea 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -76,6 +76,16 @@ enum { 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), +}; + DWORD WINAPI FwpmIPsecTunnelAdd0(HANDLE, UINT32, const FWPM_PROVIDER_CONTEXT0*, const FWPM_PROVIDER_CONTEXT0*, UINT32, const FWPM_FILTER_CONDITION0*, PSECURITY_DESCRIPTOR); diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index ac038e16b..5c956ea69 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -174,10 +174,12 @@ typedef struct { ipsec_mode_t mode; /** UDP encapsulation */ bool encap; - /** WFP allocated LUID for inbound filter/tunnel policy ID */ + /** WFP allocated LUID for inbound filter ID */ u_int64_t policy_in; - /** WFP allocated LUID for outbound filter ID, unused for tunnel mode */ + /** WFP allocated LUID for outbound filter ID */ u_int64_t policy_out; + /** provider context, for tunnel mode only */ + u_int64_t provider; /** WFP allocated LUID for SA context */ u_int64_t sa_id; } entry_t; @@ -232,29 +234,6 @@ static u_int hash_route(route_t *route) static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, bool add); -/** - * Remove a transport or tunnel policy from kernel - */ -static void cleanup_policy(private_kernel_wfp_ipsec_t *this, bool transport, - u_int64_t policy) -{ - if (transport) - { - FwpmFilterDeleteById0(this->handle, policy); - } - else - { - FWPM_PROVIDER_CONTEXT0 *ctx; - - if (FwpmProviderContextGetById0(this->handle, policy, - &ctx) == ERROR_SUCCESS) - { - FwpmIPsecTunnelDeleteByKey0(this->handle, &ctx->providerContextKey); - FwpmFreeMemory0((void**)&ctx); - } - } -} - /** * Remove policies associated to an entry from kernel */ @@ -262,12 +241,12 @@ static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry) { if (entry->policy_in) { - cleanup_policy(this, entry->mode == MODE_TRANSPORT, entry->policy_in); + FwpmFilterDeleteById0(this->handle, entry->policy_in); entry->policy_in = 0; } if (entry->policy_out) { - cleanup_policy(this, entry->mode == MODE_TRANSPORT, entry->policy_out); + FwpmFilterDeleteById0(this->handle, entry->policy_out); entry->policy_out = 0; } if (entry->mode == MODE_TUNNEL) @@ -285,6 +264,10 @@ static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry) { 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); @@ -547,10 +530,10 @@ static void free_conditions(FWPM_FILTER_CONDITION0 *conds, int count) } /** - * Install transport mode SP to the kernel + * Install a single policy in to the kernel */ -static bool install_transport_sp(private_kernel_wfp_ipsec_t *this, - entry_t *entry, bool inbound) +static bool install_sp(private_kernel_wfp_ipsec_t *this, + entry_t *entry, GUID *context, bool inbound) { FWPM_FILTER_CONDITION0 *conds = NULL; int count = 0; @@ -560,17 +543,41 @@ static bool install_transport_sp(private_kernel_wfp_ipsec_t *this, DWORD res; FWPM_FILTER0 filter = { .displayData = { - .name = L"charon IPsec transport", + .name = L"charon IPsec policy", }, .action = { .type = FWP_ACTION_CALLOUT_TERMINATING, - .calloutKey = inbound ? FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 : - FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4, }, .layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V4 : FWPM_LAYER_OUTBOUND_TRANSPORT_V4, }; + if (context) + { + if (inbound) + { + filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4; + } + else + { + filter.action.calloutKey = FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4; + } + filter.flags |= FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT; + filter.providerKey = (GUID*)&this->provider.providerKey, + memcpy(&filter.providerContextKey, context, sizeof(GUID)); + } + else + { + if (inbound) + { + filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4; + } + else + { + filter.action.calloutKey = FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4; + } + } + enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) { @@ -609,7 +616,8 @@ static bool install_transport_sp(private_kernel_wfp_ipsec_t *this, free_conditions(conds, count); if (res != ERROR_SUCCESS) { - DBG1(DBG_KNL, "installing inbound FWP filter failed: 0x%08x", res); + DBG1(DBG_KNL, "installing %sbound FWP filter failed: 0x%08x", + inbound ? "in" : "out", res); return FALSE; } return TRUE; @@ -883,8 +891,8 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, } else { - traffic.tunnelPolicyId = entry->policy_in; - spi.inboundIpsecTraffic.tunnelPolicyId = entry->policy_in; + traffic.tunnelPolicyId = entry->provider; + spi.inboundIpsecTraffic.tunnelPolicyId = entry->provider; } if (!hosts2traffic(this, entry->local, entry->remote, &traffic)) @@ -963,8 +971,8 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, */ static bool install_transport(private_kernel_wfp_ipsec_t *this, entry_t *entry) { - if (install_transport_sp(this, entry, TRUE) && - install_transport_sp(this, entry, FALSE) && + if (install_sp(this, entry, NULL, TRUE) && + install_sp(this, entry, NULL, FALSE) && install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TRANSPORT)) { return TRUE; @@ -996,10 +1004,6 @@ static bool generate_guid(private_kernel_wfp_ipsec_t *this, GUID *guid) */ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) { - FWPM_FILTER_CONDITION0 *conds = NULL; - int count = 0; - enumerator_t *enumerator; - sp_entry_t *sp; DWORD res; IPSEC_AUTH_TRANSFORM0 transform = { @@ -1031,7 +1035,7 @@ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) .idleTimeoutSecondsFailOver = proposal.lifetime.lifetimeSeconds, }, }; - FWPM_PROVIDER_CONTEXT0 *ctx, qm = { + FWPM_PROVIDER_CONTEXT0 qm = { .displayData = { .name = L"charon tunnel provider context", }, @@ -1065,38 +1069,18 @@ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) return FALSE; } - enumerator = array_create_enumerator(entry->sps); - while (enumerator->enumerate(enumerator, &sp)) - { - if (!ts2condition(sp->src, TRUE, &conds, &count) || - !ts2condition(sp->dst, FALSE, &conds, &count)) - { - free_conditions(conds, count); - enumerator->destroy(enumerator); - return FALSE; - } - } - enumerator->destroy(enumerator); - - res = FwpmIPsecTunnelAdd0(this->handle, 0, NULL, &qm, count, conds, NULL); - free_conditions(conds, count); + res = FwpmProviderContextAdd0(this->handle, &qm, NULL, &entry->provider); if (res != ERROR_SUCCESS) { - DBG1(DBG_KNL, "installing FWP tunnel policy failed: 0x%08x", res); + DBG1(DBG_KNL, "adding provider context failed: 0x%08x", res); return FALSE; } - /* to get the tunnelPolicyId LUID we have to query the context */ - res = FwpmProviderContextGetByKey0(this->handle, &qm.providerContextKey, - &ctx); - if (res != ERROR_SUCCESS) + if (!install_sp(this, entry, &qm.providerContextKey, TRUE) || + !install_sp(this, entry, &qm.providerContextKey, FALSE)) { - DBG1(DBG_KNL, "getting FWP tunnel policy context failed: 0x%08x", res); return FALSE; } - entry->policy_in = ctx->providerContextId; - FwpmFreeMemory0((void**)&ctx); - return TRUE; } From 6de788704b0cb4b087719341f0900b1b1992cdb0 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 20 Dec 2013 14:42:10 +0100 Subject: [PATCH 38/50] kernel-wfp: Install tunnel and trap forward policies --- src/libcharon/plugins/kernel_wfp/ipsecdump.c | 26 -- .../plugins/kernel_wfp/kernel_wfp_compat.c | 24 ++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 365 ++++++++++++------ 3 files changed, 277 insertions(+), 138 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/ipsecdump.c b/src/libcharon/plugins/kernel_wfp/ipsecdump.c index 55e64fe93..7ca7df5a1 100644 --- a/src/libcharon/plugins/kernel_wfp/ipsecdump.c +++ b/src/libcharon/plugins/kernel_wfp/ipsecdump.c @@ -92,13 +92,6 @@ static char *guid2string(GUID *guid) return buf; } -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 } -}; - /** * Convert filter condition key GUID to some known strings */ @@ -431,15 +424,9 @@ const GUID FWPM_LAYER_OUTBOUND_IPPACKET_V6 = { 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 = { - 0xa82acc24, 0x4ee1, 0x4ee1, { 0xb4, 0x65, 0xfd, 0x1d, 0x25, 0xcb, 0x10, 0xa4 } -}; const GUID FWPM_LAYER_IPFORWARD_V4_DISCARD = { 0x9e9ea773, 0x2fae, 0x4210, { 0x8f, 0x17, 0x34, 0x12, 0x9e, 0xf3, 0x69, 0xeb } }; -const GUID FWPM_LAYER_IPFORWARD_V6 = { - 0x7b964818, 0x19c7, 0x493a, { 0xb7, 0x1f, 0x83, 0x2c, 0x36, 0x84, 0xd2, 0x8c } -}; const GUID FWPM_LAYER_IPFORWARD_V6_DISCARD = { 0x31524a5d, 0x1dfe, 0x472f, { 0xbb, 0x93, 0x51, 0x8e, 0xe9, 0x45, 0xd8, 0xa2 } }; @@ -504,19 +491,6 @@ static char* layer2name(GUID *guid) return NULL; } -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 } -}; - /** * Convert filter callout GUID to name */ diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c index 3660ac9d6..41f85ba5c 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c @@ -21,6 +21,12 @@ const GUID FWPM_CONDITION_IP_REMOTE_ADDRESS = { 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 } }; @@ -42,6 +48,12 @@ const GUID FWPM_LAYER_OUTBOUND_TRANSPORT_V4 = { 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 } }; @@ -66,6 +78,18 @@ const GUID FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4 = { 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 diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 5c956ea69..b6d7d7a58 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -178,6 +178,10 @@ typedef struct { 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; /** provider context, for tunnel mode only */ u_int64_t provider; /** WFP allocated LUID for SA context */ @@ -252,6 +256,16 @@ static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry) if (entry->mode == MODE_TUNNEL) { manage_routes(this, entry, FALSE); + if (entry->policy_fwd_in) + { + FwpmFilterDeleteById0(this->handle, entry->policy_fwd_in); + entry->policy_fwd_in = 0; + } + if (entry->policy_fwd_out) + { + FwpmFilterDeleteById0(this->handle, entry->policy_fwd_out); + entry->policy_fwd_out = 0; + } } } @@ -343,7 +357,7 @@ static void range2cond(FWPM_FILTER_CONDITION0 *cond, /** * (Re-)allocate filter conditions for given local or remote traffic selector */ -static bool ts2condition(traffic_selector_t *ts, bool local, +static bool ts2condition(traffic_selector_t *ts, const GUID *target, FWPM_FILTER_CONDITION0 *conds[], int *count) { FWPM_FILTER_CONDITION0 *cond; @@ -361,14 +375,7 @@ static bool ts2condition(traffic_selector_t *ts, bool local, to_port = ts->get_to_port(ts); cond = append_condition(conds, count); - if (local) - { - cond->fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS; - } - else - { - cond->fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS; - } + cond->fieldKey = *target; if (ts->is_host(ts, NULL)) { cond->matchType = FWP_MATCH_EQUAL; @@ -440,7 +447,7 @@ static bool ts2condition(traffic_selector_t *ts, bool local, } proto = ts->get_protocol(ts); - if (proto && local) + if (proto && target == &FWPM_CONDITION_IP_LOCAL_ADDRESS) { cond = append_condition(conds, count); cond->fieldKey = FWPM_CONDITION_IP_PROTOCOL; @@ -451,7 +458,7 @@ static bool ts2condition(traffic_selector_t *ts, bool local, if (proto == IPPROTO_ICMP) { - if (local) + if (target == &FWPM_CONDITION_IP_LOCAL_ADDRESS) { u_int8_t from_type, to_type, from_code, to_code; @@ -476,16 +483,18 @@ static bool ts2condition(traffic_selector_t *ts, bool local, } else if (from_port != 0 || to_port != 0xFFFF) { - cond = append_condition(conds, count); - if (local) + 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); } - else + 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); } - range2cond(cond, from_port, to_port); } return TRUE; } @@ -529,17 +538,73 @@ static void free_conditions(FWPM_FILTER_CONDITION0 *conds, int count) free(conds); } +/** + * Find the callout GUID for given parameters + */ +static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward, + GUID *layer, GUID *callout) +{ + struct { + bool tunnel; + bool v6; + bool inbound; + bool forward; + const GUID *layer; + const GUID *callout; + } map[] = { + { 0, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4 }, + { 0, 0, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V4, + &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 }, + { 0, 1, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V6, + &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6 }, + { 0, 1, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V6, + &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6 }, + { 1, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4 }, + { 1, 0, 0, 1, &FWPM_LAYER_IPFORWARD_V4, + &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4 }, + { 1, 0, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V4, + &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4 }, + { 1, 0, 1, 1, &FWPM_LAYER_IPFORWARD_V4, + &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4 }, + { 1, 1, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V6, + &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6 }, + { 1, 1, 0, 1, &FWPM_LAYER_IPFORWARD_V6, + &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6 }, + { 1, 1, 1, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V6, + &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6 }, + { 1, 1, 1, 1, &FWPM_LAYER_IPFORWARD_V6, + &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; + return TRUE; + } + } + return FALSE; +} + /** * Install a single policy in to the kernel */ -static bool install_sp(private_kernel_wfp_ipsec_t *this, - entry_t *entry, GUID *context, bool inbound) +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; - int count = 0; - enumerator_t *enumerator; traffic_selector_t *local, *remote; - sp_entry_t *sp; + const GUID *ltarget, *rtarget; + int count = 0; + bool v6; DWORD res; FWPM_FILTER0 filter = { .displayData = { @@ -548,81 +613,114 @@ static bool install_sp(private_kernel_wfp_ipsec_t *this, .action = { .type = FWP_ACTION_CALLOUT_TERMINATING, }, - .layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V4 : - FWPM_LAYER_OUTBOUND_TRANSPORT_V4, }; if (context) { - if (inbound) - { - filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4; - } - else - { - filter.action.calloutKey = FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4; - } filter.flags |= FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT; - filter.providerKey = (GUID*)&this->provider.providerKey, - memcpy(&filter.providerContextKey, context, sizeof(GUID)); + 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.action.calloutKey)) + { + return FALSE; + } + + if (inbound && fwd) + { + local = sp->dst; + remote = sp->src; } else { - if (inbound) - { - filter.action.calloutKey = FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4; - } - else - { - filter.action.calloutKey = FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4; - } + local = sp->src; + remote = sp->dst; } - - enumerator = array_create_enumerator(entry->sps); - while (enumerator->enumerate(enumerator, &sp)) + if (fwd) { - if (inbound) - { - local = sp->dst; - remote = sp->src; - } - else - { - local = sp->src; - remote = sp->dst; - } - - if (!ts2condition(local, TRUE, &conds, &count) || - !ts2condition(remote, FALSE, &conds, &count)) - { - free_conditions(conds, count); - enumerator->destroy(enumerator); - return FALSE; - } + 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; } - enumerator->destroy(enumerator); filter.numFilterConditions = count; filter.filterCondition = conds; - if (inbound) - { - res = FwpmFilterAdd0(this->handle, &filter, NULL, &entry->policy_in); - } - else - { - res = FwpmFilterAdd0(this->handle, &filter, NULL, &entry->policy_out); - } + res = FwpmFilterAdd0(this->handle, &filter, NULL, filter_id); free_conditions(conds, count); if (res != ERROR_SUCCESS) { - DBG1(DBG_KNL, "installing %sbound FWP filter failed: 0x%08x", - inbound ? "in" : "out", res); + 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, &entry->policy_in)) + { + enumerator->destroy(enumerator); + return FALSE; + } + /* outbound policy */ + if (!install_sp(this, sp, context, FALSE, FALSE, &entry->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, &entry->policy_fwd_in)) + { + enumerator->destroy(enumerator); + return FALSE; + } + /* outbound forward policy, to encapsulate */ + if (!install_sp(this, sp, context, + FALSE, TRUE, &entry->policy_fwd_out)) + { + enumerator->destroy(enumerator); + return FALSE; + } + } + } + } + enumerator->destroy(enumerator); + + return TRUE; +} + /** * Convert a chunk_t to a WFP FWP_BYTE_BLOB */ @@ -971,8 +1069,7 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, */ static bool install_transport(private_kernel_wfp_ipsec_t *this, entry_t *entry) { - if (install_sp(this, entry, NULL, TRUE) && - install_sp(this, entry, NULL, FALSE) && + if (install_sps(this, entry, NULL) && install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TRANSPORT)) { return TRUE; @@ -1000,9 +1097,10 @@ static bool generate_guid(private_kernel_wfp_ipsec_t *this, GUID *guid) } /** - * Install tunnel mode SPs to the kernel + * Register a dummy tunnel provider to associate tunnel filters to */ -static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) +static bool add_tunnel_provider(private_kernel_wfp_ipsec_t *this, + entry_t *entry, GUID *guid, UINT64 *luid) { DWORD res; @@ -1037,7 +1135,7 @@ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) }; FWPM_PROVIDER_CONTEXT0 qm = { .displayData = { - .name = L"charon tunnel provider context", + .name = L"charon tunnel provider", }, .providerKey = (GUID*)&this->provider.providerKey, .type = FWPM_IPSEC_IKE_QM_TUNNEL_CONTEXT, @@ -1055,10 +1153,8 @@ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) break; case AF_INET6: policy.tunnelEndpoints.ipVersion = FWP_IP_VERSION_V6; - memcpy(&policy.tunnelEndpoints.localV6Address, - entry->local->get_address(entry->local).ptr, 16); - memcpy(&policy.tunnelEndpoints.remoteV6Address, - entry->remote->get_address(entry->remote).ptr, 16); + host2address6(entry->local, &policy.tunnelEndpoints.localV6Address); + host2address6(entry->remote, &policy.tunnelEndpoints.remoteV6Address); break; default: return FALSE; @@ -1069,15 +1165,28 @@ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) return FALSE; } - res = FwpmProviderContextAdd0(this->handle, &qm, NULL, &entry->provider); + 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; +} - if (!install_sp(this, entry, &qm.providerContextKey, TRUE) || - !install_sp(this, entry, &qm.providerContextKey, FALSE)) +/** + * 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; } @@ -1267,10 +1376,12 @@ static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry) typedef struct { /** reqid this trap is installed for */ u_int32_t reqid; - /** local traffic selector */ - traffic_selector_t *local; - /** remote traffic selector */ - traffic_selector_t *remote; + /** is this a forward policy trap for tunnel mode? */ + bool fwd; + /** 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; @@ -1280,8 +1391,8 @@ typedef struct { */ 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); } @@ -1438,6 +1549,7 @@ 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 = { @@ -1452,18 +1564,35 @@ static bool install_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap) }, }; - switch (trap->local->get_type(trap->local)) + if (trap->fwd) { - case TS_IPV4_ADDR_RANGE: + 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; - break; - case TS_IPV6_ADDR_RANGE: + } + else + { filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V6; - break; + } + starget = &FWPM_CONDITION_IP_LOCAL_ADDRESS; + dtarget = &FWPM_CONDITION_IP_REMOTE_ADDRESS; } - if (!ts2condition(trap->local, TRUE, &conds, &count) || - !ts2condition(trap->remote, FALSE, &conds, &count)) + if (!ts2condition(trap->src, starget, &conds, &count) || + !ts2condition(trap->dst, dtarget, &conds, &count)) { free_conditions(conds, count); return FALSE; @@ -1476,7 +1605,7 @@ static bool install_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap) free_conditions(conds, count); if (res != ERROR_SUCCESS) { - DBG1(DBG_KNL, "installing FWP trap filter failed: 0x%08x", res); + DBG1(DBG_KNL, "installing WFP trap filter failed: 0x%08x", res); return FALSE; } return TRUE; @@ -1492,7 +1621,7 @@ static bool uninstall_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap) res = FwpmFilterDeleteById0(this->handle, trap->filter_id); if (res != ERROR_SUCCESS) { - DBG1(DBG_KNL, "uninstalling FWP trap filter failed: 0x%08x", res); + DBG1(DBG_KNL, "uninstalling WFP trap filter failed: 0x%08x", res); return FALSE; } return TRUE; @@ -1501,15 +1630,17 @@ static bool uninstall_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap) /** * Create and install a new trap entry */ -static bool add_trap(private_kernel_wfp_ipsec_t *this, u_int32_t reqid, - traffic_selector_t *local, traffic_selector_t *remote) +static bool add_trap(private_kernel_wfp_ipsec_t *this, + u_int32_t reqid, bool fwd, + traffic_selector_t *src, traffic_selector_t *dst) { trap_t *trap; INIT(trap, .reqid = reqid, - .local = local->clone(local), - .remote = remote->clone(remote), + .fwd = fwd, + .src = src->clone(src), + .dst = dst->clone(dst), ); if (!install_trap(this, trap)) @@ -1526,8 +1657,9 @@ static bool add_trap(private_kernel_wfp_ipsec_t *this, u_int32_t reqid, /** * Uninstall and remove a new trap entry */ -static bool remove_trap(private_kernel_wfp_ipsec_t *this, u_int32_t reqid, - traffic_selector_t *local, traffic_selector_t *remote) +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; @@ -1537,8 +1669,9 @@ static bool remove_trap(private_kernel_wfp_ipsec_t *this, u_int32_t reqid, while (enumerator->enumerate(enumerator, NULL, &trap)) { if (reqid == trap->reqid && - local->equals(local, trap->local) && - remote->equals(remote, trap->remote)) + fwd == trap->fwd && + src->equals(src, trap->src) && + dst->equals(dst, trap->dst)) { this->traps->remove_at(this->traps, enumerator); found = trap; @@ -2014,11 +2147,18 @@ METHOD(kernel_ipsec_t, add_policy, status_t, case POLICY_PRIORITY_DEFAULT: break; case POLICY_PRIORITY_ROUTED: - if (add_trap(this, sa->reqid, src_ts, dst_ts)) + if (!add_trap(this, sa->reqid, FALSE, src_ts, dst_ts)) { - return SUCCESS; + return FAILED; } - return FAILED; + if (sa->mode == MODE_TUNNEL) + { + if (!add_trap(this, sa->reqid, TRUE, src_ts, dst_ts)) + { + return FAILED; + } + } + return SUCCESS; case POLICY_PRIORITY_FALLBACK: default: return NOT_SUPPORTED; @@ -2074,8 +2214,9 @@ METHOD(kernel_ipsec_t, del_policy, status_t, { if (direction == POLICY_OUT && priority == POLICY_PRIORITY_ROUTED) { - if (remove_trap(this, reqid, src_ts, dst_ts)) + if (remove_trap(this, reqid, FALSE, src_ts, dst_ts)) { + remove_trap(this, reqid, TRUE, src_ts, dst_ts); return SUCCESS; } return NOT_FOUND; @@ -2146,7 +2287,7 @@ static bool add_bypass(private_kernel_wfp_ipsec_t *this, free_conditions(conds, count); if (res != ERROR_SUCCESS) { - DBG1(DBG_KNL, "installing FWP trap filter failed: 0x%08x", res); + DBG1(DBG_KNL, "installing WFP bypass filter failed: 0x%08x", res); return FALSE; } return TRUE; From a4f3b363dabd2ba435ba2cded4cbb0ecce0304e1 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 23 Dec 2013 18:45:13 +0100 Subject: [PATCH 39/50] kernel-wfp: Set flag to get UDP encapsulation with tunnel mode working Having this flag set fixes connections initiated by the Windows host, but unfortunately does not yet fix incoming connections. Connection state issue? We still see 0xc00000e2 error events, translating to INTERNAL_ERROR. --- .../plugins/kernel_wfp/kernel_wfp_compat.h | 21 +++++++++++++++++++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 1 + 2 files changed, 22 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h index 9c9addbea..a2aa1d80b 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -86,6 +86,27 @@ enum { 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), +}; + DWORD WINAPI FwpmIPsecTunnelAdd0(HANDLE, UINT32, const FWPM_PROVIDER_CONTEXT0*, const FWPM_PROVIDER_CONTEXT0*, UINT32, const FWPM_FILTER_CONDITION0*, PSECURITY_DESCRIPTOR); diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index b6d7d7a58..d471a0010 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -913,6 +913,7 @@ static bool install_sa(private_kernel_wfp_ipsec_t *this, entry_t *entry, } else { + bundle.flags |= IPSEC_SA_BUNDLE_FLAG_ASSUME_UDP_CONTEXT_OUTBOUND; res = IPsecSaContextAddOutbound0(this->handle, entry->sa_id, &bundle); } if (res != ERROR_SUCCESS) From c7d30c2ad16d4855ade151dc96a9404a56a70c4e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 24 Dec 2013 10:01:35 +0100 Subject: [PATCH 40/50] kernel-wfp: Show a warning for packets the kernel drops in its IPsec layers --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index d471a0010..1b73b59da 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1515,6 +1515,12 @@ static void event_callback(private_kernel_wfp_ipsec_t *this, 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; From 5e6e214ab41000b3eb89ab08b1bc09364ea39408 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 24 Dec 2013 10:40:09 +0100 Subject: [PATCH 41/50] kernel-iph: Implicitly enable IP forwarding when installing routes --- .../plugins/kernel_iph/kernel_iph_net.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c index 71c595ba7..68b753792 100644 --- a/src/libcharon/plugins/kernel_iph/kernel_iph_net.c +++ b/src/libcharon/plugins/kernel_iph/kernel_iph_net.c @@ -50,6 +50,11 @@ struct private_kernel_iph_net_t { */ HANDLE changes; + /** + * EnableRouter() OVERLAPPED + */ + OVERLAPPED router; + /** * Mutex to access interface list */ @@ -677,6 +682,23 @@ static status_t manage_route(private_kernel_iph_net_t *this, bool add, 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; } @@ -701,6 +723,7 @@ METHOD(kernel_net_t, destroy, void, { CancelMibChangeNotify2(this->changes); } + CloseHandle(this->router.hEvent); this->mutex->destroy(this->mutex); this->ifaces->destroy_function(this->ifaces, (void*)iface_destroy); free(this); @@ -728,6 +751,9 @@ kernel_iph_net_t *kernel_iph_net_create() .destroy = _destroy, }, }, + .router = { + .hEvent = CreateEvent(NULL, FALSE, FALSE, NULL), + }, .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .ifaces = linked_list_create(), ); From 4b09bd6c29e5619375c4d023484d84f4f7d6bc8b Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 24 Dec 2013 11:27:59 +0100 Subject: [PATCH 42/50] child-sa: Pass the number of total policies tied to an SA to the kernel This will be useful if the kernel backend has to know how many policies follow an SA install, for example if it must install all policies concurrently. --- src/libcharon/sa/child_sa.c | 8 ++++++++ src/libstrongswan/ipsec/ipsec_types.h | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/libcharon/sa/child_sa.c b/src/libcharon/sa/child_sa.c index bbf6259c2..a7d7b7305 100644 --- a/src/libcharon/sa/child_sa.c +++ b/src/libcharon/sa/child_sa.c @@ -847,6 +847,14 @@ 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)) 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 */ From 4b5128034405369142d50dab28efff5952c480bb Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 24 Dec 2013 11:34:50 +0100 Subject: [PATCH 43/50] kernel-wfp: Support multiple traffic selectors on tunnel mode SAs --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 116 ++++++++++++------ 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 1b73b59da..5790f8389 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -142,6 +142,16 @@ typedef struct { 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; /** @@ -174,14 +184,6 @@ typedef struct { ipsec_mode_t mode; /** UDP encapsulation */ bool encap; - /** 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; /** provider context, for tunnel mode only */ u_int64_t provider; /** WFP allocated LUID for SA context */ @@ -243,30 +245,39 @@ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, */ static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry) { - if (entry->policy_in) - { - FwpmFilterDeleteById0(this->handle, entry->policy_in); - entry->policy_in = 0; - } - if (entry->policy_out) - { - FwpmFilterDeleteById0(this->handle, entry->policy_out); - entry->policy_out = 0; - } + enumerator_t *enumerator; + sp_entry_t *sp; + if (entry->mode == MODE_TUNNEL) { manage_routes(this, entry, FALSE); - if (entry->policy_fwd_in) + } + + enumerator = array_create_enumerator(entry->sps); + while (enumerator->enumerate(enumerator, &sp)) + { + if (sp->policy_in) { - FwpmFilterDeleteById0(this->handle, entry->policy_fwd_in); - entry->policy_fwd_in = 0; + FwpmFilterDeleteById0(this->handle, sp->policy_in); + sp->policy_in = 0; } - if (entry->policy_fwd_out) + if (sp->policy_out) { - FwpmFilterDeleteById0(this->handle, entry->policy_fwd_out); - entry->policy_fwd_out = 0; + 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); } /** @@ -683,13 +694,13 @@ static bool install_sps(private_kernel_wfp_ipsec_t *this, while (enumerator->enumerate(enumerator, &sp)) { /* inbound policy */ - if (!install_sp(this, sp, context, TRUE, FALSE, &entry->policy_in)) + 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, &entry->policy_out)) + if (!install_sp(this, sp, context, FALSE, FALSE, &sp->policy_out)) { enumerator->destroy(enumerator); return FALSE; @@ -701,14 +712,14 @@ static bool install_sps(private_kernel_wfp_ipsec_t *this, { /* inbound forward policy, from decapsulation */ if (!install_sp(this, sp, context, - TRUE, TRUE, &entry->policy_fwd_in)) + TRUE, TRUE, &sp->policy_fwd_in)) { enumerator->destroy(enumerator); return FALSE; } /* outbound forward policy, to encapsulate */ if (!install_sp(this, sp, context, - FALSE, TRUE, &entry->policy_fwd_out)) + FALSE, TRUE, &sp->policy_fwd_out)) { enumerator->destroy(enumerator); return FALSE; @@ -981,12 +992,24 @@ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, .trafficType = type, }, }; + enumerator_t *enumerator; + sp_entry_t *sp; DWORD res; if (type == IPSEC_TRAFFIC_TYPE_TRANSPORT) { - traffic.ipsecFilterId = entry->policy_out; - spi.inboundIpsecTraffic.ipsecFilterId = entry->policy_in; + 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 { @@ -1302,11 +1325,19 @@ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) { + if (add && sp->route) + { + continue; + } + if (!add && !sp->route) + { + continue; + } if (!sp->dst->to_subnet(sp->dst, &dst, &mask)) { continue; } - if (hydra->kernel_interface->get_address_by_ts( hydra->kernel_interface, + if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, sp->src, &src, NULL) != SUCCESS) { dst->destroy(dst); @@ -1316,7 +1347,11 @@ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, entry->remote, entry->local); if (add) { - if (!install_route(this, dst, mask, src, gtw)) + if (install_route(this, dst, mask, src, gtw)) + { + sp->route = TRUE; + } + else { DBG1(DBG_KNL, "installing route for policy %R === %R failed", sp->src, sp->dst); @@ -1324,7 +1359,11 @@ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, } else { - if (!uninstall_route(this, dst, mask, src, gtw)) + if (uninstall_route(this, dst, mask, src, gtw)) + { + sp->route = FALSE; + } + else { DBG1(DBG_KNL, "uninstalling route for policy %R === %R failed", sp->src, sp->dst); @@ -2175,16 +2214,19 @@ METHOD(kernel_ipsec_t, add_policy, status_t, entry = this->osas->get(this->osas, &key); if (entry) { - if (array_count(entry->sps) == 0) + 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 (!install(this, entry)) + if (array_count(entry->sps) == sa->policy_count) { - status = FAILED; + if (!install(this, entry)) + { + status = FAILED; + } } } else @@ -2192,6 +2234,8 @@ METHOD(kernel_ipsec_t, add_policy, status_t, /* 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; } } From be32be01a87b34cab42e04a315c5654f1a1303e7 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 2 Jan 2014 17:31:30 +0100 Subject: [PATCH 44/50] kernel-wfp: Declare GUIDs and auth/cipher configs missing in some MinGW builds --- .../plugins/kernel_wfp/kernel_wfp_compat.h | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h index a2aa1d80b..8b1d5d36f 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -107,6 +107,95 @@ enum { 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_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); From 4a8ba369b62fac3a0800520599fef8cf2753b443 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 8 Apr 2014 15:58:38 +0200 Subject: [PATCH 45/50] kernel-wfp: Install tunnel mode policies to appropriate sub-layers While it is unclear if this has any effect at all, we prefer specific sublayers to install policies as suggested. --- .../plugins/kernel_wfp/kernel_wfp_compat.h | 2 ++ .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 26 ++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h index 8b1d5d36f..50a89a007 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.h @@ -127,6 +127,8 @@ 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; diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 5790f8389..7a8b1487c 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -553,7 +553,7 @@ static void free_conditions(FWPM_FILTER_CONDITION0 *conds, int count) * Find the callout GUID for given parameters */ static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward, - GUID *layer, GUID *callout) + GUID *layer, GUID *sublayer, GUID *callout) { struct { bool tunnel; @@ -561,31 +561,40 @@ static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward, bool inbound; bool forward; const GUID *layer; + const GUID *sublayer; const GUID *callout; } map[] = { - { 0, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, + { 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, + { 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, + { 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, + { 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; @@ -599,6 +608,10 @@ static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward, { *callout = *map[i].callout; *layer = *map[i].layer; + if (map[i].sublayer) + { + *sublayer = *map[i].sublayer; + } return TRUE; } } @@ -635,7 +648,8 @@ static bool install_sp(private_kernel_wfp_ipsec_t *this, sp_entry_t *sp, v6 = sp->src->get_type(sp->src) == TS_IPV6_ADDR_RANGE; if (!find_callout(context != NULL, v6, inbound, fwd, - &filter.layerKey, &filter.action.calloutKey)) + &filter.layerKey, &filter.subLayerKey, + &filter.action.calloutKey)) { return FALSE; } From e36d1d4124df387f04c7fc0b85e447a6b9b10e05 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 9 Apr 2014 10:41:32 +0200 Subject: [PATCH 46/50] kernel-wfp: Refactor route management to separate function --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 86 ++++++++++--------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 7a8b1487c..eaf21fa6a 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1325,6 +1325,50 @@ static bool install_route(private_kernel_wfp_ipsec_t *this, 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 */ @@ -1332,9 +1376,7 @@ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, bool add) { enumerator_t *enumerator; - host_t *src, *dst, *gtw; sp_entry_t *sp; - u_int8_t mask; enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) @@ -1347,45 +1389,11 @@ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, { continue; } - if (!sp->dst->to_subnet(sp->dst, &dst, &mask)) + if (manage_route(this, entry->local, entry->remote, + sp->src, sp->dst, add)) { - continue; + sp->route = add; } - if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, - sp->src, &src, NULL) != SUCCESS) - { - dst->destroy(dst); - continue; - } - gtw = hydra->kernel_interface->get_nexthop(hydra->kernel_interface, - entry->remote, entry->local); - if (add) - { - if (install_route(this, dst, mask, src, gtw)) - { - sp->route = TRUE; - } - else - { - DBG1(DBG_KNL, "installing route for policy %R === %R failed", - sp->src, sp->dst); - } - } - else - { - if (uninstall_route(this, dst, mask, src, gtw)) - { - sp->route = FALSE; - } - else - { - DBG1(DBG_KNL, "uninstalling route for policy %R === %R failed", - sp->src, sp->dst); - } - } - dst->destroy(dst); - src->destroy(src); - DESTROY_IF(gtw); } enumerator->destroy(enumerator); From 78bde29a7c3cac06ef23fffc1bbd4f6caffef176 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 9 Apr 2014 10:42:15 +0200 Subject: [PATCH 47/50] kernel-wfp: Install routes for trap policies --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index eaf21fa6a..4f1fc7590 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1440,6 +1440,12 @@ typedef struct { 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 */ @@ -1453,6 +1459,8 @@ typedef struct { */ 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); @@ -1699,7 +1707,7 @@ static bool uninstall_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap) * Create and install a new trap entry */ static bool add_trap(private_kernel_wfp_ipsec_t *this, - u_int32_t reqid, bool fwd, + u_int32_t reqid, bool fwd, host_t *local, host_t *remote, traffic_selector_t *src, traffic_selector_t *dst) { trap_t *trap; @@ -1709,6 +1717,8 @@ static bool add_trap(private_kernel_wfp_ipsec_t *this, .fwd = fwd, .src = src->clone(src), .dst = dst->clone(dst), + .local = local->clone(local), + .remote = remote->clone(remote), ); if (!install_trap(this, trap)) @@ -1716,6 +1726,9 @@ static bool add_trap(private_kernel_wfp_ipsec_t *this, 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); @@ -1751,6 +1764,11 @@ static bool remove_trap(private_kernel_wfp_ipsec_t *this, 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; @@ -2215,13 +2233,13 @@ METHOD(kernel_ipsec_t, add_policy, status_t, case POLICY_PRIORITY_DEFAULT: break; case POLICY_PRIORITY_ROUTED: - if (!add_trap(this, sa->reqid, FALSE, src_ts, dst_ts)) + 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_ts, dst_ts)) + if (!add_trap(this, sa->reqid, TRUE, src, dst, src_ts, dst_ts)) { return FAILED; } From 75afbeee218f16a96cb00b1e776620b7e313841a Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 9 Apr 2014 10:42:36 +0200 Subject: [PATCH 48/50] kernel-wfp: Clone acquire traffic selectors only if they exist --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c index 4f1fc7590..5b44f85b5 100644 --- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c +++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c @@ -1503,8 +1503,10 @@ static void acquire(private_kernel_wfp_ipsec_t *this, UINT64 filter_id, if (reqid) { + src = src ? src->clone(src) : NULL; + dst = dst ? dst->clone(dst) : NULL; hydra->kernel_interface->acquire(hydra->kernel_interface, reqid, - src->clone(src), dst->clone(dst)); + src, dst); } } From 4b9848a2cc712a3e89106597083c97d35577e32e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 9 May 2014 15:11:58 +0200 Subject: [PATCH 49/50] kernel-wfp: Include Windows header patch for MinGW 4.8.1 --- src/libcharon/plugins/kernel_wfp/Makefile.am | 3 +++ .../plugins/kernel_wfp/mingw-w64-4.8.1.diff | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/libcharon/plugins/kernel_wfp/mingw-w64-4.8.1.diff diff --git a/src/libcharon/plugins/kernel_wfp/Makefile.am b/src/libcharon/plugins/kernel_wfp/Makefile.am index 5ff93da4b..85e5089a3 100644 --- a/src/libcharon/plugins/kernel_wfp/Makefile.am +++ b/src/libcharon/plugins/kernel_wfp/Makefile.am @@ -28,3 +28,6 @@ ipsecdump_SOURCES = \ 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/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; From 4732e29a1d5ed9bd136a804e180cb45d23d24f86 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 2 Apr 2014 11:35:19 +0200 Subject: [PATCH 50/50] travis: Build "all" tests without Windows kernel backends --- scripts/test.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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"