/* * Copyright (C) 2013 Martin Willi * Copyright (C) 2013 revosec AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ /* Windows 7, for some fwpmu.h functionality */ #define _WIN32_WINNT 0x0601 #include "kernel_wfp_compat.h" #include "kernel_wfp_ipsec.h" #include #include #include #include #include #ifndef IPPROTO_IPIP #define IPPROTO_IPIP 4 #endif #ifndef IPPROTO_IPV6 #define IPPROTO_IPV6 41 #endif typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t; struct private_kernel_wfp_ipsec_t { /** * Public interface */ kernel_wfp_ipsec_t public; /** * Next SPI to allocate */ refcount_t nextspi; /** * Mix value to distribute SPI allocation randomly */ uint32_t mixspi; /** * IKE bypass filters, as UINT64 filter LUID */ array_t *bypass; /** * Temporary SAD/SPD entries referenced reqid, as uintptr_t => entry_t */ hashtable_t *tsas; /** * SAD/SPD entries referenced by inbound SA, as sa_entry_t => entry_t */ hashtable_t *isas; /** * SAD/SPD entries referenced by outbound SA, as sa_entry_t => entry_t */ hashtable_t *osas; /** * Installed routes, as route_t => route_t */ hashtable_t *routes; /** * Installed traps, as trap_t => trap_t */ hashtable_t *traps; /** * Mutex for accessing entries */ mutex_t *mutex; /** * WFP session handle */ HANDLE handle; /** * Provider charon registers as */ FWPM_PROVIDER0 provider; /** * Event handle */ HANDLE event; }; /** * Security association entry */ typedef struct { /** SPI for this SA */ uint32_t spi; /** protocol, IPPROTO_ESP/IPPROTO_AH */ uint8_t protocol; /** hard lifetime of SA */ uint32_t lifetime; /** destination host address for this SPI */ host_t *dst; struct { /** algorithm */ uint16_t alg; /** key */ chunk_t key; } integ, encr; } sa_entry_t; /** * Hash function for sas lookup table */ static u_int hash_sa(sa_entry_t *key) { return chunk_hash_inc(chunk_from_thing(key->spi), chunk_hash(key->dst->get_address(key->dst))); } /** * equals function for sas lookup table */ static bool equals_sa(sa_entry_t *a, sa_entry_t *b) { return a->spi == b->spi && a->dst->ip_equals(a->dst, b->dst); } /** * Security policy entry */ typedef struct { /** policy source addresses */ traffic_selector_t *src; /** policy destination addresses */ traffic_selector_t *dst; /** WFP allocated LUID for inbound filter ID */ uint64_t policy_in; /** WFP allocated LUID for outbound filter ID */ uint64_t policy_out; /** WFP allocated LUID for forward inbound filter ID, tunnel mode only */ uint64_t policy_fwd_in; /** WFP allocated LUID for forward outbound filter ID, tunnel mode only */ uint64_t policy_fwd_out; /** have installed a route for it? */ bool route; } sp_entry_t; /** * Destroy an SP entry */ static void sp_entry_destroy(sp_entry_t *sp) { sp->src->destroy(sp->src); sp->dst->destroy(sp->dst); free(sp); } /** * Collection of SA/SP database entries for a reqid */ typedef struct { /** reqid of entry */ uint32_t reqid; /** outer address on local host */ host_t *local; /** outer address on remote host */ host_t *remote; /** inbound SA entry */ sa_entry_t isa; /** outbound SA entry */ sa_entry_t osa; /** associated (outbound) policies, as sp_entry_t* */ array_t *sps; /** IPsec mode, tunnel|transport */ ipsec_mode_t mode; /** UDP encapsulation */ bool encap; /** provider context, for tunnel mode only */ uint64_t provider; /** WFP allocated LUID for SA context */ uint64_t sa_id; /** WFP allocated LUID for tunnel mode IP-IPv4 inbound filter */ uint64_t ip_ipv4_in; /** WFP allocated LUID for tunnel mode IP-IPv4 outbound filter */ uint64_t ip_ipv4_out; /** WFP allocated LUID for tunnel mode IP-IPv6 inbound filter */ uint64_t ip_ipv6_in; /** WFP allocated LUID for tunnel mode IP-IPv6 outbound filter */ uint64_t ip_ipv6_out; } entry_t; /** * Installed route */ typedef struct { /** destination net of route */ host_t *dst; /** prefix length of dst */ uint8_t mask; /** source address for route */ host_t *src; /** gateway of route, NULL if directly attached */ host_t *gtw; /** references for route */ u_int refs; } route_t; /** * Destroy a route_t */ static void destroy_route(route_t *this) { this->dst->destroy(this->dst); this->src->destroy(this->src); DESTROY_IF(this->gtw); free(this); } /** * Hashtable equals function for routes */ static bool equals_route(route_t *a, route_t *b) { return a->mask == b->mask && a->dst->ip_equals(a->dst, b->dst) && a->src->ip_equals(a->src, b->src); } /** * Hashtable hash function for routes */ static u_int hash_route(route_t *route) { return chunk_hash_inc(route->src->get_address(route->src), chunk_hash_inc(route->dst->get_address(route->dst), route->mask)); } /** forward declaration */ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, bool add); /** * Remove policies associated to an entry from kernel */ static void cleanup_policies(private_kernel_wfp_ipsec_t *this, entry_t *entry) { enumerator_t *enumerator; sp_entry_t *sp; if (entry->mode == MODE_TUNNEL) { manage_routes(this, entry, FALSE); } enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) { if (sp->policy_in) { FwpmFilterDeleteById0(this->handle, sp->policy_in); sp->policy_in = 0; } if (sp->policy_out) { FwpmFilterDeleteById0(this->handle, sp->policy_out); sp->policy_out = 0; } if (sp->policy_fwd_in) { FwpmFilterDeleteById0(this->handle, sp->policy_fwd_in); sp->policy_fwd_in = 0; } if (sp->policy_fwd_out) { FwpmFilterDeleteById0(this->handle, sp->policy_fwd_out); sp->policy_fwd_out = 0; } } enumerator->destroy(enumerator); } /** * Destroy a SA/SP entry set */ static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry) { if (entry->ip_ipv4_in) { FwpmFilterDeleteById0(this->handle, entry->ip_ipv4_in); } if (entry->ip_ipv4_out) { FwpmFilterDeleteById0(this->handle, entry->ip_ipv4_out); } if (entry->ip_ipv6_in) { FwpmFilterDeleteById0(this->handle, entry->ip_ipv6_in); } if (entry->ip_ipv6_out) { FwpmFilterDeleteById0(this->handle, entry->ip_ipv6_out); } if (entry->sa_id) { IPsecSaContextDeleteById0(this->handle, entry->sa_id); } if (entry->provider) { FwpmProviderContextDeleteById0(this->handle, entry->provider); } cleanup_policies(this, entry); array_destroy_function(entry->sps, (void*)sp_entry_destroy, NULL); entry->local->destroy(entry->local); entry->remote->destroy(entry->remote); chunk_clear(&entry->isa.integ.key); chunk_clear(&entry->isa.encr.key); chunk_clear(&entry->osa.integ.key); chunk_clear(&entry->osa.encr.key); free(entry); } /** * Append/Realloc a filter condition to an existing condition set */ static FWPM_FILTER_CONDITION0 *append_condition(FWPM_FILTER_CONDITION0 *conds[], int *count) { FWPM_FILTER_CONDITION0 *cond; (*count)++; *conds = realloc(*conds, *count * sizeof(*cond)); cond = *conds + *count - 1; memset(cond, 0, sizeof(*cond)); return cond; } /** * Convert an IPv4 prefix to a host order subnet mask */ static uint32_t prefix2mask(uint8_t prefix) { uint8_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, uint16_t from, uint16_t to) { if (from == to) { cond->matchType = FWP_MATCH_EQUAL; cond->conditionValue.type = FWP_UINT16; cond->conditionValue.uint16 = from; } else { cond->matchType = FWP_MATCH_RANGE; cond->conditionValue.type = FWP_RANGE_TYPE; cond->conditionValue.rangeValue = calloc(1, sizeof(FWP_RANGE0)); cond->conditionValue.rangeValue->valueLow.type = FWP_UINT16; cond->conditionValue.rangeValue->valueLow.uint16 = from; cond->conditionValue.rangeValue->valueHigh.type = FWP_UINT16; cond->conditionValue.rangeValue->valueHigh.uint16 = to; } } /** * (Re-)allocate filter conditions for given local or remote traffic selector */ static bool ts2condition(traffic_selector_t *ts, const GUID *target, FWPM_FILTER_CONDITION0 *conds[], int *count) { FWPM_FILTER_CONDITION0 *cond; FWP_BYTE_ARRAY16 *addr; FWP_RANGE0 *range; uint16_t from_port, to_port; void *from, *to; uint8_t proto; host_t *net; uint8_t prefix; from = ts->get_from_address(ts).ptr; to = ts->get_to_address(ts).ptr; from_port = ts->get_from_port(ts); to_port = ts->get_to_port(ts); cond = append_condition(conds, count); cond->fieldKey = *target; if (ts->is_host(ts, NULL)) { cond->matchType = FWP_MATCH_EQUAL; switch (ts->get_type(ts)) { case TS_IPV4_ADDR_RANGE: cond->conditionValue.type = FWP_UINT32; cond->conditionValue.uint32 = untoh32(from); break; case TS_IPV6_ADDR_RANGE: cond->conditionValue.type = FWP_BYTE_ARRAY16_TYPE; cond->conditionValue.byteArray16 = addr = malloc(sizeof(*addr)); memcpy(addr, from, sizeof(*addr)); break; default: return FALSE; } } else if (ts->to_subnet(ts, &net, &prefix)) { FWP_V6_ADDR_AND_MASK *m6; FWP_V4_ADDR_AND_MASK *m4; cond->matchType = FWP_MATCH_EQUAL; switch (net->get_family(net)) { case AF_INET: cond->conditionValue.type = FWP_V4_ADDR_MASK; cond->conditionValue.v4AddrMask = m4 = calloc(1, sizeof(*m4)); m4->addr = untoh32(from); m4->mask = prefix2mask(prefix); break; case AF_INET6: cond->conditionValue.type = FWP_V6_ADDR_MASK; cond->conditionValue.v6AddrMask = m6 = calloc(1, sizeof(*m6)); memcpy(m6->addr, from, sizeof(m6->addr)); m6->prefixLength = prefix; break; default: net->destroy(net); return FALSE; } net->destroy(net); } else { cond->matchType = FWP_MATCH_RANGE; cond->conditionValue.type = FWP_RANGE_TYPE; cond->conditionValue.rangeValue = range = calloc(1, sizeof(*range)); switch (ts->get_type(ts)) { case TS_IPV4_ADDR_RANGE: range->valueLow.type = FWP_UINT32; range->valueLow.uint32 = untoh32(from); range->valueHigh.type = FWP_UINT32; range->valueHigh.uint32 = untoh32(to); break; case TS_IPV6_ADDR_RANGE: range->valueLow.type = FWP_BYTE_ARRAY16_TYPE; range->valueLow.byteArray16 = addr = malloc(sizeof(*addr)); memcpy(addr, from, sizeof(*addr)); range->valueHigh.type = FWP_BYTE_ARRAY16_TYPE; range->valueHigh.byteArray16 = addr = malloc(sizeof(*addr)); memcpy(addr, to, sizeof(*addr)); break; default: return FALSE; } } proto = ts->get_protocol(ts); if (proto && target == &FWPM_CONDITION_IP_LOCAL_ADDRESS) { cond = append_condition(conds, count); cond->fieldKey = FWPM_CONDITION_IP_PROTOCOL; cond->matchType = FWP_MATCH_EQUAL; cond->conditionValue.type = FWP_UINT8; cond->conditionValue.uint8 = proto; } if (proto == IPPROTO_ICMP) { if (target == &FWPM_CONDITION_IP_LOCAL_ADDRESS) { uint8_t from_type, to_type, from_code, to_code; from_type = traffic_selector_icmp_type(from_port); to_type = traffic_selector_icmp_type(to_port); from_code = traffic_selector_icmp_code(from_port); to_code = traffic_selector_icmp_code(to_port); if (from_type != 0 || to_type != 0xFF) { cond = append_condition(conds, count); cond->fieldKey = FWPM_CONDITION_ICMP_TYPE; range2cond(cond, from_type, to_type); } if (from_code != 0 || to_code != 0xFF) { cond = append_condition(conds, count); cond->fieldKey = FWPM_CONDITION_ICMP_CODE; range2cond(cond, from_code, to_code); } } } else if (from_port != 0 || to_port != 0xFFFF) { if (target == &FWPM_CONDITION_IP_LOCAL_ADDRESS) { cond = append_condition(conds, count); cond->fieldKey = FWPM_CONDITION_IP_LOCAL_PORT; range2cond(cond, from_port, to_port); } if (target == &FWPM_CONDITION_IP_REMOTE_ADDRESS) { cond = append_condition(conds, count); cond->fieldKey = FWPM_CONDITION_IP_REMOTE_PORT; range2cond(cond, from_port, to_port); } } return TRUE; } /** * Free memory associated to a single condition */ static void free_condition(FWP_DATA_TYPE type, void *value) { FWP_RANGE0 *range; switch (type) { case FWP_BYTE_ARRAY16_TYPE: case FWP_V4_ADDR_MASK: case FWP_V6_ADDR_MASK: free(value); break; case FWP_RANGE_TYPE: range = value; free_condition(range->valueLow.type, range->valueLow.sd); free_condition(range->valueHigh.type, range->valueHigh.sd); free(range); break; default: break; } } /** * Free memory used by a set of conditions */ static void free_conditions(FWPM_FILTER_CONDITION0 *conds, int count) { int i; for (i = 0; i < count; i++) { free_condition(conds[i].conditionValue.type, conds[i].conditionValue.sd); } free(conds); } /** * Find the callout GUID for given parameters */ static bool find_callout(bool tunnel, bool v6, bool inbound, bool forward, bool ale, GUID *layer, GUID *sublayer, GUID *callout) { struct { bool tunnel; bool v6; bool inbound; bool forward; bool ale; const GUID *layer; const GUID *sublayer; const GUID *callout; } map[] = { { 0, 0, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, NULL, &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4 }, { 0, 0, 1, 0, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V4, NULL, &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 }, { 0, 1, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V6, NULL, &FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V6 }, { 0, 1, 1, 0, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V6, NULL, &FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V6 }, { 1, 0, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V4, &FWPM_SUBLAYER_IPSEC_TUNNEL, &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V4 }, { 1, 0, 0, 1, 0, &FWPM_LAYER_IPFORWARD_V4, &FWPM_SUBLAYER_IPSEC_FORWARD_OUTBOUND_TUNNEL, &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V4 }, { 1, 0, 1, 0, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V4, &FWPM_SUBLAYER_IPSEC_TUNNEL, &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V4 }, { 1, 0, 1, 1, 0, &FWPM_LAYER_IPFORWARD_V4, &FWPM_SUBLAYER_IPSEC_TUNNEL, &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V4 }, { 1, 0, 0, 0, 1, &FWPM_LAYER_ALE_AUTH_CONNECT_V4, NULL, &FWPM_CALLOUT_IPSEC_ALE_CONNECT_V4 }, { 1, 0, 1, 0, 1, &FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4, NULL, &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_ALE_ACCEPT_V4}, { 1, 1, 0, 0, 0, &FWPM_LAYER_OUTBOUND_TRANSPORT_V6, &FWPM_SUBLAYER_IPSEC_TUNNEL, &FWPM_CALLOUT_IPSEC_OUTBOUND_TUNNEL_V6 }, { 1, 1, 0, 1, 0, &FWPM_LAYER_IPFORWARD_V6, &FWPM_SUBLAYER_IPSEC_FORWARD_OUTBOUND_TUNNEL, &FWPM_CALLOUT_IPSEC_FORWARD_OUTBOUND_TUNNEL_V6 }, { 1, 1, 1, 0, 0, &FWPM_LAYER_INBOUND_TRANSPORT_V6, &FWPM_SUBLAYER_IPSEC_TUNNEL, &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_V6 }, { 1, 1, 1, 1, 0, &FWPM_LAYER_IPFORWARD_V6, &FWPM_SUBLAYER_IPSEC_TUNNEL, &FWPM_CALLOUT_IPSEC_FORWARD_INBOUND_TUNNEL_V6 }, { 1, 1, 0, 0, 1, &FWPM_LAYER_ALE_AUTH_CONNECT_V6, NULL, &FWPM_CALLOUT_IPSEC_ALE_CONNECT_V6 }, { 1, 1, 1, 0, 1, &FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6, NULL, &FWPM_CALLOUT_IPSEC_INBOUND_TUNNEL_ALE_ACCEPT_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 && ale == map[i].ale) { *callout = *map[i].callout; *layer = *map[i].layer; if (map[i].sublayer) { *sublayer = *map[i].sublayer; } return TRUE; } } return FALSE; } /** * Install a single policy in to the kernel */ static bool install_sp(private_kernel_wfp_ipsec_t *this, sp_entry_t *sp, GUID *context, bool inbound, bool fwd, UINT64 *filter_id) { FWPM_FILTER_CONDITION0 *conds = NULL; traffic_selector_t *local, *remote; const GUID *ltarget, *rtarget; int count = 0; bool v6; DWORD res; FWPM_FILTER0 filter = { .displayData = { .name = L"charon IPsec policy", }, .action = { .type = FWP_ACTION_CALLOUT_TERMINATING, }, }; if (context) { filter.flags |= FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT; filter.providerKey = (GUID*)&this->provider.providerKey; filter.providerContextKey = *context; } v6 = sp->src->get_type(sp->src) == TS_IPV6_ADDR_RANGE; if (!find_callout(context != NULL, v6, inbound, fwd, FALSE, &filter.layerKey, &filter.subLayerKey, &filter.action.calloutKey)) { return FALSE; } if (inbound && fwd) { local = sp->dst; remote = sp->src; } else { local = sp->src; remote = sp->dst; } if (fwd) { ltarget = &FWPM_CONDITION_IP_SOURCE_ADDRESS; rtarget = &FWPM_CONDITION_IP_DESTINATION_ADDRESS; } else { ltarget = &FWPM_CONDITION_IP_LOCAL_ADDRESS; rtarget = &FWPM_CONDITION_IP_REMOTE_ADDRESS; } if (!ts2condition(local, ltarget, &conds, &count) || !ts2condition(remote, rtarget, &conds, &count)) { free_conditions(conds, count); return FALSE; } filter.numFilterConditions = count; filter.filterCondition = conds; res = FwpmFilterAdd0(this->handle, &filter, NULL, filter_id); free_conditions(conds, count); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "installing IPv%d %s%sbound %s WFP filter failed: 0x%08x", v6 ? 6 : 4, fwd ? "forward " : "", inbound ? "in" : "out", context ? "tunnel" : "transport", res); return FALSE; } return TRUE; } /** * Install an IP-IP allow filter for SA specific hosts */ static bool install_ipip_ale(private_kernel_wfp_ipsec_t *this, host_t *local, host_t *remote, GUID *context, bool inbound, int proto, uint64_t *filter_id) { traffic_selector_t *lts, *rts; FWPM_FILTER_CONDITION0 *conds = NULL; int count = 0; bool v6; DWORD res; FWPM_FILTER0 filter = { .displayData = { .name = L"charon IPsec IP-in-IP ALE policy", }, .action = { .type = FWP_ACTION_CALLOUT_TERMINATING, }, }; if (context) { filter.flags |= FWPM_FILTER_FLAG_HAS_PROVIDER_CONTEXT; filter.providerKey = (GUID*)&this->provider.providerKey; filter.providerContextKey = *context; } v6 = local->get_family(local) == AF_INET6; if (!find_callout(TRUE, v6, inbound, FALSE, TRUE, &filter.layerKey, &filter.subLayerKey, &filter.action.calloutKey)) { return FALSE; } lts = traffic_selector_create_from_subnet(local->clone(local), v6 ? 128 : 32 , proto, 0, 65535); rts = traffic_selector_create_from_subnet(remote->clone(remote), v6 ? 128 : 32 , proto, 0, 65535); if (!ts2condition(lts, &FWPM_CONDITION_IP_LOCAL_ADDRESS, &conds, &count) || !ts2condition(rts, &FWPM_CONDITION_IP_REMOTE_ADDRESS, &conds, &count)) { free_conditions(conds, count); lts->destroy(lts); rts->destroy(rts); return FALSE; } lts->destroy(lts); rts->destroy(rts); 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 IP-IPv%d %s ALE WFP filter failed: 0x%08x", v6 ? 6 : 4, inbound ? "inbound" : "outbound", 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; bool has_v4 = FALSE, has_v6 = FALSE; enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) { switch (sp->src->get_type(sp->src)) { case TS_IPV4_ADDR_RANGE: has_v4 = TRUE; break; case TS_IPV6_ADDR_RANGE: has_v6 = TRUE; break; } /* inbound policy */ if (!install_sp(this, sp, context, TRUE, FALSE, &sp->policy_in)) { enumerator->destroy(enumerator); return FALSE; } /* outbound policy */ if (!install_sp(this, sp, context, FALSE, FALSE, &sp->policy_out)) { enumerator->destroy(enumerator); return FALSE; } if (context) { if (!sp->src->is_host(sp->src, entry->local) || !sp->dst->is_host(sp->dst, entry->remote)) { /* inbound forward policy, from decapsulation */ if (!install_sp(this, sp, context, TRUE, TRUE, &sp->policy_fwd_in)) { enumerator->destroy(enumerator); return FALSE; } /* outbound forward policy, to encapsulate */ if (!install_sp(this, sp, context, FALSE, TRUE, &sp->policy_fwd_out)) { enumerator->destroy(enumerator); return FALSE; } } } } enumerator->destroy(enumerator); if (context) { /* In tunnel mode, Windows does firewall filtering on decrypted but * non-unwrapped packets: It sees them as IP-in-IP packets. When using * a default-drop policy, we need to allow such packets explicitly. */ if (has_v4) { if (!install_ipip_ale(this, entry->local, entry->remote, context, TRUE, IPPROTO_IPIP, &entry->ip_ipv4_in)) { return FALSE; } if (!install_ipip_ale(this, entry->local, entry->remote, NULL, FALSE, IPPROTO_IPIP, &entry->ip_ipv4_out)) { return FALSE; } } if (has_v6) { if (!install_ipip_ale(this, entry->local, entry->remote, context, TRUE, IPPROTO_IPV6, &entry->ip_ipv6_in)) { return FALSE; } if (!install_ipip_ale(this, entry->local, entry->remote, NULL, FALSE, IPPROTO_IPV6, &entry->ip_ipv6_out)) { 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 SA */ static bool install_sa(private_kernel_wfp_ipsec_t *this, entry_t *entry, bool inbound, sa_entry_t *sa, FWP_IP_VERSION version) { IPSEC_SA_AUTH_AND_CIPHER_INFORMATION0 info = {}; IPSEC_SA0 ipsec = { .spi = ntohl(sa->spi), }; IPSEC_SA_BUNDLE0 bundle = { .lifetime = { .lifetimeSeconds = inbound ? entry->isa.lifetime : entry->osa.lifetime, }, .saList = &ipsec, .numSAs = 1, .ipVersion = version, }; struct { uint16_t alg; chunk_t key; } integ = {}, encr = {}; DWORD res; switch (sa->protocol) { case IPPROTO_AH: ipsec.saTransformType = IPSEC_TRANSFORM_AH; ipsec.ahInformation = &info.saAuthInformation; integ.key = sa->integ.key; integ.alg = sa->integ.alg; break; case IPPROTO_ESP: if (sa->encr.alg == ENCR_NULL || sa->encr.alg == ENCR_NULL_AUTH_AES_GMAC) { ipsec.saTransformType = IPSEC_TRANSFORM_ESP_AUTH; ipsec.espAuthInformation = &info.saAuthInformation; } else { ipsec.saTransformType = IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER; ipsec.espAuthAndCipherInformation = &info; encr.key = sa->encr.key; encr.alg = sa->encr.alg; } if (encryption_algorithm_is_aead(sa->encr.alg)) { integ.alg = encr2integ(sa->encr.alg, sa->encr.key.len); integ.key = sa->encr.key; } else { integ.alg = sa->integ.alg; integ.key = sa->integ.key; } break; default: return FALSE; } if (integ.alg) { info.saAuthInformation.authKey = chunk2blob(integ.key); if (!alg2auth(integ.alg, &info.saAuthInformation)) { DBG1(DBG_KNL, "integrity algorithm %N not supported by WFP", integrity_algorithm_names, integ.alg); return FALSE; } } if (encr.alg) { info.saCipherInformation.cipherKey = chunk2blob(encr.key); if (!alg2cipher(encr.alg, encr.key.len, &info.saCipherInformation)) { DBG1(DBG_KNL, "encryption algorithm %N not supported by WFP", encryption_algorithm_names, encr.alg); return FALSE; } } if (inbound) { res = IPsecSaContextAddInbound0(this->handle, entry->sa_id, &bundle); } else { bundle.flags |= IPSEC_SA_BUNDLE_FLAG_ASSUME_UDP_CONTEXT_OUTBOUND; res = IPsecSaContextAddOutbound0(this->handle, entry->sa_id, &bundle); } if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "adding %sbound WFP SA failed: 0x%08x", inbound ? "in" : "out", res); return FALSE; } return TRUE; } /** * Convert an IPv6 host address to WFP representation */ static void host2address6(host_t *host, void *out) { uint32_t *src, *dst = out; src = (uint32_t*)host->get_address(host).ptr; dst[0] = untoh32(&src[3]); dst[1] = untoh32(&src[2]); dst[2] = untoh32(&src[1]); dst[3] = untoh32(&src[0]); } /** * Fill in traffic structure from entry addresses */ static bool hosts2traffic(private_kernel_wfp_ipsec_t *this, host_t *l, host_t *r, IPSEC_TRAFFIC1 *traffic) { if (l->get_family(l) != r->get_family(r)) { return FALSE; } switch (l->get_family(l)) { case AF_INET: traffic->ipVersion = FWP_IP_VERSION_V4; traffic->localV4Address = untoh32(l->get_address(l).ptr); traffic->remoteV4Address = untoh32(r->get_address(r).ptr); return TRUE; case AF_INET6: traffic->ipVersion = FWP_IP_VERSION_V6; host2address6(l, &traffic->localV6Address); host2address6(r, &traffic->remoteV6Address); return TRUE; default: return FALSE; } } /** * Install SAs to the kernel */ static bool install_sas(private_kernel_wfp_ipsec_t *this, entry_t *entry, IPSEC_TRAFFIC_TYPE type) { IPSEC_TRAFFIC1 traffic = { .trafficType = type, }; IPSEC_GETSPI1 spi = { .inboundIpsecTraffic = { .trafficType = type, }, }; enumerator_t *enumerator; sp_entry_t *sp; DWORD res; if (type == IPSEC_TRAFFIC_TYPE_TRANSPORT) { enumerator = array_create_enumerator(entry->sps); if (enumerator->enumerate(enumerator, &sp)) { traffic.ipsecFilterId = sp->policy_out; spi.inboundIpsecTraffic.ipsecFilterId = sp->policy_in; } else { enumerator->destroy(enumerator); return FALSE; } enumerator->destroy(enumerator); } else { traffic.tunnelPolicyId = entry->provider; spi.inboundIpsecTraffic.tunnelPolicyId = entry->provider; } if (!hosts2traffic(this, entry->local, entry->remote, &traffic)) { return FALSE; } res = IPsecSaContextCreate1(this->handle, &traffic, NULL, NULL, &entry->sa_id); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "creating WFP SA context failed: 0x%08x", res); return FALSE; } memcpy(spi.inboundIpsecTraffic.localV6Address, traffic.localV6Address, sizeof(traffic.localV6Address)); memcpy(spi.inboundIpsecTraffic.remoteV6Address, traffic.remoteV6Address, sizeof(traffic.remoteV6Address)); spi.ipVersion = traffic.ipVersion; res = IPsecSaContextSetSpi0(this->handle, entry->sa_id, &spi, ntohl(entry->isa.spi)); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "setting WFP SA SPI failed: 0x%08x", res); IPsecSaContextDeleteById0(this->handle, entry->sa_id); entry->sa_id = 0; return FALSE; } if (!install_sa(this, entry, TRUE, &entry->isa, spi.ipVersion) || !install_sa(this, entry, FALSE, &entry->osa, spi.ipVersion)) { IPsecSaContextDeleteById0(this->handle, entry->sa_id); entry->sa_id = 0; return FALSE; } if (entry->encap) { IPSEC_V4_UDP_ENCAPSULATION0 encap = { .localUdpEncapPort = entry->local->get_port(entry->local), .remoteUdpEncapPort = entry->remote->get_port(entry->remote), }; IPSEC_SA_CONTEXT1 *ctx; res = IPsecSaContextGetById1(this->handle, entry->sa_id, &ctx); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "getting WFP SA for UDP encap failed: 0x%08x", res); IPsecSaContextDeleteById0(this->handle, entry->sa_id); entry->sa_id = 0; return FALSE; } ctx->inboundSa->udpEncapsulation = &encap; ctx->outboundSa->udpEncapsulation = &encap; res = IPsecSaContextUpdate0(this->handle, IPSEC_SA_DETAILS_UPDATE_UDP_ENCAPSULATION, ctx); FwpmFreeMemory0((void**)&ctx); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "enable WFP UDP encap failed: 0x%08x", res); IPsecSaContextDeleteById0(this->handle, entry->sa_id); entry->sa_id = 0; return FALSE; } } return TRUE; } /** * Install a transport mode SA/SP set to the kernel */ static bool install_transport(private_kernel_wfp_ipsec_t *this, entry_t *entry) { if (install_sps(this, entry, NULL) && install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TRANSPORT)) { return TRUE; } cleanup_policies(this, entry); return FALSE; } /** * Generate a new GUID, random */ static bool generate_guid(private_kernel_wfp_ipsec_t *this, GUID *guid) { bool ok; rng_t *rng; rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); if (!rng) { return FALSE; } ok = rng->get_bytes(rng, sizeof(GUID), (uint8_t*)guid); rng->destroy(rng); return ok; } /** * Register a dummy tunnel provider to associate tunnel filters to */ static bool add_tunnel_provider(private_kernel_wfp_ipsec_t *this, entry_t *entry, GUID *guid, UINT64 *luid) { DWORD res; IPSEC_AUTH_TRANSFORM0 transform = { /* Create any valid proposal. This is actually not used, as we * don't create an SA from this information. */ .authTransformId = IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96, }; IPSEC_SA_TRANSFORM0 transforms = { .ipsecTransformType = IPSEC_TRANSFORM_ESP_AUTH, .espAuthTransform = &transform, }; IPSEC_PROPOSAL0 proposal = { .lifetime = { /* We need a valid lifetime, even if we don't create any SA * from these values. Pick some values accepted. */ .lifetimeSeconds = 0xFFFF, .lifetimeKilobytes = 0xFFFFFFFF, .lifetimePackets = 0xFFFFFFFF, }, .numSaTransforms = 1, .saTransforms = &transforms, }; IPSEC_TUNNEL_POLICY0 policy = { .numIpsecProposals = 1, .ipsecProposals = &proposal, .saIdleTimeout = { /* not used, set to lifetime for maximum */ .idleTimeoutSeconds = proposal.lifetime.lifetimeSeconds, .idleTimeoutSecondsFailOver = proposal.lifetime.lifetimeSeconds, }, }; FWPM_PROVIDER_CONTEXT0 qm = { .displayData = { .name = L"charon tunnel provider", }, .providerKey = (GUID*)&this->provider.providerKey, .type = FWPM_IPSEC_IKE_QM_TUNNEL_CONTEXT, .ikeQmTunnelPolicy = &policy, }; switch (entry->local->get_family(entry->local)) { case AF_INET: policy.tunnelEndpoints.ipVersion = FWP_IP_VERSION_V4; policy.tunnelEndpoints.localV4Address = untoh32(entry->local->get_address(entry->local).ptr); policy.tunnelEndpoints.remoteV4Address = untoh32(entry->remote->get_address(entry->remote).ptr); break; case AF_INET6: policy.tunnelEndpoints.ipVersion = FWP_IP_VERSION_V6; host2address6(entry->local, &policy.tunnelEndpoints.localV6Address); host2address6(entry->remote, &policy.tunnelEndpoints.remoteV6Address); break; default: return FALSE; } if (!generate_guid(this, &qm.providerContextKey)) { return FALSE; } res = FwpmProviderContextAdd0(this->handle, &qm, NULL, luid); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "adding provider context failed: 0x%08x", res); return FALSE; } *guid = qm.providerContextKey; return TRUE; } /** * Install tunnel mode SPs to the kernel */ static bool install_tunnel_sps(private_kernel_wfp_ipsec_t *this, entry_t *entry) { GUID guid; if (!add_tunnel_provider(this, entry, &guid, &entry->provider)) { return FALSE; } if (!install_sps(this, entry, &guid)) { return FALSE; } return TRUE; } /** * Reduce refcount, or uninstall a route if all refs gone */ static bool uninstall_route(private_kernel_wfp_ipsec_t *this, host_t *dst, uint8_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 (charon->kernel->get_interface(charon->kernel, src, &name)) { res = charon->kernel->del_route(charon->kernel, 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, uint8_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 (charon->kernel->get_interface(charon->kernel, src, &name)) { if (charon->kernel->add_route(charon->kernel, dst->get_address(dst), mask, gtw, src, name) == SUCCESS) { INIT(route, .dst = dst->clone(dst), .mask = mask, .src = src->clone(src), .gtw = gtw ? gtw->clone(gtw) : NULL, .refs = 1, ); route = this->routes->put(this->routes, route, route); if (route) { destroy_route(route); } res = TRUE; } free(name); } } this->mutex->unlock(this->mutex); return res; } /** * (Un)-install a single route */ static bool manage_route(private_kernel_wfp_ipsec_t *this, host_t *local, host_t *remote, traffic_selector_t *src_ts, traffic_selector_t *dst_ts, bool add) { host_t *src, *dst, *gtw; uint8_t mask; bool done; if (!dst_ts->to_subnet(dst_ts, &dst, &mask)) { return FALSE; } if (charon->kernel->get_address_by_ts(charon->kernel, src_ts, &src, NULL) != SUCCESS) { dst->destroy(dst); return FALSE; } gtw = charon->kernel->get_nexthop(charon->kernel, remote, -1, local, NULL); if (add) { done = install_route(this, dst, mask, src, gtw); } else { done = uninstall_route(this, dst, mask, src, gtw); } dst->destroy(dst); src->destroy(src); DESTROY_IF(gtw); if (!done) { DBG1(DBG_KNL, "%sinstalling route for policy %R === %R failed", add ? "" : "un", src_ts, dst_ts); } return done; } /** * (Un)-install routes for IPsec policies */ static bool manage_routes(private_kernel_wfp_ipsec_t *this, entry_t *entry, bool add) { enumerator_t *enumerator; sp_entry_t *sp; enumerator = array_create_enumerator(entry->sps); while (enumerator->enumerate(enumerator, &sp)) { if (add && sp->route) { continue; } if (!add && !sp->route) { continue; } if (manage_route(this, entry->local, entry->remote, sp->src, sp->dst, add)) { sp->route = add; } } enumerator->destroy(enumerator); return TRUE; } /** * Install a tunnel mode SA/SP set to the kernel */ static bool install_tunnel(private_kernel_wfp_ipsec_t *this, entry_t *entry) { if (install_tunnel_sps(this, entry) && manage_routes(this, entry, TRUE) && install_sas(this, entry, IPSEC_TRAFFIC_TYPE_TUNNEL)) { return TRUE; } cleanup_policies(this, entry); return FALSE; } /** * Install a SA/SP set to the kernel */ static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry) { switch (entry->mode) { case MODE_TRANSPORT: return install_transport(this, entry); case MODE_TUNNEL: return install_tunnel(this, entry); case MODE_BEET: default: return FALSE; } } /** * Installed trap entry */ typedef struct { /** reqid this trap is installed for */ uint32_t reqid; /** is this a forward policy trap for tunnel mode? */ bool fwd; /** do we have installed a route for this trap policy? */ bool route; /** local address of associated route */ host_t *local; /** remote address of associated route */ host_t *remote; /** src traffic selector */ traffic_selector_t *src; /** dst traffic selector */ traffic_selector_t *dst; /** LUID of installed tunnel policy filter */ UINT64 filter_id; } trap_t; /** * Destroy a trap entry */ static void destroy_trap(trap_t *this) { this->local->destroy(this->local); this->remote->destroy(this->remote); this->src->destroy(this->src); this->dst->destroy(this->dst); free(this); } /** * Hashtable equals function for traps */ static bool equals_trap(trap_t *a, trap_t *b) { return a->filter_id == b->filter_id; } /** * Hashtable hash function for traps */ static u_int hash_trap(trap_t *trap) { return chunk_hash(chunk_from_thing(trap->filter_id)); } /** * Send an acquire for an installed trap filter */ static void acquire(private_kernel_wfp_ipsec_t *this, UINT64 filter_id, traffic_selector_t *src, traffic_selector_t *dst) { uint32_t reqid = 0; trap_t *trap, key = { .filter_id = filter_id, }; this->mutex->lock(this->mutex); trap = this->traps->get(this->traps, &key); if (trap) { reqid = trap->reqid; } this->mutex->unlock(this->mutex); if (reqid) { src = src ? src->clone(src) : NULL; dst = dst ? dst->clone(dst) : NULL; charon->kernel->acquire(charon->kernel, reqid, src, dst); } } /** * Create a single host traffic selector from an FWP address definition */ static traffic_selector_t *addr2ts(FWP_IP_VERSION version, void *data, uint8_t protocol, uint16_t from_port, uint16_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 WINAPI event_callback(void *user, const FWPM_NET_EVENT1 *event) { private_kernel_wfp_ipsec_t *this = user; traffic_selector_t *local = NULL, *remote = NULL; uint8_t protocol = 0; uint16_t from_local = 0, to_local = 65535; uint16_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: DBG1(DBG_KNL, "WFP MM failure: %R === %R, 0x%08x, filterId %llu", local, remote, event->ikeMmFailure->failureErrorCode, event->ikeMmFailure->mmFilterId); break; case FWPM_NET_EVENT_TYPE_IKEEXT_QM_FAILURE: DBG1(DBG_KNL, "WFP QM failure: %R === %R, 0x%08x, filterId %llu", local, remote, event->ikeQmFailure->failureErrorCode, event->ikeQmFailure->qmFilterId); break; case FWPM_NET_EVENT_TYPE_IKEEXT_EM_FAILURE: DBG1(DBG_KNL, "WFP EM failure: %R === %R, 0x%08x, filterId %llu", local, remote, event->ikeEmFailure->failureErrorCode, event->ikeEmFailure->qmFilterId); break; case FWPM_NET_EVENT_TYPE_IPSEC_KERNEL_DROP: DBG1(DBG_KNL, "IPsec kernel drop: %R === %R, error 0x%08x, " "SPI 0x%08x, %s filterId %llu", local, remote, event->ipsecDrop->failureStatus, event->ipsecDrop->spi, event->ipsecDrop->direction ? "in" : "out", event->ipsecDrop->filterId); break; case FWPM_NET_EVENT_TYPE_IPSEC_DOSP_DROP: default: break; } DESTROY_IF(local); DESTROY_IF(remote); } /** * Register for net events */ static bool register_events(private_kernel_wfp_ipsec_t *this) { FWPM_NET_EVENT_SUBSCRIPTION0 subscription = {}; DWORD res; res = FwpmNetEventSubscribe0(this->handle, &subscription, event_callback, this, &this->event); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "registering for WFP events failed: 0x%08x", res); return FALSE; } return TRUE; } /** * Install a trap policy to kernel */ static bool install_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap) { FWPM_FILTER_CONDITION0 *conds = NULL; int count = 0; DWORD res; const GUID *starget, *dtarget; UINT64 weight = 0x000000000000ff00; FWPM_FILTER0 filter = { .displayData = { .name = L"charon IPsec trap", }, .action = { .type = FWP_ACTION_BLOCK, }, .weight = { .type = FWP_UINT64, .uint64 = &weight, }, }; if (trap->fwd) { if (trap->src->get_type(trap->src) == TS_IPV4_ADDR_RANGE) { filter.layerKey = FWPM_LAYER_IPFORWARD_V4; } else { filter.layerKey = FWPM_LAYER_IPFORWARD_V6; } starget = &FWPM_CONDITION_IP_SOURCE_ADDRESS; dtarget = &FWPM_CONDITION_IP_DESTINATION_ADDRESS; } else { if (trap->src->get_type(trap->src) == TS_IPV4_ADDR_RANGE) { filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V4; } else { filter.layerKey = FWPM_LAYER_OUTBOUND_TRANSPORT_V6; } starget = &FWPM_CONDITION_IP_LOCAL_ADDRESS; dtarget = &FWPM_CONDITION_IP_REMOTE_ADDRESS; } if (!ts2condition(trap->src, starget, &conds, &count) || !ts2condition(trap->dst, dtarget, &conds, &count)) { free_conditions(conds, count); return FALSE; } filter.numFilterConditions = count; filter.filterCondition = conds; res = FwpmFilterAdd0(this->handle, &filter, NULL, &trap->filter_id); free_conditions(conds, count); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "installing WFP trap filter failed: 0x%08x", res); return FALSE; } return TRUE; } /** * Uninstall a trap policy from kernel */ static bool uninstall_trap(private_kernel_wfp_ipsec_t *this, trap_t *trap) { DWORD res; res = FwpmFilterDeleteById0(this->handle, trap->filter_id); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "uninstalling WFP trap filter failed: 0x%08x", res); return FALSE; } return TRUE; } /** * Create and install a new trap entry */ static bool add_trap(private_kernel_wfp_ipsec_t *this, uint32_t reqid, bool fwd, host_t *local, host_t *remote, traffic_selector_t *src, traffic_selector_t *dst) { trap_t *trap; INIT(trap, .reqid = reqid, .fwd = fwd, .src = src->clone(src), .dst = dst->clone(dst), .local = local->clone(local), .remote = remote->clone(remote), ); if (!install_trap(this, trap)) { destroy_trap(trap); return FALSE; } trap->route = manage_route(this, local, remote, src, dst, TRUE); this->mutex->lock(this->mutex); this->traps->put(this->traps, trap, trap); this->mutex->unlock(this->mutex); return TRUE; } /** * Uninstall and remove a new trap entry */ static bool remove_trap(private_kernel_wfp_ipsec_t *this, uint32_t reqid, bool fwd, traffic_selector_t *src, traffic_selector_t *dst) { enumerator_t *enumerator; trap_t *trap, *found = NULL; this->mutex->lock(this->mutex); enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, NULL, &trap)) { if (reqid == trap->reqid && fwd == trap->fwd && src->equals(src, trap->src) && dst->equals(dst, trap->dst)) { this->traps->remove_at(this->traps, enumerator); found = trap; break; } } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); if (found) { if (trap->route) { trap->route = !manage_route(this, trap->local, trap->remote, src, dst, FALSE); } uninstall_trap(this, found); destroy_trap(found); return TRUE; } return FALSE; } METHOD(kernel_ipsec_t, get_features, kernel_feature_t, private_kernel_wfp_ipsec_t *this) { return KERNEL_ESP_V3_TFC | KERNEL_NO_POLICY_UPDATES; } /** * Initialize seeds for SPI generation */ static bool init_spi(private_kernel_wfp_ipsec_t *this) { bool ok = TRUE; rng_t *rng; rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG); if (!rng) { return FALSE; } ok = rng->get_bytes(rng, sizeof(this->nextspi), (uint8_t*)&this->nextspi); if (ok) { ok = rng->get_bytes(rng, sizeof(this->mixspi), (uint8_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 = ((uint64_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, uint8_t protocol, uint32_t *spi) { /* To avoid sequential SPIs, we use a one-to-one permutation function on * an incrementing counter, that is a full period PRNG for the range we * allocate SPIs in. We add some randomness using a fixed XOR and start * the counter at random position. This is not cryptographically safe, * but that is actually not required. * The selected prime should be smaller than the range we allocate SPIs * in, and it must satisfy p % 4 == 3 to map x > p/2 using p - qr. */ static const u_int p = 268435399, offset = 0xc0000000; *spi = htonl(offset + permute(ref_get(&this->nextspi) ^ this->mixspi, p)); return SUCCESS; } METHOD(kernel_ipsec_t, get_cpi, status_t, private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst, uint16_t *cpi) { return NOT_SUPPORTED; } /** * Data for an expire callback job */ typedef struct { /* backref to kernel backend */ private_kernel_wfp_ipsec_t *this; /* SPI of expiring SA */ uint32_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; uint8_t protocol; entry_t *entry = NULL; 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; 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; } this->mutex->unlock(this->mutex); } if (entry) { charon->kernel->expire(charon->kernel, protocol, data->spi, data->dst, data->hard); } return JOB_REQUEUE_NONE; } /** * Schedule an expire event for an SA */ static void schedule_expire(private_kernel_wfp_ipsec_t *this, uint32_t spi, host_t *dst, uint32_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, kernel_ipsec_sa_id_t *id, kernel_ipsec_add_sa_t *data) { host_t *local, *remote; entry_t *entry; if (data->inbound) { /* comes first, create new entry */ local = id->dst->clone(id->dst); remote = id->src->clone(id->src); INIT(entry, .reqid = data->reqid, .isa = { .spi = id->spi, .dst = local, .protocol = id->proto, .lifetime = data->lifetime->time.life, .encr = { .alg = data->enc_alg, .key = chunk_clone(data->enc_key), }, .integ = { .alg = data->int_alg, .key = chunk_clone(data->int_key), }, }, .sps = array_create(0, 0), .local = local, .remote = remote, .mode = data->mode, .encap = data->encap, ); if (data->lifetime->time.life) { schedule_expire(this, id->spi, local, data->lifetime->time.life, TRUE); } if (data->lifetime->time.rekey && data->lifetime->time.rekey != data->lifetime->time.life) { schedule_expire(this, id->spi, local, data->lifetime->time.rekey, FALSE); } this->mutex->lock(this->mutex); this->tsas->put(this->tsas, (void*)(uintptr_t)data->reqid, entry); this->isas->put(this->isas, &entry->isa, entry); this->mutex->unlock(this->mutex); } else { /* comes after inbound, update entry */ this->mutex->lock(this->mutex); entry = this->tsas->remove(this->tsas, (void*)(uintptr_t)data->reqid); this->mutex->unlock(this->mutex); if (!entry) { DBG1(DBG_KNL, "adding outbound SA failed, no inbound SA found " "for reqid %u ", data->reqid); return NOT_FOUND; } /* TODO: should we check for local/remote, mode etc.? */ entry->osa = (sa_entry_t){ .spi = id->spi, .dst = entry->remote, .protocol = id->proto, .lifetime = data->lifetime->time.life, .encr = { .alg = data->enc_alg, .key = chunk_clone(data->enc_key), }, .integ = { .alg = data->int_alg, .key = chunk_clone(data->int_key), }, }; this->mutex->lock(this->mutex); this->osas->put(this->osas, &entry->osa, entry); this->mutex->unlock(this->mutex); } return SUCCESS; } METHOD(kernel_ipsec_t, update_sa, status_t, private_kernel_wfp_ipsec_t *this, kernel_ipsec_sa_id_t *id, kernel_ipsec_update_sa_t *data) { entry_t *entry; sa_entry_t key = { .dst = id->dst, .spi = id->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, data->new_dst, data->new_src, &ctx->inboundSa->traffic) || !hosts2traffic(this, data->new_dst, data->new_src, &ctx->outboundSa->traffic)) { FwpmFreeMemory0((void**)&ctx); return FAILED; } if (data->new_encap != data->encap) { if (data->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 = data->new_dst->clone(data->new_dst); entry->remote = data->new_src->clone(data->new_src); entry->isa.dst = entry->local; entry->osa.dst = entry->remote; this->isas->put(this->isas, &entry->isa, entry); this->osas->put(this->osas, &entry->osa, entry); manage_routes(this, entry, FALSE); manage_routes(this, entry, TRUE); } this->mutex->unlock(this->mutex); return SUCCESS; } METHOD(kernel_ipsec_t, query_sa, status_t, private_kernel_wfp_ipsec_t *this, kernel_ipsec_sa_id_t *id, kernel_ipsec_query_sa_t *data, uint64_t *bytes, uint64_t *packets, time_t *time) { /* It does not seem that WFP provides any means of getting per-SA traffic * statistics. IPsecGetStatistics0/1() provides global stats, and * IPsecSaContextEnum0/1() and IPsecSaEnum0/1() return the configured * values only. */ return NOT_SUPPORTED; } METHOD(kernel_ipsec_t, del_sa, status_t, private_kernel_wfp_ipsec_t *this, kernel_ipsec_sa_id_t *id, kernel_ipsec_del_sa_t *data) { entry_t *entry; sa_entry_t key = { .dst = id->dst, .spi = id->spi, }; this->mutex->lock(this->mutex); entry = this->isas->remove(this->isas, &key); this->mutex->unlock(this->mutex); if (entry) { /* keep entry until removal of outbound */ return SUCCESS; } this->mutex->lock(this->mutex); entry = this->osas->remove(this->osas, &key); this->mutex->unlock(this->mutex); if (entry) { entry_destroy(this, entry); return SUCCESS; } return NOT_FOUND; } METHOD(kernel_ipsec_t, flush_sas, status_t, private_kernel_wfp_ipsec_t *this) { return NOT_SUPPORTED; } METHOD(kernel_ipsec_t, add_policy, status_t, private_kernel_wfp_ipsec_t *this, kernel_ipsec_policy_id_t *id, kernel_ipsec_manage_policy_t *data) { status_t status = SUCCESS; entry_t *entry; sp_entry_t *sp; sa_entry_t key = { .spi = data->sa->esp.use ? data->sa->esp.spi : data->sa->ah.spi, .dst = data->dst, }; if (data->sa->esp.use && data->sa->ah.use) { return NOT_SUPPORTED; } switch (data->type) { case POLICY_IPSEC: break; case POLICY_PASS: case POLICY_DROP: return NOT_SUPPORTED; } switch (id->dir) { case POLICY_OUT: break; case POLICY_IN: case POLICY_FWD: /* not required */ return SUCCESS; default: return NOT_SUPPORTED; } switch (data->prio) { case POLICY_PRIORITY_DEFAULT: break; case POLICY_PRIORITY_ROUTED: if (!add_trap(this, data->sa->reqid, FALSE, data->src, data->dst, id->src_ts, id->dst_ts)) { return FAILED; } if (data->sa->mode == MODE_TUNNEL) { if (!add_trap(this, data->sa->reqid, TRUE, data->src, data->dst, id->src_ts, id->dst_ts)) { return FAILED; } } return SUCCESS; case POLICY_PRIORITY_FALLBACK: default: return NOT_SUPPORTED; } this->mutex->lock(this->mutex); entry = this->osas->get(this->osas, &key); if (entry) { if (data->sa->mode == MODE_TUNNEL || array_count(entry->sps) == 0) { INIT(sp, .src = id->src_ts->clone(id->src_ts), .dst = id->dst_ts->clone(id->dst_ts), ); array_insert(entry->sps, -1, sp); if (array_count(entry->sps) == data->sa->policy_count) { if (!install(this, entry)) { status = FAILED; } } } else { /* TODO: reinstall with a filter using multiple TS? * Filters are ANDed for a match, but we could install a filter * with the inverse TS set using NOT-matches... */ DBG1(DBG_KNL, "multiple transport mode traffic selectors not " "supported by WFP"); status = NOT_SUPPORTED; } } else { DBG1(DBG_KNL, "adding SP failed, no SA found for SPI 0x%08x", key.spi); status = FAILED; } this->mutex->unlock(this->mutex); return status; } METHOD(kernel_ipsec_t, query_policy, status_t, private_kernel_wfp_ipsec_t *this, kernel_ipsec_policy_id_t *id, kernel_ipsec_query_policy_t *data, time_t *use_time) { /* see query_sa() for some notes */ return NOT_SUPPORTED; } METHOD(kernel_ipsec_t, del_policy, status_t, private_kernel_wfp_ipsec_t *this, kernel_ipsec_policy_id_t *id, kernel_ipsec_manage_policy_t *data) { if (id->dir == POLICY_OUT && data->prio == POLICY_PRIORITY_ROUTED) { if (remove_trap(this, data->sa->reqid, FALSE, id->src_ts, id->dst_ts)) { remove_trap(this, data->sa->reqid, TRUE, id->src_ts, id->dst_ts); return SUCCESS; } return NOT_FOUND; } /* not required, as we delete the whole SA/SP set during del_sa() */ return SUCCESS; } METHOD(kernel_ipsec_t, flush_policies, status_t, private_kernel_wfp_ipsec_t *this) { return NOT_SUPPORTED; } /** * Add a bypass policy for a specific UDP port */ static bool add_bypass(private_kernel_wfp_ipsec_t *this, int family, uint16_t port, bool inbound, UINT64 *luid) { FWPM_FILTER_CONDITION0 *cond, *conds = NULL; int count = 0; DWORD res; UINT64 weight = 0xff00000000000000; FWPM_FILTER0 filter = { .displayData = { .name = L"charon IKE bypass", }, .action = { .type = FWP_ACTION_PERMIT, }, .weight = { .type = FWP_UINT64, .uint64 = &weight, }, }; switch (family) { case AF_INET: filter.layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V4 : FWPM_LAYER_OUTBOUND_TRANSPORT_V4; break; case AF_INET6: filter.layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V6 : FWPM_LAYER_OUTBOUND_TRANSPORT_V6; break; default: return FALSE; } cond = append_condition(&conds, &count); cond->fieldKey = FWPM_CONDITION_IP_PROTOCOL; cond->matchType = FWP_MATCH_EQUAL; cond->conditionValue.type = FWP_UINT8; cond->conditionValue.uint8 = IPPROTO_UDP; cond = append_condition(&conds, &count); cond->fieldKey = FWPM_CONDITION_IP_LOCAL_PORT; cond->matchType = FWP_MATCH_EQUAL; cond->conditionValue.type = FWP_UINT16; cond->conditionValue.uint16 = port; filter.numFilterConditions = count; filter.filterCondition = conds; res = FwpmFilterAdd0(this->handle, &filter, NULL, luid); free_conditions(conds, count); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "installing WFP bypass filter failed: 0x%08x", res); return FALSE; } return TRUE; } METHOD(kernel_ipsec_t, bypass_socket, bool, private_kernel_wfp_ipsec_t *this, int fd, int family) { union { struct sockaddr sa; SOCKADDR_IN in; SOCKADDR_IN6 in6; } saddr; int addrlen = sizeof(saddr); UINT64 filter_out, filter_in = 0; uint16_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, uint16_t port) { return FALSE; } METHOD(kernel_ipsec_t, destroy, void, private_kernel_wfp_ipsec_t *this) { UINT64 filter; while (array_remove(this->bypass, ARRAY_TAIL, &filter)) { FwpmFilterDeleteById0(this->handle, filter); } if (this->handle) { if (this->event) { FwpmNetEventUnsubscribe0(this->handle, this->event); } FwpmProviderDeleteByKey0(this->handle, &this->provider.providerKey); FwpmEngineClose0(this->handle); } array_destroy(this->bypass); this->tsas->destroy(this->tsas); this->isas->destroy(this->isas); this->osas->destroy(this->osas); this->routes->destroy(this->routes); this->traps->destroy(this->traps); this->mutex->destroy(this->mutex); free(this); } /* * Described in header. */ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create() { private_kernel_wfp_ipsec_t *this; DWORD res; FWPM_SESSION0 session = { .displayData = { .name = L"charon", .description = L"strongSwan IKE kernel-wfp backend", }, }; INIT(this, .public = { .interface = { .get_features = _get_features, .get_spi = _get_spi, .get_cpi = _get_cpi, .add_sa = _add_sa, .update_sa = _update_sa, .query_sa = _query_sa, .del_sa = _del_sa, .flush_sas = _flush_sas, .add_policy = _add_policy, .query_policy = _query_policy, .del_policy = _del_policy, .flush_policies = _flush_policies, .bypass_socket = _bypass_socket, .enable_udp_decap = _enable_udp_decap, .destroy = _destroy, }, }, .provider = { .displayData = { .name = L"charon", .description = L"strongSwan IKE kernel-wfp backend", }, .providerKey = { 0x59cdae2e, 0xf6bb, 0x4c09, { 0xa9,0x59,0x9d,0x91,0xac,0xaf,0xf9,0x19 }}, }, .mutex = mutex_create(MUTEX_TYPE_RECURSIVE), .bypass = array_create(sizeof(UINT64), 2), .tsas = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), .isas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4), .osas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4), .routes = hashtable_create((void*)hash_route, (void*)equals_route, 4), .traps = hashtable_create((void*)hash_trap, (void*)equals_trap, 4), ); if (!init_spi(this)) { destroy(this); return NULL; } res = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &this->handle); if (res != ERROR_SUCCESS) { DBG1(DBG_KNL, "opening WFP engine failed: 0x%08x", res); destroy(this); return NULL; } res = FwpmProviderAdd0(this->handle, &this->provider, NULL); if (res != ERROR_SUCCESS && res != FWP_E_ALREADY_EXISTS) { DBG1(DBG_KNL, "registering WFP provider failed: 0x%08x", res); destroy(this); return NULL; } if (!register_events(this)) { destroy(this); return NULL; } return &this->public; }