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