| /* |
| * Copyright (C) 2013 Martin Willi |
| * Copyright (C) 2013 revosec AG |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or (at your |
| * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. |
| * |
| * This program is distributed in the hope that it will be useful, but |
| * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| */ |
| |
| /* Windows 7, for some fwpmu.h functionality */ |
| #define _WIN32_WINNT 0x0601 |
| |
| #include "kernel_wfp_compat.h" |
| #include "kernel_wfp_ipsec.h" |
| |
| #include <daemon.h> |
| #include <threading/mutex.h> |
| #include <collections/array.h> |
| #include <collections/hashtable.h> |
| #include <processing/jobs/callback_job.h> |
| |
| #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, FALSE) == 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, FALSE) == 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; |
| } |