Files
strongswan-ext/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
T
Tobias Brunner 6b58665697 kernel-wfp: Uninstall policies before deleting provider
The policies reference the provider, so it might not actually get removed
and be left dangling in the WFP system (maybe Windows refcounts it and
still removes it).
2026-07-24 08:47:36 +02:00

2732 lines
64 KiB
C

/*
* Copyright (C) 2013 Martin Willi
*
* Copyright (C) secunet Security Networks 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);
}
/* tunnel-mode policy filters reference the provider context, so remove
* them before the context to avoid leaving it dangling in WFP */
cleanup_policies(this, entry);
if (entry->provider)
{
FwpmProviderContextDeleteById0(this->handle, entry->provider);
}
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;
default:
continue;
}
/* 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)
{
kernel_acquire_data_t data = {
.cpu = CPU_ID_MAX,
};
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)
{
data.src = src ? src->clone(src) : NULL;
data.dst = dst ? dst->clone(dst) : NULL;
charon->kernel->acquire(charon->kernel, reqid, &data);
DESTROY_IF(data.src);
DESTROY_IF(data.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_REMOTE_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 = data->new_dst->get_port(data->new_dst);
ports.remoteUdpEncapPort = data->new_src->get_port(data->new_src);
}
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);
if (data->new_reqid)
{
entry->reqid = data->new_reqid;
}
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, bool tunnel,
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;
}
if (tunnel)
{
filter.subLayerKey = FWPM_SUBLAYER_IPSEC_TUNNEL;
}
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), i;
UINT64 filters[4] = { 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, FALSE, &filters[0]) ||
!add_bypass(this, family, port, TRUE, TRUE, &filters[1]) ||
!add_bypass(this, family, port, FALSE, FALSE, &filters[2]) ||
!add_bypass(this, family, port, FALSE, TRUE, &filters[3]))
{
for (i = 0; i < countof(filters); i++)
{
if (filters[i])
{
FwpmFilterDeleteById0(this->handle, filters[i]);
}
}
return FALSE;
}
this->mutex->lock(this->mutex);
for (i = 0; i < countof(filters); i++)
{
array_insert(this->bypass, ARRAY_TAIL, &filters[i]);
}
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;
}