kernel-wfp: Preliminary support for transport mode connections

This commit is contained in:
Martin Willi 2013-11-18 12:57:36 +01:00
parent b1ba0a666c
commit 149fc48e03
2 changed files with 698 additions and 3 deletions

View File

@ -21,6 +21,15 @@ const GUID FWPM_CONDITION_IP_REMOTE_ADDRESS = {
const GUID FWPM_CONDITION_IP_LOCAL_ADDRESS = {
0xd9ee00de, 0xc1ef, 0x4617, { 0xbf,0xe3,0xff,0xd8,0xf5,0xa0,0x89,0x57 }
};
const GUID FWPM_CONDITION_IP_LOCAL_PORT = {
0x0c1ba1af, 0x5765, 0x453f, { 0xaf,0x22,0xa8,0xf7,0x91,0xac,0x77,0x5b }
};
const GUID FWPM_CONDITION_IP_REMOTE_PORT = {
0xc35a604d, 0xd22b, 0x4e1a, { 0x91,0xb4,0x68,0xf6,0x74,0xee,0x67,0x4b }
};
const GUID FWPM_CONDITION_IP_PROTOCOL = {
0x3971ef2b, 0x623e, 0x4f9a, { 0x8c,0xb1,0x6e,0x79,0xb8,0x06,0xb9,0xa7 }
};
const GUID FWPM_LAYER_INBOUND_TRANSPORT_V4 = {
0x5926dfc8, 0xe3cf, 0x4426, { 0xa2,0x83,0xdc,0x39,0x3f,0x5d,0x0f,0x9d }
};

View File

@ -147,6 +147,12 @@ typedef struct {
ipsec_mode_t mode;
/** UDP encapsulation */
bool encap;
/** WFP allocated LUID for inbound filter/tunnel policy ID */
u_int64_t policy_in;
/** WFP allocated LUID for outbound filter/tunnel policy ID */
u_int64_t policy_out;
/** WFP allocated LUID for SA context */
u_int64_t sa_id;
} entry_t;
/**
@ -173,8 +179,20 @@ static entry_t *entry_create(u_int32_t reqid, host_t *local, host_t *remote,
/**
* Destroy a SA/SP entry set
*/
static void entry_destroy(entry_t *entry)
static void entry_destroy(private_kernel_wfp_ipsec_t *this, entry_t *entry)
{
if (entry->sa_id)
{
IPsecSaContextDeleteById0(this->handle, entry->sa_id);
}
if (entry->policy_in)
{
FwpmFilterDeleteById0(this->handle, entry->policy_in);
}
if (entry->policy_out)
{
FwpmFilterDeleteById0(this->handle, entry->policy_out);
}
array_destroy(entry->sas);
array_destroy(entry->sps);
entry->local->destroy(entry->local);
@ -208,6 +226,666 @@ static entry_t *get_or_create_entry(private_kernel_wfp_ipsec_t *this,
return NULL;
}
/**
* Append/Realloc a filter condition to an existing condition set
*/
static FWPM_FILTER_CONDITION0 *append_condition(FWPM_FILTER_CONDITION0 *conds[],
int *count)
{
FWPM_FILTER_CONDITION0 *cond;
(*count)++;
*conds = realloc(*conds, *count * sizeof(*cond));
cond = *conds + *count - 1;
memset(cond, 0, sizeof(*cond));
return cond;
}
/**
* Convert an IPv4 prefix to a host order subnet mask
*/
static u_int32_t prefix2mask(u_int8_t prefix)
{
u_int8_t netmask[4] = {};
int i;
for (i = 0; i < sizeof(netmask); i++)
{
if (prefix < 8)
{
netmask[i] = 0xFF << (8 - prefix);
break;
}
netmask[i] = 0xFF;
prefix -= 8;
}
return untoh32(netmask);
}
/**
* Convert a 16-bit range to a WFP condition
*/
static void range2cond(FWPM_FILTER_CONDITION0 *cond,
u_int16_t from, u_int16_t to)
{
if (from == to)
{
cond->matchType = FWP_MATCH_EQUAL;
cond->conditionValue.type = FWP_UINT16;
cond->conditionValue.uint16 = from;
}
else
{
cond->matchType = FWP_MATCH_RANGE;
cond->conditionValue.type = FWP_RANGE_TYPE;
cond->conditionValue.rangeValue = calloc(1, sizeof(FWP_RANGE0));
cond->conditionValue.rangeValue->valueLow.type = FWP_UINT16;
cond->conditionValue.rangeValue->valueLow.uint16 = from;
cond->conditionValue.rangeValue->valueHigh.type = FWP_UINT16;
cond->conditionValue.rangeValue->valueHigh.uint16 = to;
}
}
/**
* (Re-)allocate filter conditions for given local or remote traffic selector
*/
static bool ts2condition(traffic_selector_t *ts, bool local,
FWPM_FILTER_CONDITION0 *conds[], int *count)
{
FWPM_FILTER_CONDITION0 *cond;
FWP_BYTE_ARRAY16 *addr;
FWP_RANGE0 *range;
u_int16_t from_port, to_port;
void *from, *to;
u_int8_t proto;
host_t *net;
u_int8_t prefix;
from = ts->get_from_address(ts).ptr;
to = ts->get_to_address(ts).ptr;
from_port = ts->get_from_port(ts);
to_port = ts->get_to_port(ts);
cond = append_condition(conds, count);
if (local)
{
cond->fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS;
}
else
{
cond->fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
}
if (ts->is_host(ts, NULL))
{
cond->matchType = FWP_MATCH_EQUAL;
switch (ts->get_type(ts))
{
case TS_IPV4_ADDR_RANGE:
cond->conditionValue.type = FWP_UINT32;
cond->conditionValue.uint32 = untoh32(from);
break;
case TS_IPV6_ADDR_RANGE:
cond->conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
cond->conditionValue.byteArray16 = addr = malloc(sizeof(*addr));
memcpy(addr, from, sizeof(*addr));
break;
default:
return FALSE;
}
}
else if (ts->to_subnet(ts, &net, &prefix))
{
FWP_V6_ADDR_AND_MASK *m6;
FWP_V4_ADDR_AND_MASK *m4;
cond->matchType = FWP_MATCH_EQUAL;
switch (net->get_family(net))
{
case AF_INET:
cond->conditionValue.type = FWP_V4_ADDR_MASK;
cond->conditionValue.v4AddrMask = m4 = calloc(1, sizeof(*m4));
m4->addr = untoh32(from);
m4->mask = prefix2mask(prefix);
break;
case AF_INET6:
cond->conditionValue.type = FWP_V6_ADDR_MASK;
cond->conditionValue.v6AddrMask = m6 = calloc(1, sizeof(*m6));
memcpy(m6->addr, from, sizeof(m6->addr));
m6->prefixLength = prefix;
break;
default:
net->destroy(net);
return FALSE;
}
net->destroy(net);
}
else
{
cond->matchType = FWP_MATCH_RANGE;
cond->conditionValue.type = FWP_RANGE_TYPE;
cond->conditionValue.rangeValue = range = calloc(1, sizeof(*range));
switch (ts->get_type(ts))
{
case TS_IPV4_ADDR_RANGE:
range->valueLow.type = FWP_UINT32;
range->valueLow.uint32 = untoh32(from);
range->valueHigh.type = FWP_UINT32;
range->valueHigh.uint32 = untoh32(to);
break;
case TS_IPV6_ADDR_RANGE:
range->valueLow.type = FWP_BYTE_ARRAY16_TYPE;
range->valueLow.byteArray16 = addr = malloc(sizeof(*addr));
memcpy(addr, from, sizeof(*addr));
range->valueHigh.type = FWP_BYTE_ARRAY16_TYPE;
range->valueHigh.byteArray16 = addr = malloc(sizeof(*addr));
memcpy(addr, to, sizeof(*addr));
break;
default:
return FALSE;
}
}
proto = ts->get_protocol(ts);
if (proto && local)
{
cond = append_condition(conds, count);
cond->fieldKey = FWPM_CONDITION_IP_PROTOCOL;
cond->matchType = FWP_MATCH_EQUAL;
cond->conditionValue.type = FWP_UINT8;
cond->conditionValue.uint8 = proto;
}
if (proto == IPPROTO_ICMP)
{
if (local)
{
u_int8_t from_type, to_type, from_code, to_code;
from_type = traffic_selector_icmp_type(from_port);
to_type = traffic_selector_icmp_type(to_port);
from_code = traffic_selector_icmp_code(from_port);
to_code = traffic_selector_icmp_code(to_port);
if (from_type != 0 || to_type != 0xFF)
{
cond = append_condition(conds, count);
cond->fieldKey = FWPM_CONDITION_ICMP_TYPE;
range2cond(cond, from_type, to_type);
}
if (from_code != 0 || to_code != 0xFF)
{
cond = append_condition(conds, count);
cond->fieldKey = FWPM_CONDITION_ICMP_CODE;
range2cond(cond, from_code, to_code);
}
}
}
else if (from_port != 0 || to_port != 0xFFFF)
{
cond = append_condition(conds, count);
if (local)
{
cond->fieldKey = FWPM_CONDITION_IP_LOCAL_PORT;
}
else
{
cond->fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
}
range2cond(cond, from_port, to_port);
}
return TRUE;
}
/**
* Free memory associated to a single condition
*/
static void free_condition(FWP_DATA_TYPE type, void *value)
{
FWP_RANGE0 *range;
switch (type)
{
case FWP_BYTE_ARRAY16_TYPE:
case FWP_V4_ADDR_MASK:
case FWP_V6_ADDR_MASK:
free(value);
break;
case FWP_RANGE_TYPE:
range = value;
free_condition(range->valueLow.type, range->valueLow.sd);
free_condition(range->valueHigh.type, range->valueHigh.sd);
free(range);
break;
default:
break;
}
}
/**
* Free memory used by a set of conditions
*/
static void free_conditions(FWPM_FILTER_CONDITION0 *conds, int count)
{
int i;
for (i = 0; i < count; i++)
{
free_condition(conds[i].conditionValue.type, conds[i].conditionValue.sd);
}
free(conds);
}
/**
* Install transport mode SP to the kernel
*/
static bool install_transport_sp(private_kernel_wfp_ipsec_t *this,
entry_t *entry, bool inbound)
{
FWPM_FILTER_CONDITION0 *conds = NULL;
int count = 0;
enumerator_t *enumerator;
traffic_selector_t *local, *remote;
sp_entry_t *sp;
DWORD res;
FWPM_FILTER0 filter = {
.displayData = {
.name = L"charon IPsec transport",
},
.action = {
.type = FWP_ACTION_CALLOUT_TERMINATING,
.calloutKey = inbound ? FWPM_CALLOUT_IPSEC_INBOUND_TRANSPORT_V4 :
FWPM_CALLOUT_IPSEC_OUTBOUND_TRANSPORT_V4,
},
.layerKey = inbound ? FWPM_LAYER_INBOUND_TRANSPORT_V4 :
FWPM_LAYER_OUTBOUND_TRANSPORT_V4,
};
enumerator = array_create_enumerator(entry->sps);
while (enumerator->enumerate(enumerator, &sp))
{
if (inbound)
{
if (sp->direction != POLICY_IN)
{
continue;
}
local = sp->dst;
remote = sp->src;
}
else
{
if (sp->direction != POLICY_OUT)
{
continue;
}
local = sp->src;
remote = sp->dst;
}
if (!ts2condition(local, TRUE, &conds, &count) ||
!ts2condition(remote, FALSE, &conds, &count))
{
free_conditions(conds, count);
enumerator->destroy(enumerator);
return FALSE;
}
}
enumerator->destroy(enumerator);
filter.numFilterConditions = count;
filter.filterCondition = conds;
if (inbound)
{
res = FwpmFilterAdd0(this->handle, &filter, NULL, &entry->policy_in);
}
else
{
res = FwpmFilterAdd0(this->handle, &filter, NULL, &entry->policy_out);
}
free_conditions(conds, count);
if (res != ERROR_SUCCESS)
{
DBG1(DBG_KNL, "installing inbound FWP filter failed: 0x%08x", res);
return FALSE;
}
return TRUE;
}
/**
* Convert a chunk_t to a WFP FWP_BYTE_BLOB
*/
static inline FWP_BYTE_BLOB chunk2blob(chunk_t chunk)
{
return (FWP_BYTE_BLOB){
.size = chunk.len,
.data = chunk.ptr,
};
}
/**
* Convert an integrity_algorithm_t to a WFP IPSEC_AUTH_TRANFORM_ID0
*/
static bool alg2auth(integrity_algorithm_t alg,
IPSEC_SA_AUTH_INFORMATION0 *info)
{
struct {
integrity_algorithm_t alg;
IPSEC_AUTH_TRANSFORM_ID0 transform;
} map[] = {
{ AUTH_HMAC_MD5_96, IPSEC_AUTH_TRANSFORM_ID_HMAC_MD5_96 },
{ AUTH_HMAC_SHA1_96, IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96 },
{ AUTH_HMAC_SHA2_256_128, IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_256_128},
{ AUTH_AES_128_GMAC, IPSEC_AUTH_TRANSFORM_ID_GCM_AES_128 },
{ AUTH_AES_192_GMAC, IPSEC_AUTH_TRANSFORM_ID_GCM_AES_192 },
{ AUTH_AES_256_GMAC, IPSEC_AUTH_TRANSFORM_ID_GCM_AES_256 },
};
int i;
for (i = 0; i < countof(map); i++)
{
if (map[i].alg == alg)
{
info->authTransform.authTransformId = map[i].transform;
return TRUE;
}
}
return FALSE;
}
/**
* Convert an encryption_algorithm_t to a WFP IPSEC_CIPHER_TRANFORM_ID0
*/
static bool alg2cipher(encryption_algorithm_t alg, int keylen,
IPSEC_SA_CIPHER_INFORMATION0 *info)
{
struct {
encryption_algorithm_t alg;
int keylen;
IPSEC_CIPHER_TRANSFORM_ID0 transform;
} map[] = {
{ ENCR_DES, 8, IPSEC_CIPHER_TRANSFORM_ID_CBC_DES },
{ ENCR_3DES, 24, IPSEC_CIPHER_TRANSFORM_ID_CBC_3DES },
{ ENCR_AES_CBC, 16, IPSEC_CIPHER_TRANSFORM_ID_AES_128 },
{ ENCR_AES_CBC, 24, IPSEC_CIPHER_TRANSFORM_ID_AES_192 },
{ ENCR_AES_CBC, 32, IPSEC_CIPHER_TRANSFORM_ID_AES_256 },
{ ENCR_AES_GCM_ICV16, 20, IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_128 },
{ ENCR_AES_GCM_ICV16, 28, IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_192 },
{ ENCR_AES_GCM_ICV16, 36, IPSEC_CIPHER_TRANSFORM_ID_GCM_AES_256 },
};
int i;
for (i = 0; i < countof(map); i++)
{
if (map[i].alg == alg && map[i].keylen == keylen)
{
info->cipherTransform.cipherTransformId = map[i].transform;
return TRUE;
}
}
return FALSE;
}
/**
* Get the integrity algorithm used for an AEAD transform
*/
static integrity_algorithm_t encr2integ(encryption_algorithm_t encr, int keylen)
{
struct {
encryption_algorithm_t encr;
int keylen;
integrity_algorithm_t integ;
} map[] = {
{ ENCR_NULL_AUTH_AES_GMAC, 20, AUTH_AES_128_GMAC },
{ ENCR_NULL_AUTH_AES_GMAC, 28, AUTH_AES_192_GMAC },
{ ENCR_NULL_AUTH_AES_GMAC, 36, AUTH_AES_256_GMAC },
{ ENCR_AES_GCM_ICV16, 20, AUTH_AES_128_GMAC },
{ ENCR_AES_GCM_ICV16, 28, AUTH_AES_192_GMAC },
{ ENCR_AES_GCM_ICV16, 36, AUTH_AES_256_GMAC },
};
int i;
for (i = 0; i < countof(map); i++)
{
if (map[i].encr == encr && map[i].keylen == keylen)
{
return map[i].integ;
}
}
return AUTH_UNDEFINED;
}
/**
* Install a single transport mode SA
*/
static bool install_transport_sa(private_kernel_wfp_ipsec_t *this,
entry_t *entry, sa_entry_t *sa, FWP_IP_VERSION version)
{
IPSEC_SA_AUTH_AND_CIPHER_INFORMATION0 info = {};
IPSEC_SA0 ipsec = {
.spi = ntohl(sa->spi),
};
IPSEC_SA_BUNDLE0 bundle = {
.saList = &ipsec,
.numSAs = 1,
.ipVersion = version,
};
struct {
u_int16_t alg;
chunk_t key;
} integ = {}, encr = {};
DWORD res;
switch (entry->protocol)
{
case IPPROTO_AH:
ipsec.saTransformType = IPSEC_TRANSFORM_AH;
ipsec.ahInformation = &info.saAuthInformation;
integ.key = sa->integ.key;
integ.alg = sa->integ.alg;
break;
case IPPROTO_ESP:
if (sa->encr.alg == ENCR_NULL ||
sa->encr.alg == ENCR_NULL_AUTH_AES_GMAC)
{
ipsec.saTransformType = IPSEC_TRANSFORM_ESP_AUTH;
ipsec.espAuthInformation = &info.saAuthInformation;
}
else
{
ipsec.saTransformType = IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER;
ipsec.espAuthAndCipherInformation = &info;
encr.key = sa->encr.key;
encr.alg = sa->encr.alg;
}
if (encryption_algorithm_is_aead(sa->encr.alg))
{
integ.alg = encr2integ(sa->encr.alg, sa->encr.key.len);
integ.key = sa->encr.key;
}
else
{
integ.alg = sa->integ.alg;
integ.key = sa->integ.key;
}
break;
default:
return FALSE;
}
if (integ.alg)
{
info.saAuthInformation.authKey = chunk2blob(integ.key);
if (!alg2auth(integ.alg, &info.saAuthInformation))
{
DBG1(DBG_KNL, "integrity algorithm %N not supported by WFP",
integrity_algorithm_names, integ.alg);
return FALSE;
}
}
if (encr.alg)
{
info.saCipherInformation.cipherKey = chunk2blob(encr.key);
if (!alg2cipher(encr.alg, encr.key.len, &info.saCipherInformation))
{
DBG1(DBG_KNL, "encryption algorithm %N not supported by WFP",
encryption_algorithm_names, encr.alg);
return FALSE;
}
}
if (sa->inbound)
{
res = IPsecSaContextAddInbound0(this->handle, entry->sa_id, &bundle);
}
else
{
res = IPsecSaContextAddOutbound0(this->handle, entry->sa_id, &bundle);
}
if (res != ERROR_SUCCESS)
{
DBG1(DBG_KNL, "adding %sbound WFP SA failed: 0x%08x",
sa->inbound ? "in" : "out", res);
return FALSE;
}
return TRUE;
}
/**
* Install transport mode SAs to the kernel
*/
static bool install_transport_sas(private_kernel_wfp_ipsec_t *this,
entry_t *entry)
{
IPSEC_TRAFFIC0 traffic = {
.trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT,
};
IPSEC_GETSPI1 spi = {
.inboundIpsecTraffic = {
.trafficType = IPSEC_TRAFFIC_TYPE_TRANSPORT,
.ipsecFilterId = entry->policy_in,
},
};
sa_entry_t *sa;
IPSEC_SA_SPI inbound_spi = 0;
enumerator_t *enumerator;
DWORD res;
switch (entry->local->get_family(entry->local))
{
case AF_INET:
traffic.ipVersion = FWP_IP_VERSION_V4;
traffic.localV4Address =
untoh32(entry->local->get_address(entry->local).ptr);
traffic.remoteV4Address =
untoh32(entry->remote->get_address(entry->remote).ptr);
break;
case AF_INET6:
traffic.ipVersion = FWP_IP_VERSION_V6;
memcpy(&traffic.localV6Address,
entry->local->get_address(entry->local).ptr, 16);
memcpy(&traffic.remoteV6Address,
entry->remote->get_address(entry->remote).ptr, 16);
break;
default:
return FALSE;
}
traffic.ipsecFilterId = entry->policy_out;
res = IPsecSaContextCreate0(this->handle, &traffic, NULL, &entry->sa_id);
if (res != ERROR_SUCCESS)
{
DBG1(DBG_KNL, "creating WFP SA context failed: 0x%08x", res);
return FALSE;
}
enumerator = array_create_enumerator(entry->sas);
while (enumerator->enumerate(enumerator, &sa))
{
if (sa->inbound)
{
inbound_spi = ntohl(sa->spi);
break;
}
}
enumerator->destroy(enumerator);
if (!inbound_spi)
{
return FALSE;
}
memcpy(spi.inboundIpsecTraffic.localV6Address, traffic.localV6Address,
sizeof(traffic.localV6Address));
memcpy(spi.inboundIpsecTraffic.remoteV6Address, traffic.remoteV6Address,
sizeof(traffic.remoteV6Address));
spi.ipVersion = traffic.ipVersion;
res = IPsecSaContextSetSpi0(this->handle, entry->sa_id, &spi, inbound_spi);
if (res != ERROR_SUCCESS)
{
DBG1(DBG_KNL, "setting WFP SA SPI failed: 0x%08x", res);
IPsecSaContextDeleteById0(this->handle, entry->sa_id);
entry->sa_id = 0;
return FALSE;
}
enumerator = array_create_enumerator(entry->sas);
while (enumerator->enumerate(enumerator, &sa))
{
if (!install_transport_sa(this, entry, sa, spi.ipVersion))
{
enumerator->destroy(enumerator);
IPsecSaContextDeleteById0(this->handle, entry->sa_id);
entry->sa_id = 0;
return FALSE;
}
}
enumerator->destroy(enumerator);
return TRUE;
}
/**
* Install a transport mode SA/SP set to the kernel
*/
static bool install_transport(private_kernel_wfp_ipsec_t *this, entry_t *entry)
{
if (install_transport_sp(this, entry, TRUE) &&
install_transport_sp(this, entry, FALSE) &&
install_transport_sas(this, entry))
{
return TRUE;
}
if (entry->policy_in)
{
FwpmFilterDeleteById0(this->handle, entry->policy_in);
entry->policy_in = 0;
}
if (entry->policy_out)
{
FwpmFilterDeleteById0(this->handle, entry->policy_out);
entry->policy_out = 0;
}
return FALSE;
}
/**
* Install a SA/SP set to the kernel
*/
static bool install(private_kernel_wfp_ipsec_t *this, entry_t *entry)
{
switch (entry->mode)
{
case MODE_TRANSPORT:
return install_transport(this, entry);
case MODE_TUNNEL:
case MODE_BEET:
default:
return FALSE;
}
}
METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
private_kernel_wfp_ipsec_t *this)
{
@ -352,7 +1030,7 @@ METHOD(kernel_ipsec_t, del_sa, status_t,
(void*)(uintptr_t)entry->reqid);
if (entry)
{
entry_destroy(entry);
entry_destroy(this, entry);
}
}
}
@ -407,6 +1085,13 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
.direction = direction,
);
array_insert(entry->sps, -1, sp);
if (array_count(entry->sps) > 1)
{
if (!install(this, entry))
{
status = FAILED;
}
}
}
else
{
@ -464,7 +1149,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
(void*)(uintptr_t)reqid);
if (entry)
{
entry_destroy(entry);
entry_destroy(this, entry);
}
}
}
@ -539,6 +1224,7 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create()
.destroy = _destroy,
},
},
.nextspi = htonl(0xc0000001),
.mutex = mutex_create(MUTEX_TYPE_DEFAULT),
.entries = hashtable_create(hashtable_hash_ptr,
hashtable_equals_ptr, 4),