]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
kernel-wfp: Preliminary support for transport mode connections
authorMartin Willi <martin@revosec.ch>
Mon, 18 Nov 2013 11:57:36 +0000 (12:57 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 4 Jun 2014 14:32:06 +0000 (16:32 +0200)
src/libcharon/plugins/kernel_wfp/kernel_wfp_compat.c
src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c

index 1b20d8990c11e2b85a87a4dba0677bdbdd052f6a..a687c43711c910bd8b02732712fec73182e1beaf 100644 (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 }
 };
index caf955d559119e68d02718229620d3f3bfe71f38..0910efc9b9e999d0d494314270bc1fcc50438d83 100644 (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),