Using Tunnel Mode
The following sample code demonstrates how to configure a point-to-point IPsec tunnel using the Windows Filtering Platform (WFP) API.
#include <winsock2.h>
#include <ws2ipdef.h>
#include <mstcpip.h>
#include <fwpmu.h>
#include <stdio.h>
#pragma comment(lib, "Fwpuclnt.lib")
#pragma comment(lib, "ws2_32.lib")
#define EXIT_ON_ERROR(fnName) \
if (result != ERROR_SUCCESS) \
{ \
printf(#fnName " = 0x%08X\n", result); \
goto CLEANUP; \
}
// 5fb216a8-e2e8-4024-b853-391a4168641e
const GUID PROVIDER_KEY =
{
0x5fb216a8,
0xe2e8,
0x4024,
{ 0xb8, 0x53, 0x39, 0x1a, 0x41, 0x68, 0x64, 0x1e }
};
DWORD ConfigureIPsecTunnelMode(
__in HANDLE engine,
__in PCWSTR policyName,
__in_opt const GUID* providerKey,
__in const SOCKADDR* localAddr,
__in const SOCKADDR* remoteAddr,
__in const FWP_BYTE_BLOB* presharedKey
)
{
DWORD result = ERROR_SUCCESS;
FWPM_FILTER_CONDITION0 filterConditions[2];
IPSEC_TUNNEL_ENDPOINTS0 endpoints;
IKEEXT_AUTHENTICATION_METHOD0 mmAuthMethods[1];
IKEEXT_PROPOSAL0 mmProposals[1];
IKEEXT_POLICY0 mmPolicy;
FWPM_PROVIDER_CONTEXT0 mmProvCtxt;
IPSEC_AUTH_AND_CIPHER_TRANSFORM0 qmTransform00;
const IPSEC_SA_LIFETIME0 qmLifetime =
{
3600, // lifetimeSeconds
100000, // lifetimeKilobytes
0x7FFFFFFF // lifetimePackets
};
IPSEC_SA_TRANSFORM0 qmTransforms0[1];
IPSEC_PROPOSAL0 qmProposals[1];
IPSEC_TUNNEL_POLICY0 qmPolicy;
FWPM_PROVIDER_CONTEXT0 qmProvCtxt;
// Fill in the version-independent fields in the filter conditions.
memset(filterConditions, 0, sizeof(filterConditions));
filterConditions[0].fieldKey = FWPM_CONDITION_IP_LOCAL_ADDRESS;
filterConditions[0].matchType = FWP_MATCH_EQUAL;
filterConditions[1].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
filterConditions[1].matchType = FWP_MATCH_EQUAL;
// Do the version-dependent processing of the tunnel endpoints.
if (localAddr->sa_family == AF_INET)
{
endpoints.ipVersion = FWP_IP_VERSION_V4;
endpoints.localV4Address = ntohl(*(ULONG*)INETADDR_ADDRESS(localAddr));
endpoints.remoteV4Address = ntohl(*(ULONG*)INETADDR_ADDRESS(remoteAddr));
// For a point-to-point tunnel, the filter conditions are the same as the
// tunnel endpoints. If this were a site-to-site tunnel, these would be
// the local and remote subnets instead.
filterConditions[0].conditionValue.type = FWP_UINT32;
filterConditions[0].conditionValue.uint32 = endpoints.localV4Address;
filterConditions[1].conditionValue.type = FWP_UINT32;
filterConditions[1].conditionValue.uint32 = endpoints.remoteV4Address;
}
else
{
endpoints.ipVersion = FWP_IP_VERSION_V6;
memcpy(
endpoints.localV6Address,
INETADDR_ADDRESS(localAddr),
sizeof(endpoints.localV6Address)
);
memcpy(
endpoints.remoteV6Address,
INETADDR_ADDRESS(remoteAddr),
sizeof(endpoints.remoteV6Address)
);
filterConditions[0].conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
filterConditions[0].conditionValue.byteArray16 =
(FWP_BYTE_ARRAY16*)(endpoints.localV6Address);
filterConditions[1].conditionValue.type = FWP_BYTE_ARRAY16_TYPE;
filterConditions[1].conditionValue.byteArray16 =
(FWP_BYTE_ARRAY16*)(endpoints.remoteV6Address);
}
// Main mode authentication uses a pre-shared key.
memset(mmAuthMethods, 0, sizeof(mmAuthMethods));
mmAuthMethods[0].authenticationMethodType = IKEEXT_PRESHARED_KEY;
mmAuthMethods[0].presharedKeyAuthentication.presharedKey = *presharedKey;
// There is a single main mode proposal: AES-128/SHA-1 with 8 hour lifetime.
memset(mmProposals, 0, sizeof(mmProposals));
mmProposals[0].cipherAlgorithm.algoIdentifier = IKEEXT_CIPHER_AES_128;
mmProposals[0].integrityAlgorithm.algoIdentifier = IKEEXT_INTEGRITY_SHA1;
mmProposals[0].maxLifetimeSeconds = 8 * 60 * 60;
mmProposals[0].dhGroup = IKEEXT_DH_GROUP_2;
memset(&mmPolicy, 0, sizeof(mmPolicy));
mmPolicy.numAuthenticationMethods = ARRAYSIZE(mmAuthMethods);
mmPolicy.authenticationMethods = mmAuthMethods;
mmPolicy.numIkeProposals = ARRAYSIZE(mmProposals);
mmPolicy.ikeProposals = mmProposals;
memset(&mmProvCtxt, 0, sizeof(mmProvCtxt));
// For MUI compatibility, object names should be indirect strings.
// See SHLoadIndirectString for details.
mmProvCtxt.displayData.name = (PWSTR)policyName;
// Link all our objects to our provider. When multiple providers are
// installed on a computer, this makes it easy to determine who added what.
mmProvCtxt.providerKey = (GUID*)providerKey;
mmProvCtxt.type = FWPM_IPSEC_IKE_MM_CONTEXT;
mmProvCtxt.authIpMmPolicy = &mmPolicy;
// For quick mode use ESP authentication and cipher.
memset(&qmTransform00, 0, sizeof(qmTransform00));
qmTransform00.authTransform.authTransformId =
IPSEC_AUTH_TRANSFORM_ID_HMAC_SHA_1_96;
qmTransform00.cipherTransform.cipherTransformId =
IPSEC_CIPHER_TRANSFORM_ID_AES_128;
memset(qmTransforms0, 0, sizeof(qmTransforms0));
qmTransforms0[0].ipsecTransformType = IPSEC_TRANSFORM_ESP_AUTH_AND_CIPHER;
qmTransforms0[0].espAuthAndCipherTransform = &qmTransform00;
memset(qmProposals, 0, sizeof(qmProposals));
qmProposals[0].lifetime = qmLifetime;
qmProposals[0].numSaTransforms = ARRAYSIZE(qmTransforms0);
qmProposals[0].saTransforms = qmTransforms0;
memset(&qmPolicy, 0, sizeof(qmPolicy));
qmPolicy.numIpsecProposals = ARRAYSIZE(qmProposals);
qmPolicy.ipsecProposals = qmProposals;
qmPolicy.tunnelEndpoints = endpoints;
qmPolicy.saIdleTimeout.idleTimeoutSeconds = 300;
qmPolicy.saIdleTimeout.idleTimeoutSecondsFailOver = 60;
memset(&qmProvCtxt, 0, sizeof(qmProvCtxt));
qmProvCtxt.displayData.name = (PWSTR)policyName;
qmProvCtxt.providerKey = (GUID*)providerKey;
qmProvCtxt.type = FWPM_IPSEC_IKE_QM_TUNNEL_CONTEXT;
qmProvCtxt.ikeQmTunnelPolicy = &qmPolicy;
result = FwpmIPsecTunnelAdd0(
engine,
FWPM_TUNNEL_FLAG_POINT_TO_POINT,
&mmProvCtxt,
&qmProvCtxt,
ARRAYSIZE(filterConditions),
filterConditions,
NULL
);
EXIT_ON_ERROR(FwpmIPsecTunnelAdd0);
CLEANUP:
return result;
}