From 8d91eee3fcc3c170ff97801ac758d430db8ae8b3 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 31 Oct 2013 14:58:43 +0100 Subject: kernel-wfp: Add a stub for a Windows Filtering Platform based IPsec backend --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 169 +++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; +}; -- cgit v1.2.3 From 96ab7a8022341997cdf74459e2864c56be31af55 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 1 Nov 2013 10:54:38 +0100 Subject: 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(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; -- cgit v1.2.3 From ebb9362d85a5822c039b29673092267186a79cb9 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 7 Nov 2013 15:50:46 +0100 Subject: kernel-wfp: Open and close a WFP engine --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 34 +++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; -}; +} -- cgit v1.2.3 From b1ba0a666c4a46740bae63fd3cc005b7abfbe86d Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 15 Nov 2013 12:09:46 +0100 Subject: 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/kernel_wfp_ipsec.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; -- cgit v1.2.3 From 149fc48e030981aa41ab5f282691a865449504e7 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 18 Nov 2013 12:57:36 +0100 Subject: kernel-wfp: Preliminary support for transport mode connections --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 692 ++++++++++++++++++++- 1 file changed, 689 insertions(+), 3 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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), -- cgit v1.2.3 From f5ddda7f5769d8ab46ab5959bb0c9847982d7054 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 5 Dec 2013 14:29:05 +0100 Subject: kernel-wfp: Register a WFP provider to manage IPsec tunnels --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From 4a8b85684ffa3365dd0bd698aa2fb9f5a8130051 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 13:16:49 +0100 Subject: kernel-wfp: Add support for tunnel mode connections --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 230 ++++++++++++++++++--- 1 file changed, 205 insertions(+), 25 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; @@ -182,22 +182,55 @@ static entry_t *entry_create(u_int32_t reqid, host_t *local, host_t *remote, } /** - * Destroy a SA/SP entry set + * Remove a transport or tunnel policy from kernel */ -static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry) +static void cleanup_policy(private_kernel_wfp_ipsec_t *this, bool transport, + u_int64_t policy) { - if (entry->sa_id) + if (transport) { - IPsecSaContextDeleteById0(this->handle, entry->sa_id); + 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) { - FwpmFilterDeleteById0(this->handle, entry->policy_in); + cleanup_policy(this, entry->mode == MODE_TRANSPORT, entry->policy_in); + entry->policy_in = 0; } if (entry->policy_out) { - FwpmFilterDeleteById0(this->handle, entry->policy_out); + cleanup_policy(this, entry->mode == MODE_TRANSPORT, entry->policy_out); + entry->policy_out = 0; + } +} + +/** + * Destroy a SA/SP entry set + */ +static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry) +{ + if (entry->sa_id) + { + IPsecSaContextDeleteById0(this->handle, entry->sa_id); } + 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; -- cgit v1.2.3 From f351d9ef7d70ca5d62a768fef0ce14a2d934267e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 10 Dec 2013 18:15:41 +0100 Subject: 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 | 404 ++++++++------------- 1 file changed, 149 insertions(+), 255 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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->lock(this->mutex); + this->osas->put(this->osas, &entry->osa, entry); + this->mutex->unlock(this->mutex); } - this->mutex->unlock(this->mutex); - return status; + 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->isas->remove(this->isas, &key); + this->mutex->unlock(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); - } - } + /* 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); - return status; + 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, -- cgit v1.2.3 From b50d486e78a387bc06f7f60d5e74dd08d48ea9a7 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 14:36:21 +0100 Subject: 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(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From b3f90915f9f76e3c756b377e4e78144faae0063c Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 14:42:56 +0100 Subject: kernel-wfp: Enforce hard lifetimes of SAs --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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), -- cgit v1.2.3 From af098b500812db1384dd580d693f7a624679ecd8 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 15:14:55 +0100 Subject: kernel-wfp: Triggering expire events for SAs to rekey/delete --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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); -- cgit v1.2.3 From cd88f818fa48da9797910653c97bdb4ffc9fd0c5 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 15:38:28 +0100 Subject: 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(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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), -- cgit v1.2.3 From b93492980489fa4ef0664c0ac4669592537ce1c2 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 11 Dec 2013 16:02:11 +0100 Subject: 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. --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From b714746ef051ccd4ea899d1bf0fe167485312183 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 15:33:42 +0100 Subject: kernel-wfp: Install appropriate routes for tunnel mode policies --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 209 ++++++++++++++++++++- 1 file changed, 208 insertions(+), 1 deletion(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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, -- cgit v1.2.3 From bbe42a1fa545af0131fdfdce6040a95b562d7c09 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 15:34:13 +0100 Subject: 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(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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) -- cgit v1.2.3 From 9b5c95648f2b1a94bf0a9e2b1cf9efe819958dfd Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 17:13:39 +0100 Subject: kernel-wfp: Refactor SA context construction, and use IPsecSaContextCreate1() --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 51 ++++++++++++++-------- 1 file changed, 32 insertions(+), 19 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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); -- cgit v1.2.3 From 1987b709895dbefb7e8c3298b0c5808731f57b93 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 16 Dec 2013 12:13:39 +0100 Subject: kernel-wfp: Configure ports for SAs using UDP encapsulation --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From 5a5b9925f87136fe0ca1c39448205018e69b6248 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 13 Dec 2013 17:14:26 +0100 Subject: kernel-wfp: Implement update_sa() --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 98 +++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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, -- cgit v1.2.3 From 6aaa432741c6675493c7a07bc5e9c5ea69eaf21e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 18 Dec 2013 11:56:36 +0100 Subject: kernel-wfp: Add some missing IPv6 GUIDs, fix IPv6 host conversion --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; -- cgit v1.2.3 From 28683140282493fb6b9dbf825961154d0ff02504 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 09:48:43 +0100 Subject: kernel-wfp: Register for WFP Net events --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 41 ++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From f206e069f132488508ada60daff2ef1cdd3b2afc Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 14:13:06 +0100 Subject: kernel-wfp: Implement bypass_socket() using dedicated filter rules --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 119 ++++++++++++++++++++- 1 file changed, 117 insertions(+), 2 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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), -- cgit v1.2.3 From c6f189e4489c07c5bbc18fca677dc4d7812b4e23 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 14:22:00 +0100 Subject: kernel-wfp: Add support for trap policies and acquires --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 291 ++++++++++++++++++++- 1 file changed, 290 insertions(+), 1 deletion(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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)) -- cgit v1.2.3 From 1678f0a999bed5e486b56824381332a85c505033 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Thu, 19 Dec 2013 16:55:43 +0100 Subject: 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. --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 118 +++++++++------------ 1 file changed, 51 insertions(+), 67 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From 6de788704b0cb4b087719341f0900b1b1992cdb0 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Fri, 20 Dec 2013 14:42:10 +0100 Subject: kernel-wfp: Install tunnel and trap forward policies --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 361 ++++++++++++++------- 1 file changed, 251 insertions(+), 110 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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,78 +613,111 @@ 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; + } + if (fwd) + { + ltarget = &FWPM_CONDITION_IP_SOURCE_ADDRESS; + rtarget = &FWPM_CONDITION_IP_DESTINATION_ADDRESS; + } + else + { + ltarget = &FWPM_CONDITION_IP_LOCAL_ADDRESS; + rtarget = &FWPM_CONDITION_IP_REMOTE_ADDRESS; } + if (!ts2condition(local, ltarget, &conds, &count) || + !ts2condition(remote, rtarget, &conds, &count)) + { + free_conditions(conds, count); + return FALSE; + } + + filter.numFilterConditions = count; + filter.filterCondition = conds; + + res = FwpmFilterAdd0(this->handle, &filter, NULL, filter_id); + free_conditions(conds, count); + if (res != ERROR_SUCCESS) + { + DBG1(DBG_KNL, "installing %s%sbound WFP filter failed: 0x%08x", + fwd ? "forward " : "", inbound ? "in" : "out", res); + return FALSE; + } + return TRUE; +} + +/** + * Install a set of policies in to the kernel + */ +static bool install_sps(private_kernel_wfp_ipsec_t *this, + entry_t *entry, GUID *context) +{ + enumerator_t *enumerator; + sp_entry_t *sp; enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) { - if (inbound) + /* inbound policy */ + if (!install_sp(this, sp, context, TRUE, FALSE, &entry->policy_in)) { - local = sp->dst; - remote = sp->src; - } - else - { - local = sp->src; - remote = sp->dst; + enumerator->destroy(enumerator); + return FALSE; } - - if (!ts2condition(local, TRUE, &conds, &count) || - !ts2condition(remote, FALSE, &conds, &count)) + /* outbound policy */ + if (!install_sp(this, sp, context, FALSE, FALSE, &entry->policy_out)) { - free_conditions(conds, count); 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); - 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 %sbound FWP filter failed: 0x%08x", - inbound ? "in" : "out", res); - return FALSE; - } return TRUE; } @@ -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 FAILED; + } + if (sa->mode == MODE_TUNNEL) { - return SUCCESS; + if (!add_trap(this, sa->reqid, TRUE, src_ts, dst_ts)) + { + return FAILED; + } } - 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; -- cgit v1.2.3 From a4f3b363dabd2ba435ba2cded4cbb0ecce0304e1 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Mon, 23 Dec 2013 18:45:13 +0100 Subject: 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. --- src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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) -- cgit v1.2.3 From c7d30c2ad16d4855ade151dc96a9404a56a70c4e Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 24 Dec 2013 10:01:35 +0100 Subject: 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(+) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; -- cgit v1.2.3 From 4b5128034405369142d50dab28efff5952c480bb Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 24 Dec 2013 11:34:50 +0100 Subject: 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(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } } -- cgit v1.2.3 From 4a8ba369b62fac3a0800520599fef8cf2753b443 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Tue, 8 Apr 2014 15:58:38 +0200 Subject: 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_ipsec.c | 26 +++++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From e36d1d4124df387f04c7fc0b85e447a6b9b10e05 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 9 Apr 2014 10:41:32 +0200 Subject: kernel-wfp: Refactor route management to separate function --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 86 ++++++++++++---------- 1 file changed, 47 insertions(+), 39 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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)) - { - 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)) - { - sp->route = TRUE; - } - else - { - DBG1(DBG_KNL, "installing route for policy %R === %R failed", - sp->src, sp->dst); - } - } - else + if (manage_route(this, entry->local, entry->remote, + sp->src, sp->dst, add)) { - 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); - } + sp->route = add; } - dst->destroy(dst); - src->destroy(src); - DESTROY_IF(gtw); } enumerator->destroy(enumerator); -- cgit v1.2.3 From 78bde29a7c3cac06ef23fffc1bbd4f6caffef176 Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 9 Apr 2014 10:42:15 +0200 Subject: kernel-wfp: Install routes for trap policies --- .../plugins/kernel_wfp/kernel_wfp_ipsec.c | 24 +++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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; } -- cgit v1.2.3 From 75afbeee218f16a96cb00b1e776620b7e313841a Mon Sep 17 00:00:00 2001 From: Martin Willi Date: Wed, 9 Apr 2014 10:42:36 +0200 Subject: 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(-) (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c') 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); } } -- cgit v1.2.3