windows-nt/Source/XPSP1/NT/net/tcpip/driver/ipsec/sys/saapi.c
2020-09-26 16:20:57 +08:00

4218 lines
110 KiB
C

/*++
Copyright (c) 1997-2001 Microsoft Corporation
Module Name:
saapi.c
Abstract:
This module contains the SAAPI implementation
Author:
Sanjay Anand (SanjayAn) 12-May-1997
ChunYe
Environment:
Kernel mode
Revision History:
--*/
#include "precomp.h"
#pragma hdrstop
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, IPSecInitRandom)
#endif
BOOLEAN
IPSecInitRandom(
VOID
)
/*++
Routine Description:
Initialize the IPSecRngKey by calling into ksecdd to get 2048 bits of random
and create the RC4 key.
Arguments:
Called at PASSIVE level.
Return Value:
TRUE/FALSE
--*/
{
UCHAR pBuf[RNG_KEY_SIZE];
PAGED_CODE();
if (IPSEC_GEN_RANDOM(pBuf, RNG_KEY_SIZE) == FALSE) {
IPSEC_DEBUG(LOAD, ("IPSEC_GEN_RANDOM failure.\n"));
return FALSE;
}
//
// Generate the key control structure.
//
IPSEC_RC4_KEY(&IPSecRngKey, RNG_KEY_SIZE, pBuf);
return TRUE;
}
VOID
IPSecRngRekey(
IN PVOID Context
)
/*++
Routine Description:
Initialize the IPSecRngKey by calling into ksecdd to get 2048 bits of random
and create the RC4 key.
Arguments:
Called at PASSIVE level.
Return Value:
None.
--*/
{
IPSecInitRandom();
IPSEC_DECREMENT(g_ipsec.NumWorkers);
#if DBG
IPSecRngInRekey = 0;
#endif
IPSEC_SET_VALUE(IPSecRngBytes, 0);
}
BOOLEAN
IPSecGenerateRandom(
IN PUCHAR pBuf,
IN ULONG BytesNeeded
)
/*++
Routine Description:
Generate a positive pseudo-random number between Lower and Upper bounds;
simple linear congruential algorithm. ANSI C "rand()" function. Courtesy JameelH.
Arguments:
LowerBound, UpperBound - range of random number.
Return Value:
a random number.
--*/
{
ULONG RngBytes;
IPSEC_RC4(&IPSecRngKey, BytesNeeded, pBuf);
//
// Rekey if we have exceeded the threshold.
//
RngBytes = IPSEC_ADD_VALUE(IPSecRngBytes, BytesNeeded);
if (RngBytes <= RNG_REKEY_THRESHOLD &&
(RngBytes + BytesNeeded) > RNG_REKEY_THRESHOLD) {
//
// Create a worker thread to perform the rekey since it has to be done
// as paged code.
//
#if DBG
ASSERT(IPSecRngInRekey == 0);
IPSecRngInRekey = 1;
#endif
ExInitializeWorkItem( &IPSecRngQueueItem,
IPSecRngRekey,
NULL);
ExQueueWorkItem(&IPSecRngQueueItem, DelayedWorkQueue);
IPSEC_INCREMENT(g_ipsec.NumWorkers);
}
return TRUE;
}
VOID
IPSecCleanupOutboundSA(
IN PSA_TABLE_ENTRY pInboundSA,
IN PSA_TABLE_ENTRY pOutboundSA,
IN BOOLEAN fNoDelete
)
/*++
Routine Description:
Deletes an outbound SA.
Called with SADB lock held, returns with it.
Arguments:
Return Value:
The final status from the operation.
--*/
{
KIRQL kIrql;
IPSEC_DEBUG(ACQUIRE, ("Deleting assoc (outbound) SA: %lx\n", pOutboundSA));
pInboundSA->sa_AssociatedSA = NULL;
//
// de-link from the Filter lists
//
if (pOutboundSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
pOutboundSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&pOutboundSA->sa_FilterLinkage);
}
//
// So, we dont delete the Rekeyoriginal SA again.
//
if (pOutboundSA->sa_Flags & FLAGS_SA_REKEY_ORI) {
pOutboundSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI;
if (pOutboundSA->sa_RekeyLarvalSA) {
ASSERT(pOutboundSA->sa_RekeyLarvalSA->sa_Flags & FLAGS_SA_REKEY);
pOutboundSA->sa_RekeyLarvalSA->sa_RekeyOriginalSA = NULL;
}
}
//
// invalidate the associated cache entry
//
IPSecInvalidateSACacheEntry(pOutboundSA);
pOutboundSA->sa_State = STATE_SA_ZOMBIE;
pOutboundSA->sa_AssociatedSA = NULL;
if (pOutboundSA->sa_Flags & FLAGS_SA_HW_PLUMBED) {
IPSecDelHWSAAtDpc(pOutboundSA);
}
IPSEC_DEBUG(REF, ("Out Deref IPSecCleanupOutboundSA\n"));
IPSecStopTimerDerefSA(pOutboundSA);
IPSEC_INC_STATISTIC(dwNumKeyDeletions);
}
VOID
IPSecCleanupLarvalSA(
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Delete the LarvalSA.
Called with Outbound Lock held, returns with it.
Arguments:
Return Value:
The final status from the operation.
--*/
{
PSA_TABLE_ENTRY pOutboundSA;
KIRQL kIrql1;
KIRQL kIrql2;
//
// Also remove from Pending list if queued there.
//
ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &kIrql1);
if (pSA->sa_Flags & FLAGS_SA_PENDING) {
ASSERT(pSA->sa_State == STATE_SA_LARVAL);
IPSEC_DEBUG(ACQUIRE, ("IPSecSAExpired: Removed from pending too: %lx\n", pSA));
IPSecRemoveEntryList(&pSA->sa_PendingLinkage);
pSA->sa_Flags &= ~FLAGS_SA_PENDING;
}
RELEASE_LOCK(&g_ipsec.AcquireInfo.Lock, kIrql1);
//
// Flush all the queued packets
//
IPSecFlushQueuedPackets(pSA, STATUS_TIMEOUT);
//
// remove from inbound sa list
//
AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql1);
IPSecRemoveSPIEntry(pSA);
ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql1);
//
// invalidate the associated cache entry
//
ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql2);
if (pSA->sa_AcquireCtx) {
IPSecInvalidateHandle(pSA->sa_AcquireCtx);
pSA->sa_AcquireCtx = NULL;
}
RELEASE_LOCK(&pSA->sa_Lock, kIrql2);
IPSecInvalidateSACacheEntry(pSA);
if (pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
pSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&pSA->sa_FilterLinkage);
}
if (pSA->sa_RekeyOriginalSA) {
ASSERT(pSA->sa_Flags & FLAGS_SA_REKEY);
ASSERT(pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA == pSA);
ASSERT(pSA->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI);
pSA->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI;
pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL;
pSA->sa_RekeyOriginalSA = NULL;
}
if (pOutboundSA = pSA->sa_AssociatedSA) {
IPSEC_DEC_STATISTIC(dwNumActiveAssociations);
IPSEC_DEC_TUNNELS(pOutboundSA);
IPSEC_DECREMENT(g_ipsec.NumOutboundSAs);
IPSecCleanupOutboundSA(pSA, pOutboundSA, FALSE);
}
IPSEC_DEBUG(REF, ("In Deref DeleteLarvalSA\n"));
IPSecStopTimerDerefSA(pSA);
}
VOID
IPSecDeleteLarvalSA(
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Delete the LarvalSA.
Called with Outbound Lock held, returns with it.
Arguments:
Return Value:
The final status from the operation.
--*/
{
KIRQL kIrql;
ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0);
//
// Remove from larval list
//
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql);
IPSecRemoveEntryList(&pSA->sa_LarvalLinkage);
IPSEC_DEC_STATISTIC(dwNumPendingKeyOps);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
//
// Cleanup the rest of larval SA
//
IPSecCleanupLarvalSA(pSA);
}
VOID
IPSecDeleteInboundSA(
IN PSA_TABLE_ENTRY pInboundSA
)
/*++
Routine Description:
Deletes the corresponding outbound SA, and then deletes itself.
Called with Outbound Lock held, returns with it.
Arguments:
Return Value:
The final status from the operation.
--*/
{
PSA_TABLE_ENTRY pOutboundSA;
PSA_TABLE_ENTRY pSA;
KIRQL kIrql;
PLIST_ENTRY pEntry;
PFILTER pFilter;
ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &kIrql);
IPSecNotifySAExpiration(pInboundSA, NULL, kIrql, FALSE);
if (pOutboundSA = pInboundSA->sa_AssociatedSA) {
IPSEC_DEC_STATISTIC(dwNumActiveAssociations);
IPSEC_DEC_TUNNELS(pOutboundSA);
IPSEC_DECREMENT(g_ipsec.NumOutboundSAs);
IPSecCleanupOutboundSA(pInboundSA, pOutboundSA, FALSE);
}
IPSEC_DEBUG(ACQUIRE, ("Deleting inbound SA: %lx\n", pInboundSA));
//
// remove from inbound sa list
//
AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql);
IPSecRemoveSPIEntry(pInboundSA);
ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql);
//
// invalidate the associated cache entry
//
IPSecInvalidateSACacheEntry(pInboundSA);
//
// also remove from the filter list
//
if (pInboundSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
pInboundSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&pInboundSA->sa_FilterLinkage);
}
if (pInboundSA->sa_Flags & FLAGS_SA_HW_PLUMBED) {
IPSecDelHWSAAtDpc(pInboundSA);
}
ASSERT(pInboundSA->sa_AssociatedSA == NULL);
IPSEC_DEBUG(REF, ("In Deref DeleteInboundSA\n"));
IPSecStopTimerDerefSA(pInboundSA);
}
VOID
IPSecExpireInboundSA(
IN PSA_TABLE_ENTRY pInboundSA
)
/*++
Routine Description:
Deletes the corresponding outbound SA, and places itself (inbound) on timer
Queue for later.
NOTE: Called with SADB lock held.
Arguments:
Return Value:
The final status from the operation.
--*/
{
PSA_TABLE_ENTRY pOutboundSA;
KIRQL OldIrq;
KIRQL kIrql;
if (pInboundSA->sa_Flags & FLAGS_SA_HW_PLUMBED) {
IPSecDelHWSAAtDpc(pInboundSA);
}
ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &OldIrq);
IPSecNotifySAExpiration(pInboundSA, NULL, OldIrq, FALSE);
if (pOutboundSA = pInboundSA->sa_AssociatedSA) {
IPSEC_DEC_STATISTIC(dwNumActiveAssociations);
IPSEC_DEC_TUNNELS(pOutboundSA);
IPSEC_DECREMENT(g_ipsec.NumOutboundSAs);
IPSecCleanupOutboundSA(pInboundSA, pOutboundSA, TRUE);
}
IPSEC_DEBUG(ACQUIRE, ("Queueing inbound SA: %lx\n", pInboundSA));
//
// Place this on the timer Q so it gets cleared when the next interval hits.
//
ACQUIRE_LOCK(&pInboundSA->sa_Lock, &kIrql);
if (pInboundSA->sa_AcquireCtx) {
IPSecInvalidateHandle(pInboundSA->sa_AcquireCtx);
pInboundSA->sa_AcquireCtx = NULL;
}
IPSecStartSATimer( pInboundSA,
IPSecSAExpired,
IPSEC_INBOUND_KEEPALIVE_TIME);
RELEASE_LOCK(&pInboundSA->sa_Lock, kIrql);
}
NTSTATUS
IPSecCheckInboundSA(
IN PSA_STRUCT pSAStruct,
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Ensures that the SA being updated is actually the SA we initially
kicked off negotiation for.
Arguments:
pSAInfo - information about the SA
pSA - SA to be populated.
Return Value:
STATUS_PENDING if the buffer is to be held on to, the normal case.
Notes:
--*/
{
LARGE_INTEGER uliSrcDstAddr;
LARGE_INTEGER uliSrcDstMask;
LARGE_INTEGER uliProtoSrcDstPort;
PSECURITY_ASSOCIATION pSAInfo = &pSAStruct->SecAssoc[pSAStruct->NumSAs - 1];
IPSEC_BUILD_SRC_DEST_ADDR( uliSrcDstAddr,
pSAStruct->InstantiatedFilter.SrcAddr,
pSAStruct->InstantiatedFilter.DestAddr);
IPSEC_BUILD_SRC_DEST_MASK( uliSrcDstMask,
pSAStruct->InstantiatedFilter.SrcMask,
pSAStruct->InstantiatedFilter.DestMask);
IPSEC_BUILD_PROTO_PORT_LI( uliProtoSrcDstPort,
pSAStruct->InstantiatedFilter.Protocol,
pSAStruct->InstantiatedFilter.SrcPort,
pSAStruct->InstantiatedFilter.DestPort);
IPSEC_DEBUG(ACQUIRE, ("IPSecCheckInboundSA: S: %lx-%lx, D: %lx-%lx\n",
SRC_ADDR, SRC_MASK, DEST_ADDR, DEST_MASK));
IPSEC_DEBUG(ACQUIRE, ("IPSecCheckInboundSA: SA->S: %lx-%lx, SA->D: %lx-%lx\n",
pSA->SA_SRC_ADDR, pSA->SA_SRC_MASK, pSA->SA_DEST_ADDR, pSA->SA_DEST_MASK));
if ((pSA->sa_TunnelAddr != 0) || (pSA->sa_Flags & FLAGS_SA_TUNNEL)) {
if (((SRC_ADDR & pSA->SA_SRC_MASK) ==
(pSA->SA_SRC_ADDR & pSA->SA_SRC_MASK)) &&
((DEST_ADDR & pSA->SA_DEST_MASK) ==
(pSA->SA_DEST_ADDR & pSA->SA_DEST_MASK)) &&
(pSA->sa_SPI == pSAInfo->SPI)) {
return STATUS_SUCCESS;
}
} else {
if ((uliSrcDstAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) &&
(pSA->sa_SPI == pSAInfo->SPI)) {
return STATUS_SUCCESS;
}
}
return STATUS_FAIL_CHECK;
}
BOOLEAN
IPSecIsWeakDESKey(
IN PUCHAR Key
)
/*++
Routine Description:
Checks for weak DES keys
Arguments:
Key - the key to be checked.
Return Value:
TRUE/FALSE
Notes:
--*/
{
ULONG j;
for (j = 0; j < NUM_WEAK_KEYS; j++) {
if (IPSecEqualMemory(Key, weak_keys[j], DES_BLOCKLEN)) {
return TRUE;
}
}
return FALSE;
}
BOOLEAN
IPSecIsWeak3DESKey(
IN PUCHAR Key
)
/*++
Routine Description:
Checks for weak Triple DES keys
Arguments:
Key - the key to be checked.
Return Value:
TRUE/FALSE
Notes:
--*/
{
if (IPSecEqualMemory(Key, Key + DES_BLOCKLEN, DES_BLOCKLEN) ||
IPSecEqualMemory(Key + DES_BLOCKLEN, Key + 2 * DES_BLOCKLEN, DES_BLOCKLEN)) {
return TRUE;
}
return FALSE;
}
NTSTATUS
IPSecPopulateSA(
IN PSA_STRUCT pSAStruct,
IN ULONG KeyLen,
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Populates an SA with info passed in the SECURITY_ASSOCIATION block
Arguments:
pSAInfo - information about the SA
KeyLen - the length of the composite key (we do the slicing/dicing here)
pSA - SA to be populated.
Return Value:
STATUS_PENDING if the buffer is to be held on to, the normal case.
Notes:
--*/
{
PSECURITY_ASSOCIATION pSAInfo = &pSAStruct->SecAssoc[0];
ULONG Index;
ULONG len = 0;
IPSEC_BUILD_SRC_DEST_ADDR( pSA->sa_uliSrcDstAddr,
pSAStruct->InstantiatedFilter.SrcAddr,
pSAStruct->InstantiatedFilter.DestAddr);
IPSEC_BUILD_SRC_DEST_MASK( pSA->sa_uliSrcDstMask,
pSAStruct->InstantiatedFilter.SrcMask,
pSAStruct->InstantiatedFilter.DestMask);
IPSEC_BUILD_PROTO_PORT_LI( pSA->sa_uliProtoSrcDstPort,
pSAStruct->InstantiatedFilter.Protocol,
pSAStruct->InstantiatedFilter.SrcPort,
pSAStruct->InstantiatedFilter.DestPort);
if ((pSAStruct->NumSAs < 1) ||
(pSAStruct->NumSAs > MAX_SAS)) {
IPSEC_DEBUG(SAAPI, ("Invalid NumOps count: %d\n", pSAStruct->NumSAs));
return STATUS_INVALID_PARAMETER;
}
//
// If inbound SA, ensure that the last SPI is the one we returned.
//
if (!(pSA->sa_Flags & FLAGS_SA_OUTBOUND)) {
if (pSA->sa_SPI != pSAStruct->SecAssoc[pSAStruct->NumSAs - 1].SPI) {
IPSEC_DEBUG(SAAPI, ("SPI in invalid location: SPI: %lx, in loc: %lx\n",
pSA->sa_SPI,
pSAStruct->SecAssoc[pSAStruct->NumSAs - 1].SPI));
return STATUS_INVALID_PARAMETER;
}
}
if (pSAStruct->Flags & IPSEC_SA_TUNNEL) {
IPSEC_DEBUG(TUNNEL, ("SA %lx tunneled to %lx\n", pSA, pSAStruct->TunnelAddr));
pSA->sa_TunnelAddr = pSAStruct->TunnelAddr;
pSA->sa_Flags |= FLAGS_SA_TUNNEL;
}
if (pSAStruct->Flags & IPSEC_SA_DISABLE_IDLE_OUT) {
pSA->sa_Flags |= FLAGS_SA_DISABLE_IDLE_OUT;
}
if (pSAStruct->Flags & IPSEC_SA_DISABLE_ANTI_REPLAY_CHECK) {
pSA->sa_Flags |= FLAGS_SA_DISABLE_ANTI_REPLAY_CHECK;
}
if (pSAStruct->Flags & IPSEC_SA_DISABLE_LIFETIME_CHECK) {
pSA->sa_Flags |= FLAGS_SA_DISABLE_LIFETIME_CHECK;
}
pSA->sa_NumOps = pSAStruct->NumSAs;
pSA->sa_Lifetime = pSAStruct->Lifetime;
pSA->sa_TruncatedLen = TRUNCATED_HASH_LEN;
pSA->sa_ReplayLen = sizeof(ULONG);
pSA->sa_QMPFSGroup = pSAStruct->dwQMPFSGroup;
RtlCopyMemory( &pSA->sa_CookiePair,
&pSAStruct->CookiePair,
sizeof(IKE_COOKIE_PAIR));
for (Index = 0; Index < pSAStruct->NumSAs; Index++) {
pSAInfo = &pSAStruct->SecAssoc[Index];
pSA->sa_OtherSPIs[Index] = pSAInfo->SPI;
pSA->sa_Operation[Index] = pSAInfo->Operation;
pSA->sa_ReplaySendSeq[Index] = pSA->sa_ReplayStartPoint;
pSA->sa_ReplayLastSeq[Index] = pSA->sa_ReplayStartPoint + 1;
//
// Now parse the Algorithm info..
//
switch (pSA->sa_Operation[Index]) {
case None:
IPSEC_DEBUG(ACQUIRE, ("NULL operation.\n"));
if (pSA->sa_NumOps > 1) {
IPSEC_DEBUG(SAAPI, ("Invalid NumOps count; none specified, but more ops than 1\n"));
return STATUS_INVALID_PARAMETER;
}
break;
case Auth: {
pSA->INT_ALGO(Index) = pSAInfo->EXT_INT_ALGO;
if (pSA->INT_ALGO(Index) >= IPSEC_AH_MAX) {
IPSEC_DEBUG(SAAPI, ("Invalid int algo: %d %d\n", pSA->INT_ALGO(Index), IPSEC_AH_MAX));
return STATUS_INVALID_PARAMETER;
}
pSA->INT_KEYLEN(Index) = pSAInfo->EXT_INT_KEYLEN;
pSA->INT_ROUNDS(Index) = pSAInfo->EXT_INT_ROUNDS;
//
// Make sure the right key len was passed in
//
if (KeyLen > 0 && pSAInfo->EXT_INT_KEYLEN == (KeyLen - len)) {
IPSEC_DEBUG(SAAPI, ("Key len more than reserved, allocing new keys\n"));
if (!(pSA->INT_KEY(Index) = IPSecAllocateKeyBuffer(KeyLen))) {
return STATUS_INSUFFICIENT_RESOURCES;
}
RtlCopyMemory( pSA->INT_KEY(Index),
(UCHAR UNALIGNED *)(pSAStruct->KeyMat + len),
pSAInfo->EXT_INT_KEYLEN);
} else {
//
// bogus - reject
//
IPSEC_DEBUG(SAAPI, ("AH: Key len is bogus - extra bytes: %d, keylen in struct: %d.\n",
KeyLen-len,
pSAInfo->EXT_INT_KEYLEN));
return STATUS_INVALID_PARAMETER;
}
len = pSAInfo->EXT_INT_KEYLEN;
break;
}
case Encrypt: {
pSA->INT_ALGO(Index) = pSAInfo->EXT_INT_ALGO;
if (pSA->INT_ALGO(Index) >= IPSEC_AH_MAX) {
IPSEC_DEBUG(SAAPI, ("Invalid int algo: %d %d\n", pSA->INT_ALGO(Index), IPSEC_AH_MAX));
return STATUS_INVALID_PARAMETER;
}
if (pSA->INT_ALGO(Index) == IPSEC_AH_NONE) {
IPSEC_DEBUG(SAAPI, ("None Auth algo\n"));
//pSA->sa_TruncatedLen = 0;
}
pSA->INT_KEYLEN(Index) = pSAInfo->EXT_INT_KEYLEN;
pSA->INT_ROUNDS(Index) = pSAInfo->EXT_INT_ROUNDS;
pSA->CONF_ALGO(Index) = pSAInfo->EXT_CONF_ALGO;
if (pSA->CONF_ALGO(Index) >= IPSEC_ESP_MAX) {
IPSEC_DEBUG(SAAPI, ("Invalid conf algo: %d %d\n", pSA->CONF_ALGO(Index), IPSEC_ESP_MAX));
return STATUS_INVALID_PARAMETER;
}
if ((pSA->CONF_ALGO(Index) == IPSEC_ESP_DES) ||
(pSA->CONF_ALGO(Index) == IPSEC_ESP_3_DES) ||
(pSA->CONF_ALGO(Index) == IPSEC_ESP_NONE)) {
LARGE_INTEGER Li;
NdisGetCurrentSystemTime(&Li);
pSA->sa_ivlen = DES_BLOCKLEN;
*(UNALIGNED ULONG *)&pSA->sa_iv[Index][0] = Li.LowPart;
*(UNALIGNED ULONG *)&pSA->sa_iv[Index][4] = Li.HighPart;
IPSecGenerateRandom((PUCHAR)&pSA->sa_iv[Index][0], DES_BLOCKLEN);
IPSEC_DEBUG(SAAPI, ("IV: %lx-%lx\n", *(PULONG)&pSA->sa_iv[Index][0], *(PULONG)&pSA->sa_iv[Index][4]));
pSA->CONF_KEYLEN(Index) = pSAInfo->EXT_CONF_KEYLEN;
pSA->CONF_ROUNDS(Index) = pSAInfo->EXT_CONF_ROUNDS;
//
// Make sure the right key len was passed in
//
if ((KeyLen-len == pSAStruct->KeyLen) &&
(pSAInfo->EXT_INT_KEYLEN + pSAInfo->EXT_CONF_KEYLEN <= KeyLen-len)) {
//
// confKeyMatLen is the amount of conf key material that came down.
// this is the reduced (weakened) length for export.
// it is expanded to the real length later.
//
ULONG confKeyMatLen = pSAInfo->EXT_CONF_KEYLEN;
ULONG realConfKeyLen = 0;
realConfKeyLen = confKeyMatLen;
if (pSA->CONF_ALGO(Index) == IPSEC_ESP_DES) {
if (pSAInfo->EXT_CONF_KEYLEN != DES_BLOCKLEN) {
ASSERT(FALSE);
IPSEC_DEBUG(SAAPI, ("Bad DES key length: pSAInfo->EXT_CONF_KEYLEN: %lx, conf: %lx, DES_BLOCKLEN: %lx\n",
pSAInfo->EXT_CONF_KEYLEN, confKeyMatLen, DES_BLOCKLEN));
return STATUS_INVALID_PARAMETER;
}
} else if (pSA->CONF_ALGO(Index) == IPSEC_ESP_3_DES) {
if (pSAInfo->EXT_CONF_KEYLEN != 3 * DES_BLOCKLEN) {
ASSERT(FALSE);
IPSEC_DEBUG(SAAPI, ("Bad 3DES key length\n"));
return STATUS_INVALID_PARAMETER;
}
}
IPSEC_DEBUG(SAAPI, ("Key len more than reserved, allocing new keys\n"));
if (pSAInfo->EXT_INT_KEYLEN > 0 &&
!(pSA->INT_KEY(Index) = IPSecAllocateKeyBuffer(pSAInfo->EXT_INT_KEYLEN))) {
return STATUS_INSUFFICIENT_RESOURCES;
}
if (realConfKeyLen > 0 &&
!(pSA->CONF_KEY(Index) = IPSecAllocateKeyBuffer(realConfKeyLen))) {
if (pSA->INT_KEY(Index)) {
IPSecFreeKeyBuffer(pSA->INT_KEY(Index));
pSA->INT_KEY(Index) = NULL;
}
return STATUS_INSUFFICIENT_RESOURCES;
}
if (pSA->CONF_KEY(Index) && confKeyMatLen) {
RtlCopyMemory( pSA->CONF_KEY(Index),
pSAStruct->KeyMat,
confKeyMatLen);
if (confKeyMatLen < realConfKeyLen) {
if (pSA->INT_KEY(Index)) {
IPSecFreeKeyBuffer(pSA->INT_KEY(Index));
pSA->INT_KEY(Index) = NULL;
}
if (pSA->CONF_KEY(Index)) {
IPSecFreeKeyBuffer(pSA->CONF_KEY(Index));
pSA->CONF_KEY(Index) = NULL;
}
return STATUS_INVALID_PARAMETER;
}
if ((pSA->CONF_ALGO(Index) == IPSEC_ESP_DES &&
IPSecIsWeakDESKey(pSA->CONF_KEY(Index))) ||
(pSA->CONF_ALGO(Index) == IPSEC_ESP_3_DES &&
IPSecIsWeak3DESKey(pSA->CONF_KEY(Index)))) {
PSA_TABLE_ENTRY pLarvalSA;
IPSEC_DEBUG(SAAPI, ("Got a weak key!!: %lx\n", pSA->CONF_KEY(Index)));
//
// if initiator, re-start a new negotiation and throw away this one
//
if (pSA->sa_Flags & FLAGS_SA_INITIATOR) {
IPSecNegotiateSA( pSA->sa_Filter,
pSA->sa_uliSrcDstAddr,
pSA->sa_uliProtoSrcDstPort,
pSA->sa_NewMTU,
&pLarvalSA,
pSA->sa_DestType);
IPSecQueuePacket(pLarvalSA, pSA->sa_BlockedBuffer);
}
return STATUS_INVALID_PARAMETER;
}
} else {
if (pSA->CONF_ALGO(Index) != IPSEC_ESP_NONE) {
IPSEC_DEBUG(SAAPI, ("Algo: %lx with no keymat!!: %lx\n", pSA->CONF_ALGO(Index)));
ASSERT(FALSE);
return STATUS_INVALID_PARAMETER;
}
pSA->sa_ivlen = 0;
}
if (pSAInfo->EXT_INT_KEYLEN > 0) {
RtlCopyMemory( pSA->INT_KEY(Index),
(UCHAR UNALIGNED *)(pSAStruct->KeyMat + pSAInfo->EXT_CONF_KEYLEN),
pSAInfo->EXT_INT_KEYLEN);
}
len = pSAInfo->EXT_CONF_KEYLEN + pSAInfo->EXT_INT_KEYLEN;
} else {
//
// bogus - reject
//
IPSEC_DEBUG(SAAPI, ("ESP: Key len is bogus - extra bytes: %lx, keylen in struct: %lx.\n",
KeyLen-len,
pSAInfo->EXT_INT_KEYLEN + pSAInfo->EXT_CONF_KEYLEN));
return STATUS_INVALID_PARAMETER;
}
}
break;
}
default:
IPSEC_DEBUG(SAAPI, ("IPSecPopulateSA: Bad operation\n"));
break;
}
}
return STATUS_SUCCESS;
}
NTSTATUS
IPSecCreateSA(
OUT PSA_TABLE_ENTRY *ppSA
)
/*++
Routine Description:
Creates a Security Association block.
Arguments:
ppSA - returns the SA pointer
Return Value:
STATUS_PENDING if the buffer is to be held on to, the normal case.
Notes:
--*/
{
PSA_TABLE_ENTRY pSA;
IPSEC_DEBUG(SAAPI, ("Entering IPSecCreateSA\n"));
pSA = IPSecAllocateMemory(sizeof(SA_TABLE_ENTRY), IPSEC_TAG_SA);
if (!pSA) {
return STATUS_INSUFFICIENT_RESOURCES;
}
IPSecZeroMemory(pSA, sizeof(SA_TABLE_ENTRY));
pSA->sa_Signature = IPSEC_SA_SIGNATURE;
pSA->sa_NewMTU = MAX_LONG;
#if DBG
pSA->sa_d1 = IPSEC_SA_D_1;
pSA->sa_d2 = IPSEC_SA_D_2;
pSA->sa_d3 = IPSEC_SA_D_3;
pSA->sa_d4 = IPSEC_SA_D_4;
#endif
INIT_LOCK(&pSA->sa_Lock);
InitializeListHead(&pSA->sa_SPILinkage);
InitializeListHead(&pSA->sa_FilterLinkage);
InitializeListHead(&pSA->sa_LarvalLinkage);
InitializeListHead(&pSA->sa_PendingLinkage);
pSA->sa_Reference = 1;
pSA->sa_State = STATE_SA_CREATED;
pSA->sa_ExpiryTime = IPSEC_SA_EXPIRY_TIME;
*ppSA = pSA;
return STATUS_SUCCESS;
}
PSA_TABLE_ENTRY
IPSecLookupSABySPI(
IN tSPI SPI,
IN IPAddr DestAddr
)
/*++
Routine Description:
Looks up the SA given the SPI and Filter variables.
Arguments:
Return Value:
Notes:
--*/
{
KIRQL kIrql;
PSA_TABLE_ENTRY pSA;
AcquireReadLock(&g_ipsec.SPIListLock, &kIrql);
pSA = IPSecLookupSABySPIWithLock(SPI, DestAddr);
ReleaseReadLock(&g_ipsec.SPIListLock, kIrql);
return pSA;
}
PSA_TABLE_ENTRY
IPSecLookupSABySPIWithLock(
IN tSPI SPI,
IN IPAddr DestAddr
)
/*++
Routine Description:
Looks up the SA given the SPI and Filter variables.
NOTE: Always call with the SPIListLock held.
Arguments:
Return Value:
Notes:
--*/
{
PSA_HASH pHash;
PLIST_ENTRY pEntry;
PSA_TABLE_ENTRY pSA;
//
// get to hash bucket
//
IPSEC_HASH_SPI(DestAddr, SPI, pHash);
//
// search for specific entry in collision chain
//
for ( pEntry = pHash->SAList.Flink;
pEntry != &pHash->SAList;
pEntry = pEntry->Flink) {
pSA = CONTAINING_RECORD(pEntry,
SA_TABLE_ENTRY,
sa_SPILinkage);
if (pSA->sa_TunnelAddr) {
if ((DestAddr == pSA->sa_TunnelAddr) &&
(pSA->sa_SPI == SPI)) {
IPSEC_DEBUG(HASH, ("Matched Tunnel entry: %lx\n", pSA));
return pSA;
}
} else {
if ((DestAddr == pSA->SA_DEST_ADDR) &&
(pSA->sa_SPI == SPI)) {
IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pSA));
return pSA;
}
}
}
//
// no entry found
//
return NULL;
}
NTSTATUS
IPSecLookupSAByAddr(
IN ULARGE_INTEGER uliSrcDstAddr,
IN ULARGE_INTEGER uliProtoSrcDstPort,
OUT PFILTER *ppFilter,
OUT PSA_TABLE_ENTRY *ppSA,
OUT PSA_TABLE_ENTRY *ppNextSA,
OUT PSA_TABLE_ENTRY *ppTunnelSA,
IN BOOLEAN fOutbound,
IN BOOLEAN fFWPacket,
IN BOOLEAN fBypass
)
/*++
Routine Description:
Looks up the SA given the relevant addresses.
Arguments:
uliSrcDstAddr - src/dest IP addr
uliProtoSrcDstPort - protocol, src/dest port
ppFilter - filter found
ppSA - SA found
ppTunnelSA - tunnel SA found
fOutbound - direction flag
Return Value:
STATUS_SUCCESS - both filter and SA found
STATUS_UNSUCCESSFUL - none found
STATUS_PENDING - filter found, but no SA
Notes:
Called with SADBLock held.
--*/
{
PFILTER pFilter;
PLIST_ENTRY pEntry;
PLIST_ENTRY pFilterList;
PLIST_ENTRY pSAChain;
PSA_TABLE_ENTRY pSA;
REGISTER ULARGE_INTEGER uliPort;
REGISTER ULARGE_INTEGER uliAddr;
BOOLEAN fFound = FALSE;
*ppSA = NULL;
*ppFilter = NULL;
*ppTunnelSA = NULL;
//
// Search in Tunnel filters list
//
pFilterList = IPSecResolveFilterList(TRUE, fOutbound);
for ( pEntry = pFilterList->Flink;
pEntry != pFilterList;
pEntry = pEntry->Flink) {
pFilter = CONTAINING_RECORD(pEntry,
FILTER,
MaskedLinkage);
if (fBypass && IS_EXEMPT_FILTER(pFilter)) {
//
// Don't search block/pass-thru filters for host bypass traffic
//
continue;
}
uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart;
uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart;
if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) &&
(uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) {
IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter));
fFound = TRUE;
break;
}
}
if (fFound) {
fFound = FALSE;
//
// Search for the particular SA now.
//
pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR);
for ( pEntry = pSAChain->Flink;
pEntry != pSAChain;
pEntry = pEntry->Flink) {
pSA = CONTAINING_RECORD(pEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
ASSERT(pSA->sa_Flags & FLAGS_SA_TUNNEL);
if (pFilter->TunnelAddr != 0) {
//
// match the outbound flag also
//
IPSEC_DEBUG(HASH, ("Matched specific tunnel entry: %lx\n", pSA));
ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0));
fFound = TRUE;
*ppTunnelSA = pSA;
break;
}
}
if (fFound) {
fFound = FALSE;
*ppFilter = pFilter;
} else {
//
// Found a filter entry, but need to negotiate keys
//
*ppFilter = pFilter;
return STATUS_PENDING;
}
}
//
// Search in Masked filters list
//
pFilterList = IPSecResolveFilterList(FALSE, fOutbound);
for ( pEntry = pFilterList->Flink;
pEntry != pFilterList;
pEntry = pEntry->Flink) {
pFilter = CONTAINING_RECORD(pEntry,
FILTER,
MaskedLinkage);
if (fFWPacket && !IS_EXEMPT_FILTER(pFilter)) {
//
// Search only block/pass-thru filters in forward path
//
continue;
}
if (fBypass && IS_EXEMPT_FILTER(pFilter)) {
//
// Don't search block/pass-thru filters for host bypass traffic
//
continue;
}
uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart;
uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart;
if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) &&
(uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) {
IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter));
fFound = TRUE;
break;
}
}
if (fFound) {
//
// Search for the particular SA now.
//
fFound=FALSE;
pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR);
for ( pEntry = pSAChain->Flink;
pEntry != pSAChain;
pEntry = pEntry->Flink) {
pSA = CONTAINING_RECORD(pEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
if (IS_CLASSD(NET_LONG(pSA->SA_SRC_ADDR))
|| IS_CLASSD(NET_LONG(pSA->SA_DEST_ADDR))) {
uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pSA->sa_uliSrcDstMask.QuadPart;
IPSEC_DEBUG(HASH, ("MCAST: %d %d %d %d", uliAddr.LowPart, uliAddr.HighPart,
pSA->sa_uliSrcDstAddr.LowPart,pSA->sa_uliSrcDstAddr.HighPart));
if (uliAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) {
fFound=TRUE;
}
} else if (uliSrcDstAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) {
fFound=TRUE;
}
if (fFound) {
IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pSA));
ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0));
//
// if there is also a tunnel SA, associate it here.
//
if (*ppTunnelSA && fOutbound) {
*ppNextSA = *ppTunnelSA;
IPSEC_DEBUG(SAAPI, ("linked next sa: %lx, next: %lx", pSA, *ppTunnelSA));
*ppTunnelSA = NULL;
}
*ppFilter = pFilter;
*ppSA = pSA;
return STATUS_SUCCESS;
}
}
//
// Found a filter entry, but need to negotiate keys
//
// Also, ppTunnelSA is set to the proper tunnel SA we need
// to hook to this end-2-end SA once it is negotiated.
//
*ppFilter = pFilter;
return STATUS_PENDING;
} else {
//
// if only tunnel SA found, return that as the SA
// found.
//
if (*ppTunnelSA) {
*ppSA = *ppTunnelSA;
*ppTunnelSA = NULL;
return STATUS_SUCCESS;
}
}
//
// no entry found
//
return STATUS_NOT_FOUND;
}
NTSTATUS
IPSecLookupTunnelSA(
IN ULARGE_INTEGER uliSrcDstAddr,
IN ULARGE_INTEGER uliProtoSrcDstPort,
OUT PFILTER *ppFilter,
OUT PSA_TABLE_ENTRY *ppSA,
IN BOOLEAN fOutbound
)
/*++
Routine Description:
Looks up the SA given the relevant addresses.
Arguments:
uliSrcDstAddr - src/dest IP addr
uliProtoSrcDstPort - protocol, src/dest port
ppFilter - filter found
ppSA - SA found
fOutbound - direction flag
Return Value:
STATUS_SUCCESS - both filter and SA found
STATUS_UNSUCCESSFUL - none found
STATUS_PENDING - filter found, but no SA
Notes:
Called with SADBLock held.
--*/
{
PFILTER pFilter;
PLIST_ENTRY pEntry;
PLIST_ENTRY pFilterList;
PLIST_ENTRY pSAChain;
PSA_TABLE_ENTRY pSA;
REGISTER ULARGE_INTEGER uliPort;
REGISTER ULARGE_INTEGER uliAddr;
BOOLEAN fFound = FALSE;
*ppSA = NULL;
*ppFilter = NULL;
//
// Search in Tunnel filters list
//
pFilterList = IPSecResolveFilterList(TRUE, fOutbound);
for ( pEntry = pFilterList->Flink;
pEntry != pFilterList;
pEntry = pEntry->Flink) {
pFilter = CONTAINING_RECORD(pEntry,
FILTER,
MaskedLinkage);
uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart;
uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart;
if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) &&
(uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) {
IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter));
fFound = TRUE;
break;
}
}
if (fFound) {
//
// Search for the particular SA now.
//
pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR);
for ( pEntry = pSAChain->Flink;
pEntry != pSAChain;
pEntry = pEntry->Flink) {
pSA = CONTAINING_RECORD(pEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
ASSERT(pSA->sa_Flags & FLAGS_SA_TUNNEL);
if (pFilter->TunnelAddr != 0) {
//
// match the outbound flag also
//
IPSEC_DEBUG(HASH, ("Matched specific tunnel entry: %lx\n", pSA));
ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0));
*ppFilter = pFilter;
*ppSA = pSA;
return STATUS_SUCCESS;
}
}
//
// Found a filter entry, but need to negotiate keys
//
*ppFilter = pFilter;
return STATUS_PENDING;
}
//
// no entry found
//
return STATUS_NOT_FOUND;
}
NTSTATUS
IPSecLookupMaskedSA(
IN ULARGE_INTEGER uliSrcDstAddr,
IN ULARGE_INTEGER uliProtoSrcDstPort,
OUT PFILTER *ppFilter,
OUT PSA_TABLE_ENTRY *ppSA,
IN BOOLEAN fOutbound
)
/*++
Routine Description:
Looks up the SA given the relevant addresses.
Arguments:
uliSrcDstAddr - src/dest IP addr
uliProtoSrcDstPort - protocol, src/dest port
ppFilter - filter found
ppSA - SA found
fOutbound - direction flag
Return Value:
STATUS_SUCCESS - both filter and SA found
STATUS_UNSUCCESSFUL - none found
STATUS_PENDING - filter found, but no SA
Notes:
Called with SADBLock held.
--*/
{
PFILTER pFilter;
PLIST_ENTRY pEntry;
PLIST_ENTRY pFilterList;
PLIST_ENTRY pSAChain;
PSA_TABLE_ENTRY pSA;
REGISTER ULARGE_INTEGER uliPort;
REGISTER ULARGE_INTEGER uliAddr;
BOOLEAN fFound = FALSE;
*ppSA = NULL;
*ppFilter = NULL;
//
// Search in Masked filters list
//
pFilterList = IPSecResolveFilterList(FALSE, fOutbound);
for ( pEntry = pFilterList->Flink;
pEntry != pFilterList;
pEntry = pEntry->Flink) {
pFilter = CONTAINING_RECORD(pEntry,
FILTER,
MaskedLinkage);
uliAddr.QuadPart = uliSrcDstAddr.QuadPart & pFilter->uliSrcDstMask.QuadPart;
uliPort.QuadPart = uliProtoSrcDstPort.QuadPart & pFilter->uliProtoSrcDstMask.QuadPart;
if ((uliAddr.QuadPart == pFilter->uliSrcDstAddr.QuadPart) &&
(uliPort.QuadPart == pFilter->uliProtoSrcDstPort.QuadPart)) {
IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pFilter));
fFound = TRUE;
break;
}
}
if (fFound) {
//
// Search for the particular SA now.
//
pSAChain = IPSecResolveSAChain(pFilter, fOutbound? DEST_ADDR: SRC_ADDR);
for ( pEntry = pSAChain->Flink;
pEntry != pSAChain;
pEntry = pEntry->Flink) {
pSA = CONTAINING_RECORD(pEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
if (uliSrcDstAddr.QuadPart == pSA->sa_uliSrcDstAddr.QuadPart) {
IPSEC_DEBUG(HASH, ("Matched entry: %lx\n", pSA));
ASSERT(fOutbound == (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_OUTBOUND) != 0));
*ppFilter = pFilter;
*ppSA = pSA;
return STATUS_SUCCESS;
}
}
//
// Found a filter entry, but need to negotiate keys
//
*ppFilter = pFilter;
return STATUS_PENDING;
}
//
// no entry found
//
return STATUS_NOT_FOUND;
}
NTSTATUS
IPSecAllocateSPI(
OUT tSPI * pSpi,
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Allocates an SPI for an incoming SA - guards against collisions
Arguments:
pSpi - the SPI allocated is filled in here
pSA - SA for which SPI is needed
Return Value:
Notes:
--*/
{
ULONG rand;
ULONG numRetries = 0;
IPAddr DestAddr;
if (pSA->sa_TunnelAddr) {
DestAddr = pSA->sa_TunnelAddr;
} else {
DestAddr = pSA->SA_DEST_ADDR;
}
//
// if SPI passed in, use that spi else allocate one.
//
if (*pSpi) {
if (IPSecLookupSABySPIWithLock(
*pSpi,
DestAddr)) {
return STATUS_UNSUCCESSFUL;
} else {
return STATUS_SUCCESS;
}
} else {
rand = (ULONG)(ULONG_PTR)pSA;
IPSecGenerateRandom((PUCHAR)&rand, sizeof(ULONG));
rand = LOWER_BOUND_SPI + rand % (UPPER_BOUND_SPI - LOWER_BOUND_SPI);
while (numRetries++ < MAX_SPI_RETRIES) {
if (!IPSecLookupSABySPIWithLock(
(tSPI)rand,
DestAddr)) {
*pSpi = (tSPI)rand;
return STATUS_SUCCESS;
}
rand++;
//
// Collision, retry
//
IPSEC_DEBUG(ACQUIRE, ("IPSecAllocateSPI: collision for: %lx\n", rand));
}
}
return STATUS_UNSUCCESSFUL;
}
NTSTATUS
IPSecNegotiateSA(
IN PFILTER pFilter,
IN ULARGE_INTEGER uliSrcDstAddr,
IN ULARGE_INTEGER uliProtoSrcDstPort,
IN ULONG NewMTU,
OUT PSA_TABLE_ENTRY *ppSA,
IN UCHAR DestType
)
/*++
Routine Description:
Allocates a Larval Inbound SA block then kicks off key manager to negotiate
the algorithms/keys.
Called with SADB lock held, returns with it.
Arguments:
pFilter - the filter and policy that matched this packet.
ppSA - returns the SA created here.
Return Value:
STATUS_PENDING if the buffer is to be held on to, the normal case.
Notes:
--*/
{
KIRQL kIrql;
KIRQL OldIrq;
NTSTATUS status;
PSA_TABLE_ENTRY pSA;
//
// Make sure we dont already have this SA under negotiation
// walk the LarvalSA list to see if we can find another SA.
//
pSA = IPSecLookupSAInLarval(uliSrcDstAddr, uliProtoSrcDstPort);
if (pSA != NULL) {
IPSEC_DEBUG(PATTERN, ("Found in Larval: %lx\n", pSA));
*ppSA = pSA;
return STATUS_DUPLICATE_OBJECTID;
}
IPSEC_DEBUG(ACQUIRE, ("IPSecNegotiateSA: SA: %lx, DA: %lx, P: %lx, SP: %lx, DP: %lx\n", SRC_ADDR, DEST_ADDR, PROTO, SRC_PORT, DEST_PORT));
//
// Initiator
//
status = IPSecInitiatorCreateLarvalSA(
pFilter,
uliSrcDstAddr,
ppSA,
DestType
);
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(SAAPI, ("IPSecNegotiateSA: IPSecCreateSA failed: %lx\n", status));
return status;
}
//
// Save the NewMTU value if this SA has been PMTU'd.
//
(*ppSA)->sa_NewMTU = NewMTU;
//
// If this is a tunnel filter to be negotiated, save off the tunnel addr in the
// SA.
//
if (pFilter->TunnelFilter) {
IPSEC_DEBUG(TUNNEL, ("Negotiating tunnel SA: %lx\n", (*ppSA)));
// (*ppSA)->sa_TunnelAddr = pFilter->TunnelAddr;
}
//
// Now send this up to the Key Manager to negotiate the keys
//
ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &OldIrq);
status = IPSecSubmitAcquire(*ppSA, OldIrq, FALSE);
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(SAAPI, ("IPSecNegotiateSA: IPSecSubmitAcquire failed: %lx\n", status));
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql);
IPSecRemoveEntryList(&(*ppSA)->sa_LarvalLinkage);
IPSEC_DEC_STATISTIC(dwNumPendingKeyOps);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql);
IPSecRemoveSPIEntry(*ppSA);
ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql);
//
// also remove from the filter list
//
if ((*ppSA)->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
(*ppSA)->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&(*ppSA)->sa_FilterLinkage);
(*ppSA)->sa_Filter = NULL;
}
if ((*ppSA)->sa_RekeyOriginalSA) {
ASSERT((*ppSA)->sa_Flags & FLAGS_SA_REKEY);
ASSERT((*ppSA)->sa_RekeyOriginalSA->sa_RekeyLarvalSA == (*ppSA));
ASSERT((*ppSA)->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI);
(*ppSA)->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI;
(*ppSA)->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL;
(*ppSA)->sa_RekeyOriginalSA = NULL;
}
(*ppSA)->sa_Flags &= ~FLAGS_SA_TIMER_STARTED;
IPSecStopTimer(&(*ppSA)->sa_Timer);
IPSecDerefSA(*ppSA);
return status;
}
return status;
}
VOID
IPSecFlushQueuedPackets(
IN PSA_TABLE_ENTRY pSA,
IN NTSTATUS status
)
/*++
Routine Description:
Flushes queued packets now that the keys are known
Arguments:
Return Value:
Notes:
--*/
{
PIPSEC_SEND_COMPLETE_CONTEXT pContext;
IPOptInfo optInfo;
ULONG len;
PNDIS_BUFFER pHdrMdl;
ULONG dataLen;
IPHeader UNALIGNED * pIPH;
KIRQL kIrql;
//
// We need to acquire a lock here because this routine can be called in
// parallel with one in SA delete and the other in SA update (normal).
//
ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql);
pHdrMdl = pSA->sa_BlockedBuffer;
dataLen = pSA->sa_BlockedDataLen;
pSA->sa_BlockedBuffer = NULL;
pSA->sa_BlockedDataLen = 0;
RELEASE_LOCK(&pSA->sa_Lock, kIrql);
if (!pHdrMdl) {
IPSEC_DEBUG(ACQUIRE, ("FlushQueuedPackets: pHdrMdl == NULL\n"));
return;
}
if (status == STATUS_SUCCESS) {
ASSERT(pSA->sa_State == STATE_SA_ACTIVE);
ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0);
ASSERT(pHdrMdl);
pContext = IPSecAllocateSendCompleteCtx(IPSEC_TAG_ESP);
if (!pContext) {
PNDIS_BUFFER pNextMdl;
PNDIS_BUFFER pMdl = pHdrMdl;
NTSTATUS status;
IPSEC_DEBUG(ESP, ("Failed to alloc. SendCtx\n"));
ASSERT(pMdl);
while (pMdl) {
pNextMdl = NDIS_BUFFER_LINKAGE(pMdl);
IPSecFreeBuffer(&status, pMdl);
pMdl = pNextMdl;
}
return;
}
IPSEC_INCREMENT(g_ipsec.NumSends);
IPSecZeroMemory(pContext, sizeof(IPSEC_SEND_COMPLETE_CONTEXT));
#if DBG
RtlCopyMemory(pContext->Signature, "ISC6", 4);
#endif
pContext->FlushMdl = pHdrMdl;
pContext->Flags |= SCF_FLUSH;
IPSecQueryNdisBuf(pHdrMdl, (PVOID)&pIPH, &len);
//
// Call IPTransmit with proper Protocol type so it takes this packet
// at *face* value.
//
optInfo = g_ipsec.OptInfo;
optInfo.ioi_flags |= IP_FLAG_IPSEC;
status = TCPIP_IP_TRANSMIT( &g_ipsec.IPProtInfo,
pContext,
pHdrMdl,
dataLen,
pIPH->iph_dest,
pIPH->iph_src,
&optInfo,
NULL,
pIPH->iph_protocol,
NULL);
//
// Even in the synchronous case, we free the MDL chain in ProtocolSendComplete
// (called by IPSecSendComplete). So, we dont call anything here.
// See IPSecReinjectPacket.
//
} else {
PNDIS_BUFFER pNextMdl;
PNDIS_BUFFER pMdl = pHdrMdl;
NTSTATUS status;
ASSERT(pMdl);
while (pMdl) {
pNextMdl = NDIS_BUFFER_LINKAGE(pMdl);
IPSecFreeBuffer(&status, pMdl);
pMdl = pNextMdl;
}
}
return;
}
NTSTATUS
IPSecInsertOutboundSA(
IN PSA_TABLE_ENTRY pSA,
IN PIPSEC_ACQUIRE_CONTEXT pAcquireCtx,
IN BOOLEAN fTunnelFilter
)
/*++
Routine Description:
Adds an SA into the database, typically called to add outbound SAs as a
result of successful negotiation of keys corresponding to the inbound SA
specified in the context that comes down.
NOTE: Called with SADB lock held.
Arguments:
pSA - SA to be inserted
pAcquireContext - The Acquire context
Return Value:
Notes:
--*/
{
PSA_TABLE_ENTRY pInboundSA = pAcquireCtx->pSA;
PSA_TABLE_ENTRY pAssociatedSA;
KIRQL kIrql;
KIRQL kIrql1;
NTSTATUS status;
PFILTER pFilter;
PSA_TABLE_ENTRY pOutboundSA = NULL;
PSA_TABLE_ENTRY pTunnelSA = NULL;
PLIST_ENTRY pSAChain;
ASSERT(pSA->sa_Flags & FLAGS_SA_OUTBOUND);
ASSERT((pInboundSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0);
ASSERT(pInboundSA->sa_State == STATE_SA_LARVAL);
//
// Potential dangling pointer, always go through the lookup path.
//
if (fTunnelFilter) {
status = IPSecLookupTunnelSA( pSA->sa_uliSrcDstAddr,
pSA->sa_uliProtoSrcDstPort,
&pFilter,
&pOutboundSA,
TRUE);
} else {
#if GPC
if (IS_GPC_ACTIVE()) {
status = IPSecLookupGpcMaskedSA(pSA->sa_uliSrcDstAddr,
pSA->sa_uliProtoSrcDstPort,
&pFilter,
&pOutboundSA,
TRUE);
} else {
status = IPSecLookupMaskedSA( pSA->sa_uliSrcDstAddr,
pSA->sa_uliProtoSrcDstPort,
&pFilter,
&pOutboundSA,
TRUE);
}
#else
status = IPSecLookupMaskedSA( pSA->sa_uliSrcDstAddr,
pSA->sa_uliProtoSrcDstPort,
&pFilter,
&pOutboundSA,
TRUE);
#endif
}
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(ACQUIRE, ("IPSecInsertOutboundSA: IPSecLookupSAByAddr failed: %lx\n", status));
return status;
}
pSAChain = IPSecResolveSAChain(pFilter, pSA->SA_DEST_ADDR);
if (status == STATUS_SUCCESS) {
//
// re-negotiate case: delete the outbound; expire the inbound; add the new one.
//
IPSEC_DEBUG(ACQUIRE, ("IPSecInsertOutboundSA: found another: %lx\n", pOutboundSA));
ASSERT(pOutboundSA);
ASSERT(pOutboundSA->sa_Flags & FLAGS_SA_OUTBOUND);
pSA->sa_Filter = pFilter;
pSA->sa_Flags |= FLAGS_SA_ON_FILTER_LIST;
InsertHeadList(pSAChain, &pSA->sa_FilterLinkage);
IPSEC_INC_STATISTIC(dwNumReKeys);
pAssociatedSA = pOutboundSA->sa_AssociatedSA;
if (pAssociatedSA &&
((pOutboundSA->sa_Flags & FLAGS_SA_REKEY_ORI) ||
!(pInboundSA->sa_Filter))) {
IPSecExpireInboundSA(pAssociatedSA);
}
} else {
//
// pending => this will be the add.
//
ASSERT(pOutboundSA == NULL);
pSA->sa_Filter = pFilter;
pSA->sa_Flags |= FLAGS_SA_ON_FILTER_LIST;
InsertHeadList(pSAChain, &pSA->sa_FilterLinkage);
}
if (pFilter->TunnelAddr != 0) {
pSA->sa_Flags |= FLAGS_SA_TUNNEL;
pSA->sa_TunnelAddr = pFilter->TunnelAddr;
}
//
// Initiator if the original SA had a filter pointer.
//
if (pInboundSA->sa_Filter) {
pSA->sa_Flags |= FLAGS_SA_INITIATOR;
}
//
// Flush this filter from cache table so we match the SA next.
//
if (IS_EXEMPT_FILTER(pFilter)) {
IPSecInvalidateFilterCacheEntry(pFilter);
}
return STATUS_SUCCESS;
}
NTSTATUS
IPSecAddSA(
IN PIPSEC_ADD_SA pAddSA,
IN ULONG TotalSize
)
/*++
Routine Description:
Adds an SA into the database, typically called to add outbound SAs as a
result of successful negotiation of keys corresponding to the inbound SA
specified in the context that comes down.
Arguments:
pAddSA - Add SA context and info.
TotalSize - the total size of the input buffer.
Return Value:
Notes:
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PSA_STRUCT saInfo = &pAddSA->SAInfo;
PSA_TABLE_ENTRY pSA;
ULONG keyLen = 0;
PSA_TABLE_ENTRY pInboundSA;
KIRQL kIrql;
KIRQL kIrql1;
PIPSEC_ACQUIRE_CONTEXT pAcquireContext = (PIPSEC_ACQUIRE_CONTEXT)(saInfo->Context);
//
// Lock the larval list so this SA does not go away.
//
AcquireWriteLock(&g_ipsec.SADBLock, &kIrql1);
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql);
//
// Sanity check the incoming context to see if it is actually
// an SA block
//
if (!NT_SUCCESS(IPSecValidateHandle(pAcquireContext, STATE_SA_LARVAL))) {
IPSEC_DEBUG(SAAPI, ("IPSecAddSA: invalid context: %lx\n", pAcquireContext));
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
return STATUS_INVALID_PARAMETER;
}
//
// figure out the key length and pass that in
//
keyLen = TotalSize - IPSEC_ADD_SA_NO_KEY_SIZE;
IPSEC_DEBUG(SAAPI, ("IPSecAddSA: keyLen: %d\n", keyLen));
//
// create SA block
//
status = IPSecCreateSA(&pSA);
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(SAAPI, ("IPSecAddSA: IPSecCreateSA failed: %lx\n", status));
IPSecAbortAcquire(pAcquireContext);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
return status;
}
pSA->sa_Flags |= FLAGS_SA_OUTBOUND;
//
// Populate with the info in AddSA
//
status = IPSecPopulateSA(saInfo, keyLen, pSA);
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(SAAPI, ("IPSecAddSA: IPSecPopulateSA failed: %lx\n", status));
// IPSecPopulateSA will not free the outbound SA so we have to do it.
IPSecFreeSA(pSA);
IPSecAbortAcquire(pAcquireContext);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
return status;
}
//
// Stash the outermost spi
//
pSA->sa_SPI = pSA->sa_OtherSPIs[pSA->sa_NumOps-1];
//
// insert into proper tables
//
status = IPSecInsertOutboundSA(pSA, pAcquireContext, (BOOLEAN)((pSA->sa_Flags & FLAGS_SA_TUNNEL) != 0));
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(SAAPI, ("IPSecAddSA: IPSecInsertOutboundSA failed: %lx\n", status));
IPSecFreeSA(pSA);
IPSecAbortAcquire(pAcquireContext);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
return status;
}
pInboundSA = pAcquireContext->pSA;
//
// Associate the inbound and outbound SAs
//
pSA->sa_AssociatedSA = pInboundSA;
pInboundSA->sa_AssociatedSA = pSA;
pInboundSA->sa_State = STATE_SA_ASSOCIATED;
//
// Initialize IPSec overhead for the outbound SA.
//
IPSecCalcHeaderOverheadFromSA(pSA, &pSA->sa_IPSecOverhead);
// Copy the NewMTU value over to the new SA.
//
pSA->sa_NewMTU = pInboundSA->sa_NewMTU;
//
// Adjust SA lifetime to the maximum/minimum allowed in driver
//
if (pSA->sa_Lifetime.KeyExpirationTime > IPSEC_MAX_EXPIRE_TIME) {
pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MAX_EXPIRE_TIME;
}
if (pSA->sa_Lifetime.KeyExpirationTime &&
pSA->sa_Lifetime.KeyExpirationTime < IPSEC_MIN_EXPIRE_TIME) {
pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MIN_EXPIRE_TIME;
}
//
// Setup lifetime characteristics
//
IPSecSetupSALifetime(pSA);
//
// Init the LastUsedTime
//
NdisGetCurrentSystemTime(&pSA->sa_LastUsedTime);
//
// outbound is ready to go!
//
pSA->sa_State = STATE_SA_ACTIVE;
IPSEC_DEBUG(SA, ("IPSecAddSA: SA: %lx, S:%lx, D:%lx, O: %c\n",
pSA,
pSA->SA_SRC_ADDR,
pSA->SA_DEST_ADDR,
(pSA->sa_Operation[0] == Auth) ?
'A' : (pSA->sa_Operation[0] == Encrypt) ?
'E' : 'N'));
IPSEC_INC_STATISTIC(dwNumActiveAssociations);
IPSEC_INC_TUNNELS(pSA);
IPSEC_INCREMENT(g_ipsec.NumOutboundSAs);
IPSEC_INC_STATISTIC(dwNumKeyAdditions);
if (pSA->sa_Operation[0] != None) {
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
} else {
//
// The key manager doesnt call update for None;
// call it ourselves.
//
IPSEC_UPDATE_SA updSA;
ASSERT(pInboundSA->sa_Flags & FLAGS_SA_TIMER_STARTED);
IPSEC_DEBUG(SA, ("Calling update on InboundSA: %lx\n", pInboundSA));
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
//
// Reverse the addresses/ports here (by copying from the InboundSA)
//
updSA.SAInfo.Context = pAddSA->SAInfo.Context;
updSA.SAInfo.NumSAs = pAddSA->SAInfo.NumSAs;
updSA.SAInfo.Flags = pAddSA->SAInfo.Flags;
updSA.SAInfo.TunnelAddr = pAddSA->SAInfo.TunnelAddr;
updSA.SAInfo.Lifetime = pAddSA->SAInfo.Lifetime;
updSA.SAInfo.InstantiatedFilter = pAddSA->SAInfo.InstantiatedFilter;
updSA.SAInfo.SecAssoc[0] = pAddSA->SAInfo.SecAssoc[0];
updSA.SAInfo.SecAssoc[0].SPI = pInboundSA->sa_SPI;
updSA.SAInfo.InstantiatedFilter.SrcAddr = pInboundSA->SA_SRC_ADDR;
updSA.SAInfo.InstantiatedFilter.DestAddr = pInboundSA->SA_DEST_ADDR;
updSA.SAInfo.InstantiatedFilter.Protocol = pInboundSA->SA_PROTO;
updSA.SAInfo.InstantiatedFilter.SrcPort = SA_SRC_PORT(pInboundSA);
updSA.SAInfo.InstantiatedFilter.DestPort = SA_DEST_PORT(pInboundSA);
status = IPSecUpdateSA(&updSA, TotalSize);
}
return status;
}
NTSTATUS
IPSecUpdateSA(
IN PIPSEC_UPDATE_SA pUpdateSA,
IN ULONG TotalSize
)
/*++
Routine Description:
Updates an inbound SA for which negotiation was kicked off via AcquireSA with
the relevant keys/algorithms etc.
By the time this routine is called, the SA should be ASSOCIATED with an outbound
SA.
Arguments:
pUpdateSA - Update SA context and info.
TotalSize - the total size of the input buffer.
Return Value:
Notes:
--*/
{
NTSTATUS status = STATUS_SUCCESS;
PSA_STRUCT saInfo = &pUpdateSA->SAInfo;
PSA_TABLE_ENTRY pSA;
PSA_TABLE_ENTRY pOutboundSA;
PSA_HASH pHash;
ULONG keyLen = 0;
KIRQL kIrql;
KIRQL kIrql1;
KIRQL kIrql2;
PIPSEC_ACQUIRE_CONTEXT pAcquireContext = (PIPSEC_ACQUIRE_CONTEXT)(saInfo->Context);
IPSEC_DEBUG(SAAPI, ("IPSecUpdateSA\n"));
//
// Lock the larval list so this SA does not go away.
//
AcquireWriteLock(&g_ipsec.SADBLock, &kIrql1);
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql);
//
// Sanity check the incoming context to see if it is actually
// an SA block
//
if (!NT_SUCCESS(IPSecValidateHandle(pAcquireContext, STATE_SA_ASSOCIATED))) {
IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: invalid context: %lx\n", pAcquireContext));
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
return STATUS_INVALID_PARAMETER;
}
pSA = pAcquireContext->pSA;
ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0);
ASSERT(pSA->sa_State == STATE_SA_ASSOCIATED);
//
// figure out the key length and pass that in
//
keyLen = TotalSize - IPSEC_UPDATE_SA_NO_KEY_SIZE;
IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: keyLen: %d\n", keyLen));
//
// sanity check the info passed in against the initial SA
//
if (pSA->sa_Filter) {
status = IPSecCheckInboundSA(saInfo, pSA);
if (!NT_SUCCESS(status) ||
!pSA->sa_AssociatedSA) {
IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: IPSecCheckInboundSA failed: %lx\n", status));
IPSecAbortAcquire(pAcquireContext);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
return status;
}
}
//
// Populate the SA block
//
status = IPSecPopulateSA(saInfo, keyLen, pSA);
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(SAAPI, ("IPSecUpdSA: IPSecPopulateSA failed: %lx\n", status));
// No need to free inbound SA since IPSecAbortAcquire will do it.
IPSecAbortAcquire(pAcquireContext);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
return status;
}
//
// Set the source Tunnel IP address in outbound SA
//
if (pOutboundSA = pSA->sa_AssociatedSA) {
//
// See if we have well-associated SAs
//
ASSERT(pSA == pSA->sa_AssociatedSA->sa_AssociatedSA);
if (pSA->sa_Flags & FLAGS_SA_TUNNEL) {
pOutboundSA->sa_SrcTunnelAddr = pSA->sa_TunnelAddr;
}
if (pOutboundSA->sa_Flags & FLAGS_SA_TUNNEL) {
pSA->sa_SrcTunnelAddr = pOutboundSA->sa_TunnelAddr;
}
}
//
// Expire the original SA that kicked off this rekey
//
if (pSA->sa_Flags & FLAGS_SA_REKEY) {
PSA_TABLE_ENTRY pOriSA;
if (pOriSA = pSA->sa_RekeyOriginalSA) {
KIRQL kIrql;
pSA->sa_RekeyOriginalSA = NULL;
IPSEC_DEBUG(SA, ("Deleting original SA: pSA: %lx\n", pOriSA));
if (pOriSA->sa_AssociatedSA) {
IPSecExpireInboundSA(pOriSA->sa_AssociatedSA);
}
IPSEC_INC_STATISTIC(dwNumReKeys);
}
}
//
// inbound is ready to go!
//
pSA->sa_State = STATE_SA_ACTIVE;
IPSEC_DEBUG(SA, ("IPSecUpdateSA: SA: %lx, S:%lx, D:%lx, O: %c\n",
pSA,
pSA->SA_SRC_ADDR,
pSA->SA_DEST_ADDR,
(pSA->sa_Operation[0] == Auth) ?
'A' : (pSA->sa_Operation[0] == Encrypt) ?
'E' : 'N'));
//
// Remove from larval list
//
IPSecRemoveEntryList(&pSA->sa_LarvalLinkage);
IPSEC_DEC_STATISTIC(dwNumPendingKeyOps);
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ASSERT(pSA->sa_Flags & FLAGS_SA_TIMER_STARTED);
//
// Bump the SA count for flush SA use; this is necessary because we flush
// SA after releasing the lock because classification routine needs
// it and the SA can be deleted right after we release the lock.
//
IPSecRefSA(pSA);
//
// Free the Acquire Context
//
ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql);
if (pSA->sa_AcquireCtx) {
IPSecInvalidateHandle(pSA->sa_AcquireCtx);
pSA->sa_AcquireCtx = NULL;
}
//
// Adjust SA lifetime to the maximum/minimum allowed in driver
//
if (pSA->sa_Lifetime.KeyExpirationTime > IPSEC_MAX_EXPIRE_TIME) {
pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MAX_EXPIRE_TIME;
}
if (pSA->sa_Lifetime.KeyExpirationTime &&
pSA->sa_Lifetime.KeyExpirationTime < IPSEC_MIN_EXPIRE_TIME) {
pSA->sa_Lifetime.KeyExpirationTime = IPSEC_MIN_EXPIRE_TIME;
}
//
// Setup lifetime characteristics
//
IPSecSetupSALifetime(pSA);
//
// Init the LastUsedTime
//
NdisGetCurrentSystemTime(&pSA->sa_LastUsedTime);
if ((pSA->sa_Flags & FLAGS_SA_DISABLE_LIFETIME_CHECK)) {
if (!IPSecStopTimer(&(pSA->sa_Timer))) {
IPSEC_DEBUG(TIMER, ("Update: couldnt stop timer: %lx\n", pSA));
}
pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED;
} else {
//
// Reschedules the timer on this new value.
//
if (pSA->sa_Lifetime.KeyExpirationTime) {
if (IPSecStopTimer(&pSA->sa_Timer)) {
IPSecStartTimer(&pSA->sa_Timer,
IPSecSAExpired,
pSA->sa_Lifetime.KeyExpirationTime, // expire in key expiration secs
(PVOID)pSA);
}
} else {
ASSERT(FALSE);
if (!IPSecStopTimer(&(pSA->sa_Timer))) {
IPSEC_DEBUG(TIMER, ("Update: couldnt stop timer: %lx\n", pSA));
}
pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED;
}
}
RELEASE_LOCK(&pSA->sa_Lock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
//
// Flush all the queued packets
//
IPSecFlushQueuedPackets(pSA, STATUS_SUCCESS);
IPSecDerefSA(pSA);
return status;
}
VOID
IPSecRefSA(
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Reference the SA passed in
Arguments:
pSA - SA to be refed
Return Value:
The final status from the operation.
--*/
{
if (IPSEC_INCREMENT(pSA->sa_Reference) == 1) {
ASSERT(FALSE);
}
}
VOID
IPSecDerefSA(
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Dereference the SA passed in; if refcount drops to 0, free the block.
Arguments:
pSA - SA to be derefed
Return Value:
The final status from the operation.
--*/
{
ULONG val;
if ((val = IPSEC_DECREMENT(pSA->sa_Reference)) == 0) {
//
// last reference - destroy SA
//
IPSEC_DEBUG(REF, ("Freeing SA: %lx\n", pSA));
#if DBG
if ((pSA->sa_Flags & FLAGS_SA_HW_PLUMBED)) {
DbgPrint("Freeing SA: %lx with offload on\n", pSA);
DbgBreakPoint();
}
if (IPSEC_GET_VALUE(pSA->sa_NumSends) != 0) {
DbgPrint("Freeing SA: %lx with numsends > 0\n", pSA);
DbgBreakPoint();
}
if ((pSA->sa_Flags & FLAGS_SA_TIMER_STARTED)) {
DbgPrint("Freeing SA: %lx with timer on\n", pSA);
DbgBreakPoint();
}
if (pSA->sa_Signature != IPSEC_SA_SIGNATURE) {
DbgPrint("Signature doesnt match for SA: %lx\n", pSA);
DbgBreakPoint();
}
if (!IPSEC_DRIVER_IS_INACTIVE() &&
(pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST)) {
DbgPrint("Freeing SA: %lx while still on filter list\n", pSA);
DbgBreakPoint();
}
#endif
pSA->sa_Signature = IPSEC_SA_SIGNATURE + 1;
IPSecFreeSA(pSA);
}
ASSERT((LONG)val >= 0);
}
VOID
IPSecStopSATimers()
/*++
Routine Description:
Stop all timers active on Larval SA list and Filter list.
Arguments:
Return Value:
The final status from the operation.
--*/
{
PLIST_ENTRY pFilterEntry;
PLIST_ENTRY pSAEntry;
PFILTER pFilter;
PSA_TABLE_ENTRY pSA;
KIRQL kIrql;
LONG Index;
LONG SAIndex;
AcquireWriteLock(&g_ipsec.SADBLock, &kIrql);
//
// Go through all SA's and stop its timers
//
for ( Index = MIN_FILTER;
Index <= MAX_FILTER;
Index++) {
for ( pFilterEntry = g_ipsec.FilterList[Index].Flink;
pFilterEntry != &g_ipsec.FilterList[Index];
pFilterEntry = pFilterEntry->Flink) {
pFilter = CONTAINING_RECORD(pFilterEntry,
FILTER,
MaskedLinkage);
for ( SAIndex = 0;
SAIndex < pFilter->SAChainSize;
SAIndex++) {
for ( pSAEntry = pFilter->SAChain[SAIndex].Flink;
pSAEntry != &pFilter->SAChain[SAIndex];
pSAEntry = pSAEntry->Flink) {
pSA = CONTAINING_RECORD(pSAEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
IPSecStopSATimer(pSA);
}
}
}
}
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql);
}
VOID
IPSecFlushLarvalSAList()
/*++
Routine Description:
When the Acquire Irp is cancelled, this is called to flush all Larval SAs
Called with SADB lock held (first); returns with it.
Called with AcquireInfo.Lock held; returns with it.
Arguments:
Return Value:
The final status from the operation.
--*/
{
KIRQL OldIrq;
KIRQL OldIrq1;
KIRQL kIrql;
PSA_TABLE_ENTRY pLarvalSA;
LIST_ENTRY FreeList;
InitializeListHead(&FreeList);
while (TRUE) {
if (!IsListEmpty(&g_ipsec.AcquireInfo.PendingAcquires)) {
PLIST_ENTRY pEntry;
pEntry = RemoveHeadList(&g_ipsec.AcquireInfo.PendingAcquires);
pLarvalSA = CONTAINING_RECORD( pEntry,
SA_TABLE_ENTRY,
sa_PendingLinkage);
ASSERT(pLarvalSA->sa_State == STATE_SA_LARVAL);
ASSERT(pLarvalSA->sa_Flags & FLAGS_SA_PENDING);
pLarvalSA->sa_Flags &= ~FLAGS_SA_PENDING;
//
// Insert into another list, which we walk without the lock
//
InsertTailList(&FreeList, &pLarvalSA->sa_PendingLinkage);
//
// also remove from Larval list
//
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &OldIrq1);
IPSecRemoveEntryList(&pLarvalSA->sa_LarvalLinkage);
IPSEC_DEC_STATISTIC(dwNumPendingKeyOps);
RELEASE_LOCK(&g_ipsec.LarvalListLock, OldIrq1);
} else {
break;
}
}
//
// get the remaining Larval SAs
//
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &OldIrq);
while (TRUE) {
if (!IsListEmpty(&g_ipsec.LarvalSAList)) {
PLIST_ENTRY pEntry;
pEntry = RemoveHeadList(&g_ipsec.LarvalSAList);
pLarvalSA = CONTAINING_RECORD( pEntry,
SA_TABLE_ENTRY,
sa_LarvalLinkage);
//
// Insert into another list, which we walk without the lock
//
InsertTailList(&FreeList, &pLarvalSA->sa_PendingLinkage);
} else {
break;
}
}
RELEASE_LOCK(&g_ipsec.LarvalListLock, OldIrq);
while (TRUE) {
if (!IsListEmpty(&FreeList)) {
PLIST_ENTRY pEntry;
pEntry = RemoveHeadList(&FreeList);
pLarvalSA = CONTAINING_RECORD( pEntry,
SA_TABLE_ENTRY,
sa_PendingLinkage);
AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql);
IPSecRemoveSPIEntry(pLarvalSA);
ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql);
//
// Flush all the queued packets
//
IPSecFlushQueuedPackets(pLarvalSA, STATUS_TIMEOUT);
//
// also remove from the filter list
//
if (pLarvalSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
pLarvalSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&pLarvalSA->sa_FilterLinkage);
}
if (pLarvalSA->sa_RekeyOriginalSA) {
ASSERT(pLarvalSA->sa_Flags & FLAGS_SA_REKEY);
ASSERT(pLarvalSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA == pLarvalSA);
ASSERT(pLarvalSA->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI);
pLarvalSA->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI;
pLarvalSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL;
pLarvalSA->sa_RekeyOriginalSA = NULL;
}
//
// release acquire context and invalidate the associated cache entry
//
ACQUIRE_LOCK(&pLarvalSA->sa_Lock, &kIrql);
if (pLarvalSA->sa_AcquireCtx) {
IPSecInvalidateHandle(pLarvalSA->sa_AcquireCtx);
pLarvalSA->sa_AcquireCtx = NULL;
}
RELEASE_LOCK(&pLarvalSA->sa_Lock, kIrql);
IPSecInvalidateSACacheEntry(pLarvalSA);
IPSecStopTimerDerefSA(pLarvalSA);
} else {
break;
}
}
return;
}
NTSTATUS
IPSecDeleteSA(
IN PIPSEC_DELETE_SA pDeleteSA
)
/*++
Routine Description:
Delete the SA matching the particulars passed in. Both inbound and
outbound SAs are deleted. No timer set for inbound SA.
Arguments:
Return Value:
The final status from the operation.
--*/
{
PFILTER pFilter;
PSA_TABLE_ENTRY pSA, pInboundSA;
PLIST_ENTRY pEntry, pSAEntry;
KIRQL kIrql;
LONG Index;
LONG SAIndex;
AcquireWriteLock(&g_ipsec.SADBLock, &kIrql);
//
// Walk through the outbound SAs and delete matched ones.
//
for ( Index = OUTBOUND_TRANSPORT_FILTER;
Index <= OUTBOUND_TUNNEL_FILTER;
Index += TRANSPORT_TUNNEL_INCREMENT) {
for ( pEntry = g_ipsec.FilterList[Index].Flink;
pEntry != &g_ipsec.FilterList[Index];
pEntry = pEntry->Flink) {
pFilter = CONTAINING_RECORD(pEntry,
FILTER,
MaskedLinkage);
for ( SAIndex = 0;
SAIndex < pFilter->SAChainSize;
SAIndex++) {
pSAEntry = pFilter->SAChain[SAIndex].Flink;
while (pSAEntry != &pFilter->SAChain[SAIndex]) {
pSA = CONTAINING_RECORD(pSAEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
pSAEntry = pSAEntry->Flink;
if (IPSecMatchSATemplate(pSA, &pDeleteSA->SATemplate)) {
ASSERT(pSA->sa_State == STATE_SA_ACTIVE);
ASSERT(pSA->sa_Flags & FLAGS_SA_OUTBOUND);
ASSERT(pSA->sa_AssociatedSA);
pInboundSA = pSA->sa_AssociatedSA;
if (pInboundSA) {
IPSecDeleteInboundSA(pInboundSA);
}
}
}
}
}
}
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql);
return STATUS_SUCCESS;
}
NTSTATUS
IPSecExpireSA(
IN PIPSEC_EXPIRE_SA pExpireSA
)
/*++
Routine Description:
Expires the SA matching the particulars passed in.
Applied to Inbound SAs - we place the SA in the timer queue
for the next time the timer hits. Also, we delete the
corresponding outbound SA so no further packets match that
SA.
Arguments:
Return Value:
The final status from the operation.
--*/
{
PSA_TABLE_ENTRY pInboundSA;
KIRQL kIrql;
NTSTATUS status;
AcquireWriteLock(&g_ipsec.SADBLock, &kIrql);
pInboundSA = IPSecLookupSABySPI(pExpireSA->DelInfo.SPI,
pExpireSA->DelInfo.DestAddr);
if (pInboundSA) {
ASSERT((pInboundSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0);
if (pInboundSA->sa_State == STATE_SA_ACTIVE) {
IPSEC_DEBUG(ACQUIRE, ("Expiring SA: %lx\n", pInboundSA));
if (pInboundSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
pInboundSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&pInboundSA->sa_FilterLinkage);
}
pInboundSA->sa_Flags |= FLAGS_SA_DELETE_BY_IOCTL;
IPSecExpireInboundSA(pInboundSA);
}
status = STATUS_SUCCESS;
} else {
IPSEC_DEBUG(ACQUIRE, ("Expire for a non-existent SA: %lx\n", pExpireSA));
status = STATUS_NO_MATCH;
}
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql);
return status;
}
VOID
IPSecSAExpired(
IN PIPSEC_TIMER pTimer,
IN PVOID Context
)
/*++
Routine Description:
Called when an SA has expired or when a Larval SA has timed out.
Arguments:
pTimer - the timer struct
Context - SA ptr
Return Value:
STATUS_PENDING if the buffer is to be held on to, the normal case.
Notes:
--*/
{
PSA_TABLE_ENTRY pSA = (PSA_TABLE_ENTRY)Context;
PSA_TABLE_ENTRY pOutboundSA;
KIRQL kIrql;
KIRQL kIrql1;
KIRQL kIrql2;
KIRQL OldIrq;
IPSEC_DEBUG(TIMER, ("IPSecSAExpired: pSA: %lx state: %lx\n", pSA, pSA->sa_State));
AcquireWriteLock(&g_ipsec.SADBLock, &kIrql1);
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql);
switch (pSA->sa_State) {
case STATE_SA_CREATED:
ASSERT(FALSE);
break;
case STATE_SA_LARVAL:
case STATE_SA_ASSOCIATED:
//
// Lock the larval list so this SA does not go away.
//
ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0);
//
// Remove from larval list
//
IPSecRemoveEntryList(&pSA->sa_LarvalLinkage);
IPSEC_DEC_STATISTIC(dwNumPendingKeyOps);
//
// Also remove from Pending list if queued there.
//
ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &kIrql1);
if (pSA->sa_Flags & FLAGS_SA_PENDING) {
ASSERT(pSA->sa_State == STATE_SA_LARVAL);
IPSEC_DEBUG(ACQUIRE, ("IPSecSAExpired: Removed from pending too: %lx\n", pSA));
IPSecRemoveEntryList(&pSA->sa_PendingLinkage);
pSA->sa_Flags &= ~FLAGS_SA_PENDING;
}
RELEASE_LOCK(&g_ipsec.AcquireInfo.Lock, kIrql1);
//
// Flush all the queued packets
//
IPSecFlushQueuedPackets(pSA, STATUS_TIMEOUT);
//
// remove from inbound sa list
//
AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql1);
IPSecRemoveSPIEntry(pSA);
ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql1);
//
// also remove from the filter list
//
if (pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
pSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&pSA->sa_FilterLinkage);
}
//
// invalidate the associated cache entry
//
ACQUIRE_LOCK(&pSA->sa_Lock, &kIrql2);
if (pSA->sa_AcquireCtx) {
IPSecInvalidateHandle(pSA->sa_AcquireCtx);
pSA->sa_AcquireCtx = NULL;
}
RELEASE_LOCK(&pSA->sa_Lock, kIrql2);
IPSecInvalidateSACacheEntry(pSA);
pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED;
if (pSA->sa_RekeyOriginalSA) {
ASSERT(pSA->sa_Flags & FLAGS_SA_REKEY);
ASSERT(pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA == pSA);
ASSERT(pSA->sa_RekeyOriginalSA->sa_Flags & FLAGS_SA_REKEY_ORI);
pSA->sa_RekeyOriginalSA->sa_Flags &= ~FLAGS_SA_REKEY_ORI;
pSA->sa_RekeyOriginalSA->sa_RekeyLarvalSA = NULL;
pSA->sa_RekeyOriginalSA = NULL;
}
if (pOutboundSA = pSA->sa_AssociatedSA) {
IPSEC_DEC_STATISTIC(dwNumActiveAssociations);
IPSEC_DEC_TUNNELS(pOutboundSA);
IPSEC_DECREMENT(g_ipsec.NumOutboundSAs);
IPSecCleanupOutboundSA(pSA, pOutboundSA, FALSE);
}
IPSEC_DEBUG(REF, ("Timer in Deref\n"));
IPSecDerefSA(pSA);
break;
case STATE_SA_ZOMBIE:
ASSERT(FALSE);
break;
case STATE_SA_ACTIVE:
//
// Inbound SA being expired; outbound was deleted immediately
//
ASSERT((pSA->sa_Flags & FLAGS_SA_OUTBOUND) == 0);
ACQUIRE_LOCK(&g_ipsec.AcquireInfo.Lock, &OldIrq);
IPSecNotifySAExpiration(pSA, NULL, OldIrq, FALSE);
//
// remove from inbound sa list
//
AcquireWriteLock(&g_ipsec.SPIListLock, &kIrql1);
IPSecRemoveSPIEntry(pSA);
ReleaseWriteLock(&g_ipsec.SPIListLock, kIrql1);
//
// also remove from the filter list
//
if (pSA->sa_Flags & FLAGS_SA_ON_FILTER_LIST) {
pSA->sa_Flags &= ~FLAGS_SA_ON_FILTER_LIST;
IPSecRemoveEntryList(&pSA->sa_FilterLinkage);
}
//
// invalidate the associated cache entry
//
IPSecInvalidateSACacheEntry(pSA);
pSA->sa_Flags &= ~FLAGS_SA_TIMER_STARTED;
if (pOutboundSA = pSA->sa_AssociatedSA) {
IPSEC_DEC_STATISTIC(dwNumActiveAssociations);
IPSEC_DEC_TUNNELS(pOutboundSA);
IPSEC_DECREMENT(g_ipsec.NumOutboundSAs);
IPSecCleanupOutboundSA(pSA, pOutboundSA, FALSE);
}
if (pSA->sa_Flags & FLAGS_SA_HW_PLUMBED) {
IPSecDelHWSAAtDpc(pSA);
}
ASSERT(pSA->sa_AssociatedSA == NULL);
IPSEC_DEBUG(REF, ("Timer#2 in Deref\n"));
IPSecDerefSA(pSA);
break;
default:
ASSERT(FALSE);
}
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql1);
}
VOID
IPSecFillSAInfo(
IN PSA_TABLE_ENTRY pSA,
OUT PIPSEC_SA_INFO pBuf
)
/*++
Routine Description:
Fill out the SA_INFO structure.
Arguments:
pSA - SA to be filled in
pBuf - where to fill in
Returns:
None.
--*/
{
LONG Index;
PSA_TABLE_ENTRY pAssociatedSA = pSA->sa_AssociatedSA;
RtlZeroMemory(pBuf, sizeof(IPSEC_SA_INFO));
pBuf->PolicyId = pSA->sa_Filter->PolicyId;
pBuf->FilterId = pSA->sa_Filter->FilterId;
pBuf->Lifetime = pSA->sa_Lifetime;
pBuf->InboundTunnelAddr = pSA->sa_SrcTunnelAddr;
pBuf->NumOps = pSA->sa_NumOps;
pBuf->dwQMPFSGroup = pSA->sa_QMPFSGroup;
RtlCopyMemory( &pBuf->CookiePair,
&pSA->sa_CookiePair,
sizeof(IKE_COOKIE_PAIR));
for (Index = 0; Index < pSA->sa_NumOps; Index++) {
pBuf->Operation[Index] = pSA->sa_Operation[Index];
pBuf->EXT_INT_ALGO_EX(Index) = pSA->INT_ALGO(Index);
pBuf->EXT_INT_KEYLEN_EX(Index) = pSA->INT_KEYLEN(Index);
pBuf->EXT_INT_ROUNDS_EX(Index) = pSA->INT_ROUNDS(Index);
pBuf->EXT_CONF_ALGO_EX(Index) = pSA->CONF_ALGO(Index);
pBuf->EXT_CONF_KEYLEN_EX(Index) = pSA->CONF_KEYLEN(Index);
pBuf->EXT_CONF_ROUNDS_EX(Index) = pSA->CONF_ROUNDS(Index);
if (pAssociatedSA) {
pBuf->InboundSPI[Index] = pAssociatedSA->sa_OtherSPIs[Index];
}
pBuf->OutboundSPI[Index] = pSA->sa_OtherSPIs[Index];
}
pBuf->AssociatedFilter.SrcAddr = pSA->SA_SRC_ADDR & pSA->SA_SRC_MASK;
pBuf->AssociatedFilter.SrcMask = pSA->SA_SRC_MASK;
pBuf->AssociatedFilter.DestAddr = pSA->SA_DEST_ADDR & pSA->SA_DEST_MASK;
pBuf->AssociatedFilter.DestMask = pSA->SA_DEST_MASK;
pBuf->AssociatedFilter.Protocol = pSA->SA_PROTO;
pBuf->AssociatedFilter.SrcPort = SA_SRC_PORT(pSA);
pBuf->AssociatedFilter.DestPort = SA_DEST_PORT(pSA);
pBuf->AssociatedFilter.TunnelAddr = pSA->sa_TunnelAddr;
pBuf->AssociatedFilter.TunnelFilter = (pSA->sa_Flags & FLAGS_SA_TUNNEL) != 0;
if (pSA->sa_Flags & FLAGS_SA_OUTBOUND) {
pBuf->AssociatedFilter.Flags = FILTER_FLAGS_OUTBOUND;
} else {
pBuf->AssociatedFilter.Flags = FILTER_FLAGS_INBOUND;
}
if (pSA->sa_Flags & FLAGS_SA_INITIATOR) {
pBuf->EnumFlags |= SA_ENUM_FLAGS_INITIATOR;
}
if (pSA->sa_Flags & FLAGS_SA_MTU_BUMPED) {
pBuf->EnumFlags |= SA_ENUM_FLAGS_MTU_BUMPED;
}
if (pSA->sa_Flags & FLAGS_SA_HW_PLUMBED) {
pBuf->EnumFlags |= SA_ENUM_FLAGS_OFFLOADED;
}
if (pSA->sa_Flags & FLAGS_SA_HW_PLUMB_FAILED) {
pBuf->EnumFlags |= SA_ENUM_FLAGS_OFFLOAD_FAILED;
}
if (pSA->sa_Flags & FLAGS_SA_OFFLOADABLE) {
pBuf->EnumFlags |= SA_ENUM_FLAGS_OFFLOADABLE;
}
if (pSA->sa_Flags & FLAGS_SA_REKEY_ORI) {
pBuf->EnumFlags |= SA_ENUM_FLAGS_IN_REKEY;
}
pBuf->Stats.ConfidentialBytesSent = pSA->sa_Stats.ConfidentialBytesSent;
pBuf->Stats.AuthenticatedBytesSent = pSA->sa_Stats.AuthenticatedBytesSent;
pBuf->Stats.TotalBytesSent = pSA->sa_Stats.TotalBytesSent;
pBuf->Stats.OffloadedBytesSent = pSA->sa_Stats.OffloadedBytesSent;
if (pAssociatedSA) {
pBuf->Stats.ConfidentialBytesReceived =
pAssociatedSA->sa_Stats.ConfidentialBytesReceived;
pBuf->Stats.AuthenticatedBytesReceived =
pAssociatedSA->sa_Stats.AuthenticatedBytesReceived;
pBuf->Stats.TotalBytesReceived =
pAssociatedSA->sa_Stats.TotalBytesReceived;
pBuf->Stats.OffloadedBytesReceived =
pAssociatedSA->sa_Stats.OffloadedBytesReceived;
}
}
NTSTATUS
IPSecEnumSAs(
IN PIRP pIrp,
OUT PULONG pBytesCopied
)
/*++
Routine Description:
Fills in the request to enumerate SAs.
Arguments:
pIrp - The actual Irp
pBytesCopied - the number of bytes copied.
Returns:
Status of the operation.
--*/
{
PNDIS_BUFFER NdisBuffer = NULL;
PIPSEC_ENUM_SAS pEnum = NULL;
ULONG BufferLength = 0;
KIRQL kIrql;
PLIST_ENTRY pEntry;
PLIST_ENTRY pSAEntry;
IPSEC_SA_INFO infoBuff = {0};
NTSTATUS status = STATUS_SUCCESS;
ULONG BytesCopied = 0;
ULONG Offset = 0;
PFILTER pFilter;
PSA_TABLE_ENTRY pSA;
LONG Index;
LONG FilterIndex;
LONG SAIndex;
//
// Get at the IO buffer - its in the MDL
//
NdisBuffer = REQUEST_NDIS_BUFFER(pIrp);
if (NdisBuffer == NULL) {
return STATUS_INVALID_PARAMETER;
}
NdisQueryBufferSafe(NdisBuffer,
(PVOID *)&pEnum,
&BufferLength,
NormalPagePriority);
//
// Make sure NdisQueryBufferSafe succeeds.
//
if (!pEnum) {
IPSEC_DEBUG (IOCTL, ("EnumSAs failed, no resources\n"));
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Make sure we have enough room for just the header not
// including the data.
//
if (BufferLength < (UINT)(FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]))) {
IPSEC_DEBUG (IOCTL, ("EnumSAs failed, buffer too small\n"));
return STATUS_BUFFER_TOO_SMALL;
}
//
// Make sure we are naturally aligned.
//
if (((ULONG_PTR)(pEnum)) & (TYPE_ALIGNMENT(IPSEC_ENUM_SAS) - 1)) {
IPSEC_DEBUG (IOCTL, ("EnumSAs failed, alignment\n"));
return STATUS_DATATYPE_MISALIGNMENT_ERROR;
}
pEnum->NumEntries = 0;
pEnum->NumEntriesPresent = 0;
//
// Now copy over the SA data into the user buffer and fit as many as possible.
//
BufferLength -= FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]);
Offset = FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]);
Index = pEnum->Index; // where to start?
AcquireReadLock(&g_ipsec.SADBLock, &kIrql);
for ( FilterIndex = MIN_FILTER;
FilterIndex <= MAX_FILTER;
FilterIndex++) {
for ( pEntry = g_ipsec.FilterList[FilterIndex].Flink;
pEntry != &g_ipsec.FilterList[FilterIndex];
pEntry = pEntry->Flink) {
pFilter = CONTAINING_RECORD(pEntry,
FILTER,
MaskedLinkage);
for ( SAIndex = 0;
SAIndex < pFilter->SAChainSize;
SAIndex ++) {
for ( pSAEntry = pFilter->SAChain[SAIndex].Flink;
pSAEntry != &pFilter->SAChain[SAIndex];
pSAEntry = pSAEntry->Flink) {
pSA = CONTAINING_RECORD(pSAEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
//
// Only interested in outbound or multicast SAs.
//
if (!(pSA->sa_Flags & FLAGS_SA_OUTBOUND)) {
continue;
}
//
// Dump only SAs that match the template.
//
if (IPSecMatchSATemplate(pSA, &pEnum->SATemplate)) {
if (Index > 0) {
Index--; // Skip number of Index SAs.
continue;
}
pEnum->NumEntriesPresent++;
if ((INT)(BufferLength - BytesCopied) >= (INT)sizeof(IPSEC_SA_INFO)) {
IPSecFillSAInfo(pSA, &infoBuff);
BytesCopied += sizeof(IPSEC_SA_INFO);
NdisBuffer = CopyToNdis(NdisBuffer, (UCHAR *)&infoBuff, sizeof(IPSEC_SA_INFO), &Offset);
if (!NdisBuffer) {
ReleaseReadLock(&g_ipsec.SADBLock, kIrql);
return STATUS_INSUFFICIENT_RESOURCES;
}
}
}
}
}
}
}
ReleaseReadLock(&g_ipsec.SADBLock, kIrql);
pEnum->NumEntries = BytesCopied / sizeof(IPSEC_SA_INFO);
*pBytesCopied = BytesCopied + FIELD_OFFSET(IPSEC_ENUM_SAS, pInfo[0]);
if (pEnum->NumEntries < pEnum->NumEntriesPresent) {
status = STATUS_BUFFER_OVERFLOW;
}
return status;
}
VOID
IPSecReaper(
IN PIPSEC_TIMER pTimer,
IN PVOID Context
)
/*++
Routine Description:
Called every 5 mins; reaps the (active) SA list
Arguments:
pTimer - the timer struct
Context - NULL
Return Value:
STATUS_PENDING if the buffer is to be held on to, the normal case.
Notes:
--*/
{
KIRQL kIrql;
IPSEC_DEBUG(TIMER, ("Entering IPSecReaper\n"));
AcquireWriteLock(&g_ipsec.SADBLock, &kIrql);
//
// walk the outbound SAs and delete/expire them if they have been
// idle for sometime (lets say 5 mins for now).
//
IPSecReapIdleSAs();
ReleaseWriteLock(&g_ipsec.SADBLock, kIrql);
IPSEC_DEBUG(TIMER, ("Exiting IPSecReaper\n"));
if (!IPSEC_DRIVER_IS_INACTIVE()) {
IPSecStartTimer(&g_ipsec.ReaperTimer,
IPSecReaper,
IPSEC_REAPER_TIME,
(PVOID)NULL);
}
}
VOID
IPSecReapIdleSAs()
/*++
Routine Description:
Called to reap the idle SA list
Arguments:
Return Value:
--*/
{
PSA_TABLE_ENTRY pSA;
PFILTER pFilter;
PLIST_ENTRY pEntry;
PLIST_ENTRY pSAEntry;
BOOLEAN fExpired;
LONG Index;
LONG SAIndex;
IPSEC_DEBUG(TIMER, ("Entering IPSecReapIdleSAs\n"));
//
// Walk the inbound SAs and delete/expire them if they have been
// idle for sometime (lets say 5 mins for now).
//
for ( Index = INBOUND_TRANSPORT_FILTER;
Index <= INBOUND_TUNNEL_FILTER;
Index += TRANSPORT_TUNNEL_INCREMENT) {
for ( pEntry = g_ipsec.FilterList[Index].Flink;
pEntry != &g_ipsec.FilterList[Index];
pEntry = pEntry->Flink) {
pFilter = CONTAINING_RECORD(pEntry,
FILTER,
MaskedLinkage);
for ( SAIndex = 0;
SAIndex < pFilter->SAChainSize;
SAIndex++) {
pSAEntry = pFilter->SAChain[SAIndex].Flink;
while (pSAEntry != &pFilter->SAChain[SAIndex]) {
pSA = CONTAINING_RECORD(pSAEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
ASSERT(!(pSA->sa_Flags & FLAGS_SA_OUTBOUND));
pSAEntry = pSAEntry->Flink;
if (!(pSA->sa_Flags & FLAGS_SA_IDLED_OUT) &&
(pSA->sa_State == STATE_SA_ACTIVE) &&
!(pSA->sa_Flags & FLAGS_SA_DISABLE_IDLE_OUT)) {
IPSEC_SA_EXPIRED(pSA, fExpired);
if (fExpired) {
pSA->sa_Flags |= FLAGS_SA_IDLED_OUT;
IPSecExpireInboundSA(pSA);
}
}
}
}
}
}
IPSEC_DEBUG(TIMER, ("Exiting IPSecReapIdleSAs\n"));
}
VOID
IPSecFlushEventLog(
IN PIPSEC_TIMER pTimer,
IN PVOID Context
)
/*++
Routine Description:
Called every LogInterval seconds; flush all events currently logged.
Arguments:
pTimer - the timer struct
Context - NULL
Return Value:
Notes:
--*/
{
KIRQL kIrql;
IPSEC_DEBUG(TIMER, ("Entering IPSecFlushEventLog\n"));
ACQUIRE_LOCK(&g_ipsec.EventLogLock, &kIrql);
if (g_ipsec.IPSecLogMemoryLoc > g_ipsec.IPSecLogMemory) {
//
// Flush the logs.
//
IPSecQueueLogEvent();
}
RELEASE_LOCK(&g_ipsec.EventLogLock, kIrql);
if (!IPSEC_DRIVER_IS_INACTIVE()) {
IPSecStartTimer(&g_ipsec.EventLogTimer,
IPSecFlushEventLog,
g_ipsec.LogInterval,
(PVOID)NULL);
}
}
NTSTATUS
IPSecQuerySpi(
IN OUT PIPSEC_QUERY_SPI pQuerySpi
)
/*++
Routine Description:
Queries IPSEC for spis corresponding to given filter
Arguments:
Return Value:
Notes:
--*/
{
NTSTATUS status;
ULARGE_INTEGER uliSrcDstAddr;
ULARGE_INTEGER uliProtoSrcDstPort;
PFILTER pFilter = NULL;
PSA_TABLE_ENTRY pSA = NULL;
PSA_TABLE_ENTRY pNextSA = NULL;
PSA_TABLE_ENTRY pTunnelSA = NULL;
KIRQL kIrql;
pQuerySpi->Spi = 0;
pQuerySpi->OtherSpi = 0;
pQuerySpi->Operation = 0;
IPSEC_DEBUG(ACQUIRE, ("IPSecQuerySPI: Src %08x.%04x Dst %08x.%04x Protocol %d",
pQuerySpi->Filter.SrcAddr,
pQuerySpi->Filter.SrcPort,
pQuerySpi->Filter.DestAddr,
pQuerySpi->Filter.DestPort,
pQuerySpi->Filter.Protocol));
IPSEC_BUILD_SRC_DEST_ADDR( uliSrcDstAddr,
pQuerySpi->Filter.SrcAddr,
pQuerySpi->Filter.DestAddr);
IPSEC_BUILD_PROTO_PORT_LI( uliProtoSrcDstPort,
pQuerySpi->Filter.Protocol,
pQuerySpi->Filter.SrcPort,
pQuerySpi->Filter.DestPort);
AcquireReadLock(&g_ipsec.SADBLock, &kIrql);
//
// search for SA
//
status = IPSecLookupSAByAddr( uliSrcDstAddr,
uliProtoSrcDstPort,
&pFilter,
&pSA,
&pNextSA,
&pTunnelSA,
FALSE,
FALSE,
FALSE);
if (!NT_SUCCESS(status)) {
IPSEC_DEBUG(ACQUIRE, ("IPSecQuerySPI: IPSecLookupSAByAddr failed: %lx\n", status));
ReleaseReadLock(&g_ipsec.SADBLock, kIrql);
return status;
}
if (status == STATUS_SUCCESS) {
ASSERT(pSA);
} else {
ReleaseReadLock(&g_ipsec.SADBLock, kIrql);
return STATUS_SUCCESS;
}
pQuerySpi->Spi = pSA->sa_SPI;
if (pSA->sa_AssociatedSA) {
pQuerySpi->OtherSpi = pSA->sa_AssociatedSA->sa_SPI;
}
pQuerySpi->Operation = pSA->sa_Operation[pSA->sa_NumOps-1];
ReleaseReadLock(&g_ipsec.SADBLock, kIrql);
return STATUS_SUCCESS;
}
NTSTATUS
IPSecSetOperationMode(
IN PIPSEC_SET_OPERATION_MODE pSetOperationMode
)
/*++
Routine Description:
Set the driver operation mode.
Arguments:
Return Value:
Notes:
--*/
{
g_ipsec.OperationMode = pSetOperationMode->OperationMode;
return STATUS_SUCCESS;
}
NTSTATUS
IPSecInitializeTcpip(
IN PIPSEC_SET_TCPIP_STATUS pSetTcpipStatus
)
/*++
Routine Description:
Initialize TCP/IP.
Arguments:
Return Value:
Notes:
--*/
{
IPInfo Info;
if (IPSEC_DRIVER_INIT_TCPIP()) {
return STATUS_SUCCESS;
}
//
// Store all TCP/IP function pointers for future use. There is no check
// for NULL pointer here because the function pointer can also be stale
// address. We trust TCP/IP to pass in the values corretly.
//
TCPIP_FREE_BUFF = pSetTcpipStatus->TcpipFreeBuff;
TCPIP_ALLOC_BUFF = pSetTcpipStatus->TcpipAllocBuff;
TCPIP_GET_INFO = pSetTcpipStatus->TcpipGetInfo;
TCPIP_NDIS_REQUEST = pSetTcpipStatus->TcpipNdisRequest;
TCPIP_SET_IPSEC_STATUS = pSetTcpipStatus->TcpipSetIPSecStatus;
TCPIP_SET_IPSEC = pSetTcpipStatus->TcpipSetIPSecPtr;
TCPIP_UNSET_IPSEC = pSetTcpipStatus->TcpipUnSetIPSecPtr;
TCPIP_UNSET_IPSEC_SEND = pSetTcpipStatus->TcpipUnSetIPSecSendPtr;
TCPIP_TCP_XSUM = pSetTcpipStatus->TcpipTCPXsum;
//
// Initialize IPInfo for reinjecting packets to TCP/IP.
//
if (TCPIP_GET_INFO(&Info, sizeof(IPInfo)) != IP_SUCCESS) {
ASSERT(FALSE);
return STATUS_BUFFER_TOO_SMALL;
}
Info.ipi_initopts(&g_ipsec.OptInfo);
//
// The followings come from IPInfo.
//
TCPIP_REGISTER_PROTOCOL = Info.ipi_protreg;
TCPIP_DEREGISTER_PROTOCOL = Info.ipi_protdereg;
TCPIP_IP_TRANSMIT = Info.ipi_xmit;
TCPIP_GET_ADDRTYPE = Info.ipi_getaddrtype;
TCPIP_GEN_IPID = Info.ipi_getipid;
//
// Don't register IPSecStatus function for AH and ESP protocol here.
// Registration occurs with filter addition.
//
//
// Everything is ready to go, bind to IP so we will intercept traffic.
//
IPSecBindToIP();
IPSEC_DRIVER_INIT_TCPIP() = TRUE;
return STATUS_SUCCESS;
}
NTSTATUS
IPSecDeinitializeTcpip(
VOID
)
/*++
Routine Description:
Deinitialize TCP/IP.
Arguments:
Return Value:
Notes:
--*/
{
if (!IPSEC_DRIVER_INIT_TCPIP()) {
return STATUS_SUCCESS;
}
IPSEC_DRIVER_INIT_TCPIP() = FALSE;
//
// Unbind IPSecHandlerPtr from TCP/IP and wait for all transmits, pending
// sends, worker threads and iotcls to complete.
//
IPSecUnbindSendFromIP();
//
// Wait for all threads (transmits) to finish.
//
while (IPSEC_GET_VALUE(g_ipsec.NumThreads) != 0) {
IPSEC_DELAY_EXECUTION();
}
//
// Wait for all pending IOCTLs to finish. Note this current IOCTL also
// takes one count.
//
while (IPSEC_GET_VALUE(g_ipsec.NumIoctls) != 1) {
IPSEC_DELAY_EXECUTION();
}
//
// Wait for all worker threads (logs or plumbs) to finish.
//
while (IPSEC_GET_VALUE(g_ipsec.NumWorkers) != 0) {
IPSEC_DELAY_EXECUTION();
}
//
// Wait for all send completes to go through.
//
while (IPSEC_GET_VALUE(g_ipsec.NumSends) != 0) {
IPSEC_DELAY_EXECUTION();
}
//
// Reset IPSecStatus functions in TCP/IP to NULL.
//
if (IPSEC_GET_VALUE(gdwInitEsp)) {
TCPIP_DEREGISTER_PROTOCOL(PROTOCOL_ESP);
IPSEC_SET_VALUE(gdwInitEsp, 0);
}
if (IPSEC_GET_VALUE(gdwInitAh)) {
TCPIP_DEREGISTER_PROTOCOL(PROTOCOL_AH);
IPSEC_SET_VALUE(gdwInitAh, 0);
}
//
// Unbind the rest of IPSec routines from TCP/IP.
//
IPSecUnbindFromIP();
return STATUS_SUCCESS;
}
NTSTATUS
IPSecSetTcpipStatus(
IN PIPSEC_SET_TCPIP_STATUS pSetTcpipStatus
)
/*++
Routine Description:
Set the TCP/IP driver status indicating whether can register with it.
Arguments:
Return Value:
Notes:
--*/
{
PAGED_CODE();
if (pSetTcpipStatus->TcpipStatus) {
return IPSecInitializeTcpip(pSetTcpipStatus);
} else {
return IPSecDeinitializeTcpip();
}
}
NTSTATUS
IPSecResetCacheTable(
VOID
)
/*++
Routine Description:
Invalidate all cache entries and its associated SA or Filter.
Arguments:
Return Value:
Notes:
--*/
{
PFILTER_CACHE pCache;
ULONG i;
for (i = 0; i < g_ipsec.CacheSize; i ++) {
pCache = g_ipsec.ppCache[i];
if (pCache && IS_VALID_CACHE_ENTRY(pCache)) {
if (pCache->FilterEntry) {
pCache->pFilter->FilterCache = NULL;
} else {
pCache->pSAEntry->sa_FilterCache = NULL;
if (pCache->pNextSAEntry) {
pCache->pNextSAEntry->sa_FilterCache = NULL;
}
}
INVALIDATE_CACHE_ENTRY(pCache);
}
}
return STATUS_SUCCESS;
}
NTSTATUS
IPSecPurgeFilterSAs(
IN PFILTER pFilter
)
/*++
Routine Description
Delete all SAs that are related to this filter.
Locks
Called with SADB held.
Arguments
pFilter - filter of interest
Return Value
STATUS_SUCCESS
--*/
{
PLIST_ENTRY pEntry;
PSA_TABLE_ENTRY pSA;
KIRQL kIrql;
LONG Index;
//
// Expire each inbound SA and delete outbound SA
//
for (Index = 0; Index < pFilter->SAChainSize; Index ++) {
pEntry = pFilter->SAChain[Index].Flink;
while (pEntry != &pFilter->SAChain[Index]) {
pSA = CONTAINING_RECORD(pEntry,
SA_TABLE_ENTRY,
sa_FilterLinkage);
pEntry = pEntry->Flink;
if (pSA->sa_State == STATE_SA_ACTIVE) {
IPSEC_DEBUG(ACQUIRE, ("Destroying active SA: %lx\n", pSA));
//
// Filter is going away, SA must be deleted now
//
if (!(pSA->sa_Flags & FLAGS_SA_OUTBOUND)) {
//ASSERT(pSA->sa_AssociatedSA);
IPSecDeleteInboundSA(pSA);
} else {
ASSERT(pSA->sa_AssociatedSA);
if (pSA->sa_AssociatedSA->sa_State == STATE_SA_ASSOCIATED) {
IPSecDeleteLarvalSA(pSA->sa_AssociatedSA);
} else {
IPSecDeleteInboundSA(pSA->sa_AssociatedSA);
}
}
} else {
IPSEC_DEBUG(ACQUIRE, ("Destroying larval SA: %lx\n", pSA));
//
// SA undergoing negotiation - just invalidate the context.
// the timer will take care of the rest
//
if (pSA->sa_AssociatedSA) {
if (pSA->sa_AssociatedSA->sa_AcquireCtx) {
IPSecInvalidateHandle(pSA->sa_AssociatedSA->sa_AcquireCtx);
pSA->sa_AssociatedSA->sa_AcquireCtx = NULL;
}
}
IPSecDeleteLarvalSA(pSA);
}
}
}
//
// Also need to remove all those larval SAs whose sa_Filter is pointing
// to the filter being deleted.
//
ACQUIRE_LOCK(&g_ipsec.LarvalListLock, &kIrql);
pEntry = g_ipsec.LarvalSAList.Flink;
while (pEntry != &g_ipsec.LarvalSAList) {
pSA = CONTAINING_RECORD(pEntry,
SA_TABLE_ENTRY,
sa_LarvalLinkage);
pEntry = pEntry->Flink;
if (pSA->sa_Filter == pFilter) {
IPSecRemoveEntryList(&pSA->sa_LarvalLinkage);
IPSEC_DEC_STATISTIC(dwNumPendingKeyOps);
IPSecCleanupLarvalSA(pSA);
}
}
RELEASE_LOCK(&g_ipsec.LarvalListLock, kIrql);
return STATUS_SUCCESS;
}
NTSTATUS
IPSecSetupSALifetime(
IN PSA_TABLE_ENTRY pSA
)
/*++
Routine Description:
Setup the SA lifetime characteristics for rekey and idle timeout.
Arguments:
Return Value:
--*/
{
LARGE_INTEGER CurrentTime;
LARGE_INTEGER Delta = {0};
LARGE_INTEGER Pad = {(pSA->sa_Flags & FLAGS_SA_INITIATOR)?
IPSEC_EXPIRE_TIME_PAD_I :
IPSEC_EXPIRE_TIME_PAD_R,
0};
//
// pSA->sa_Lifetime.KeyExpirationTime is in seconds.
//
if (pSA->sa_Lifetime.KeyExpirationTime) {
IPSEC_CONVERT_SECS_TO_100NS(Delta, pSA->sa_Lifetime.KeyExpirationTime);
NdisGetCurrentSystemTime(&CurrentTime);
pSA->sa_KeyExpirationTime.QuadPart = (CurrentTime.QuadPart + Delta.QuadPart);
pSA->sa_KeyExpirationTimeWithPad.QuadPart = pSA->sa_KeyExpirationTime.QuadPart - Pad.QuadPart;
if (!(pSA->sa_KeyExpirationTimeWithPad.QuadPart > 0i64)) {
pSA->sa_KeyExpirationTimeWithPad.QuadPart = 0i64;
}
}
//
// pSA->sa_Lifetime.KeyExpirationBytes is in Kbytes.
//
if (pSA->sa_Lifetime.KeyExpirationBytes) {
pSA->sa_KeyExpirationBytes.LowPart = pSA->sa_Lifetime.KeyExpirationBytes;
pSA->sa_KeyExpirationBytes = EXTENDED_MULTIPLY(pSA->sa_KeyExpirationBytes, 1024);
if (pSA->sa_Flags & FLAGS_SA_INITIATOR) {
pSA->sa_KeyExpirationBytesWithPad.LowPart = pSA->sa_Lifetime.KeyExpirationBytes * IPSEC_EXPIRE_THRESHOLD_I / 100;
} else {
pSA->sa_KeyExpirationBytesWithPad.LowPart = pSA->sa_Lifetime.KeyExpirationBytes * IPSEC_EXPIRE_THRESHOLD_R / 100;
}
pSA->sa_KeyExpirationBytesWithPad = EXTENDED_MULTIPLY(pSA->sa_KeyExpirationBytesWithPad, 1024);
}
//
// Also setup the idle timeout characteristics.
//
if (pSA->sa_Flags & FLAGS_SA_INITIATOR) {
IPSEC_CONVERT_SECS_TO_100NS(pSA->sa_IdleTime,
(g_ipsec.DefaultSAIdleTime + IPSEC_DEFAULT_SA_IDLE_TIME_PAD_I));
} else {
IPSEC_CONVERT_SECS_TO_100NS(pSA->sa_IdleTime,
(g_ipsec.DefaultSAIdleTime + IPSEC_DEFAULT_SA_IDLE_TIME_PAD_R));
}
return STATUS_SUCCESS;
}
DWORD ConvertAddr(IPAddr Addr, IPAddr Mask, ADDR* OutAddr)
{
if (Mask == 0xffffffff) {
OutAddr->AddrType=IP_ADDR_UNIQUE;
} else {
OutAddr->AddrType=IP_ADDR_SUBNET;
}
OutAddr->uSubNetMask=Mask;
OutAddr->uIpAddr=Addr;
return STATUS_SUCCESS;
}
DWORD ConvertSAToIPSecQMSA(PIPSEC_QM_SA pOutSA,
PSA_TABLE_ENTRY pInSA)
/*++
Routine Description:
Convert SA_TABLE_ENTRY to IPSEC_QM_SA
Arguments:
Return Value:
--*/
{
int i;
memcpy(&pOutSA->gQMPolicyID,&pInSA->sa_Filter->PolicyId,sizeof(GUID));
memcpy(&pOutSA->gQMFilterID,&pInSA->sa_Filter->FilterId,sizeof(GUID));
memcpy(&pOutSA->MMSpi.Initiator,&pInSA->sa_CookiePair.Initiator,sizeof(IKE_COOKIE));
memcpy(&pOutSA->MMSpi.Responder,&pInSA->sa_CookiePair.Responder,sizeof(IKE_COOKIE));
ConvertAddr(pInSA->SA_SRC_ADDR,pInSA->SA_SRC_MASK,&pOutSA->IpsecQMFilter.SrcAddr);
ConvertAddr(pInSA->SA_DEST_ADDR,pInSA->SA_DEST_MASK,&pOutSA->IpsecQMFilter.DesAddr);
pOutSA->IpsecQMFilter.Protocol.ProtocolType=PROTOCOL_UNIQUE;
pOutSA->IpsecQMFilter.Protocol.dwProtocol=pInSA->SA_PROTO;
pOutSA->IpsecQMFilter.SrcPort.PortType=PORT_UNIQUE;
pOutSA->IpsecQMFilter.SrcPort.wPort=NET_SHORT(SA_SRC_PORT(pInSA));
pOutSA->IpsecQMFilter.DesPort.PortType=PORT_UNIQUE;
pOutSA->IpsecQMFilter.DesPort.wPort=NET_SHORT(SA_DEST_PORT(pInSA));
if (pInSA->sa_Flags & FLAGS_SA_TUNNEL) {
pOutSA->IpsecQMFilter.QMFilterType = QM_TUNNEL_FILTER;
ConvertAddr(pInSA->sa_SrcTunnelAddr,0xffffffff,&pOutSA->IpsecQMFilter.MyTunnelEndpt);
ConvertAddr(pInSA->sa_TunnelAddr,0xffffffff,&pOutSA->IpsecQMFilter.PeerTunnelEndpt);
} else {
pOutSA->IpsecQMFilter.QMFilterType = QM_TRANSPORT_FILTER;
}
pOutSA->SelectedQMOffer.dwPFSGroup=pInSA->sa_QMPFSGroup;
if (pOutSA->SelectedQMOffer.dwPFSGroup) {
pOutSA->SelectedQMOffer.bPFSRequired=TRUE;
}
pOutSA->SelectedQMOffer.Lifetime.uKeyExpirationTime=pInSA->sa_Lifetime.KeyExpirationTime;
pOutSA->SelectedQMOffer.Lifetime.uKeyExpirationKBytes=pInSA->sa_Lifetime.KeyExpirationBytes;
pOutSA->SelectedQMOffer.dwNumAlgos=pInSA->sa_NumOps;
for (i=0; i < pInSA->sa_NumOps;i++) {
pOutSA->SelectedQMOffer.Algos[i].Operation=pInSA->sa_Operation[i];
if (pInSA->sa_AssociatedSA) {
pOutSA->SelectedQMOffer.Algos[i].MySpi= pInSA->sa_AssociatedSA->sa_OtherSPIs[i];
}
pOutSA->SelectedQMOffer.Algos[i].PeerSpi= pInSA->sa_OtherSPIs[i];
switch(pOutSA->SelectedQMOffer.Algos[i].Operation) {
case AUTHENTICATION:
pOutSA->SelectedQMOffer.Algos[i].uAlgoIdentifier=pInSA->INT_ALGO(i);
pOutSA->SelectedQMOffer.Algos[i].uAlgoKeyLen=pInSA->INT_KEYLEN(i);
pOutSA->SelectedQMOffer.Algos[i].uAlgoRounds=pInSA->INT_ROUNDS(i);
break;
case ENCRYPTION:
pOutSA->SelectedQMOffer.Algos[i].uAlgoIdentifier=pInSA->CONF_ALGO(i);
pOutSA->SelectedQMOffer.Algos[i].uAlgoKeyLen=pInSA->CONF_KEYLEN(i);
pOutSA->SelectedQMOffer.Algos[i].uAlgoRounds=pInSA->CONF_ROUNDS(i);
pOutSA->SelectedQMOffer.Algos[i].uSecAlgoIdentifier=pInSA->INT_ALGO(i);
pOutSA->SelectedQMOffer.Algos[i].uSecAlgoKeyLen=pInSA->INT_KEYLEN(i);
break;
default:
break;
}
}
return STATUS_SUCCESS;
}
BOOLEAN
IPSecMatchSATemplate(
IN PSA_TABLE_ENTRY pSA,
IN PIPSEC_QM_SA pSATemplate
)
/*++
Routine Description:
Try to see if the SA passed in matches the template.
Arguments:
pSA - SA of interest
pSATemplate - SA template
Return Value:
TRUE/FALSE
--*/
{
LARGE_INTEGER ZeroLI = {0};
ADDR ZeroADDR = {0};
PROTOCOL ZeroPROTOCOL = {0};
PORT ZeroPORT = {0};
IPSEC_QM_SA CurSA;
memset(&CurSA,0,sizeof(IPSEC_QM_SA));
ConvertSAToIPSecQMSA(&CurSA,pSA);
return((BOOLEAN)MatchQMSATemplate(pSATemplate,&CurSA));
}