You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2093 lines
60 KiB
2093 lines
60 KiB
/*++
|
|
|
|
Copyright (c) 1997, Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
pptp.c
|
|
|
|
Abstract:
|
|
|
|
Contains routines for managing the NAT's PPTP session-mappings
|
|
and for editing PPTP control-session messages.
|
|
|
|
Author:
|
|
|
|
Abolade Gbadegesin (t-abolag) 19-Aug-1997
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
//
|
|
// Structure: PPTP_PSEUDO_HEADER
|
|
//
|
|
// This structure serves as a pseudo-header for a PPTP control message.
|
|
// Its fields are initialized by 'NatBuildPseudoHeaderPptp' with pointers
|
|
// to the positions in a buffer-chain of the given PPTP header fields.
|
|
// This allows us to access header-fields through a structure even though
|
|
// the actual fields may be spread through the received buffer-chain.
|
|
//
|
|
|
|
typedef struct _PPTP_PSEUDO_HEADER {
|
|
PUSHORT PacketLength;
|
|
#if 0
|
|
PUSHORT PacketType; // currently unused
|
|
#endif
|
|
ULONG UNALIGNED * MagicCookie;
|
|
PUSHORT MessageType;
|
|
PUSHORT CallId;
|
|
PUSHORT PeerCallId;
|
|
} PPTP_PSEUDO_HEADER, *PPPTP_PSEUDO_HEADER;
|
|
|
|
|
|
//
|
|
// Structure: PPTP_DELETE_WORK_ITEM
|
|
//
|
|
|
|
typedef struct _PPTP_DELETE_WORK_ITEM {
|
|
ULONG64 PrivateKey;
|
|
ULONG64 PortKey;
|
|
PIO_WORKITEM IoWorkItem;
|
|
} PPTP_DELETE_WORK_ITEM, *PPPTP_DELETE_WORK_ITEM;
|
|
|
|
|
|
//
|
|
// GLOBAL DATA DEFINITIONS
|
|
//
|
|
|
|
NPAGED_LOOKASIDE_LIST PptpLookasideList;
|
|
LIST_ENTRY PptpMappingList[NatMaximumDirection];
|
|
KSPIN_LOCK PptpMappingLock;
|
|
IP_NAT_REGISTER_EDITOR PptpRegisterEditorClient;
|
|
IP_NAT_REGISTER_EDITOR PptpRegisterEditorServer;
|
|
|
|
|
|
//
|
|
// FORWARD DECLARATIONS
|
|
//
|
|
|
|
IPRcvBuf*
|
|
NatBuildPseudoHeaderPptp(
|
|
IPRcvBuf* ReceiveBuffer,
|
|
PLONG DataOffset,
|
|
PPPTP_PSEUDO_HEADER Header
|
|
);
|
|
|
|
VOID
|
|
NatDeleteHandlerWorkItemPptp(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PVOID Context
|
|
);
|
|
|
|
|
|
//
|
|
// PPTP MAPPING MANAGEMENT ROUTINES (alphabetically)
|
|
//
|
|
|
|
NTSTATUS
|
|
NatAllocatePublicPptpCallId(
|
|
ULONG64 PublicKey,
|
|
PUSHORT CallIdp,
|
|
PLIST_ENTRY *InsertionPoint OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine allocates a public call-ID for a PPTP mapping.
|
|
|
|
Arguments:
|
|
|
|
PublicKey - the public key (public and remote addresses) of the mapping
|
|
the public call ID is for
|
|
|
|
CallIdp - receives the allocated public call ID
|
|
|
|
InsertionPoint -- receives the correct location to insert the mapping
|
|
on the inbound list
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
Environment:
|
|
|
|
Invoked with 'PptpMappingLock' held by the caller.
|
|
|
|
--*/
|
|
|
|
{
|
|
USHORT CallId;
|
|
PLIST_ENTRY Link;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
|
|
CALLTRACE(("NatAllocatePublicPptpCallId\n"));
|
|
|
|
CallId = 1;
|
|
|
|
for (Link = PptpMappingList[NatInboundDirection].Flink;
|
|
Link != &PptpMappingList[NatInboundDirection];
|
|
Link = Link->Flink) {
|
|
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatInboundDirection]
|
|
);
|
|
if (PublicKey > Mapping->PublicKey) {
|
|
continue;
|
|
} else if (PublicKey < Mapping->PublicKey) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Primary key equal; see if the call-ID we've chosen
|
|
// collides with this one
|
|
//
|
|
|
|
if (CallId > Mapping->PublicCallId) {
|
|
continue;
|
|
} else if (CallId < Mapping->PublicCallId) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// The call-ID's collide; choose another and go on
|
|
//
|
|
|
|
++CallId;
|
|
}
|
|
|
|
if (Link == &PptpMappingList[NatInboundDirection] && !CallId) {
|
|
|
|
//
|
|
// We are at the end of the list, and all 64K-1 call-IDs are taken
|
|
//
|
|
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
*CallIdp = CallId;
|
|
if (InsertionPoint) {*InsertionPoint = Link;}
|
|
|
|
return STATUS_SUCCESS;
|
|
} // NatAllocatePublicPptpCallId
|
|
|
|
|
|
NTSTATUS
|
|
NatCreatePptpMapping(
|
|
ULONG RemoteAddress,
|
|
ULONG PrivateAddress,
|
|
USHORT CallId,
|
|
ULONG PublicAddress,
|
|
PUSHORT CallIdp,
|
|
IP_NAT_DIRECTION Direction,
|
|
USHORT PrivatePort,
|
|
USHORT PublicPort,
|
|
USHORT RemotePort,
|
|
PNAT_PPTP_MAPPING* MappingCreated
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine creates and initializes a mapping for a PPTP session.
|
|
The mapping is created when a client issues an IncomingCallRequest
|
|
message, at which point only the client's call-ID is available.
|
|
The mapping is therefore initially marked as half-open.
|
|
When the IncomingCallReply is received from the server, the server's
|
|
call-ID is recorded, and when the IncomingCallConnected is issued
|
|
by the client, the 'half-open' flag is cleared.
|
|
|
|
Arguments:
|
|
|
|
RemoteAddress - the address of the remote PPTP endpoint (server)
|
|
|
|
PrivateAddress - the address of the private PPTP endpoint (client)
|
|
|
|
CallId - the call-ID specified by the PPTP endpoint. For outbound,
|
|
this is the private call ID; for inbound, it is the remote
|
|
call ID
|
|
|
|
PublicAddress - the publicly-visible address for the PPTP session
|
|
|
|
CallIdp - the call-ID to be used publicly for the PPTP session,
|
|
or NULL if a call-ID should be allocated. Ignored for inbound.
|
|
|
|
Direction - the initial list that this mapping should be placed on
|
|
|
|
MappingCreated - receives the mapping created
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
Environment:
|
|
|
|
Invoked with 'PptpMappingLock' held by the caller.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY Link;
|
|
PLIST_ENTRY InsertionPoint;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
NTSTATUS Status;
|
|
PNAT_PPTP_MAPPING Temp;
|
|
|
|
TRACE(PPTP, ("NatCreatePptpMapping\n"));
|
|
|
|
//
|
|
// Allocate and initialize a new mapping, marking it half-open
|
|
// since we don't yet know what the remote machine's call-ID will be.
|
|
//
|
|
|
|
Mapping = ALLOCATE_PPTP_BLOCK();
|
|
if (!Mapping) {
|
|
ERROR(("NatCreatePptpMapping: allocation failed.\n"));
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
RtlZeroMemory(Mapping, sizeof(*Mapping));
|
|
Mapping->Flags = NAT_PPTP_FLAG_HALF_OPEN;
|
|
Mapping->PublicKey = MAKE_PPTP_KEY(RemoteAddress, PublicAddress);
|
|
Mapping->PrivateKey = MAKE_PPTP_KEY(RemoteAddress, PrivateAddress);
|
|
Mapping->PortKey =
|
|
MAKE_PPTP_PORT_KEY(PrivatePort, PublicPort, RemotePort);
|
|
|
|
if (NatOutboundDirection == Direction) {
|
|
|
|
//
|
|
// CallId refers to the private id -- select an id to use
|
|
// as the public id, and put the mapping on the inbound list
|
|
//
|
|
|
|
Mapping->PrivateCallId = CallId;
|
|
|
|
//
|
|
// Select the public call-ID to be used for the mapping.
|
|
// This may either be specified by the caller, or allocated automatically
|
|
// from the next available call-ID unused in our list of mappings.
|
|
//
|
|
|
|
if (CallIdp) {
|
|
|
|
//
|
|
// Use the caller-specified call-ID
|
|
//
|
|
|
|
Mapping->PublicCallId = *CallIdp;
|
|
|
|
if (NatLookupInboundPptpMapping(
|
|
Mapping->PublicKey,
|
|
Mapping->PublicCallId,
|
|
&InsertionPoint
|
|
)) {
|
|
|
|
//
|
|
// Conflicting call-ID
|
|
//
|
|
|
|
TRACE(
|
|
PPTP, (
|
|
"NatCreatePptpMapping: Conflicting Call-ID %i\n",
|
|
*CallIdp
|
|
));
|
|
|
|
FREE_PPTP_BLOCK(Mapping);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
//
|
|
// Find the next available call-ID
|
|
// by searching the list of inbound mappings
|
|
//
|
|
|
|
Status = NatAllocatePublicPptpCallId(
|
|
Mapping->PublicKey,
|
|
&Mapping->PublicCallId,
|
|
&InsertionPoint
|
|
);
|
|
|
|
if (!NT_SUCCESS(Status)) {
|
|
TRACE(
|
|
PPTP, (
|
|
"NatCreatePptpMapping: Unable to allocate public Call-Id\n"
|
|
));
|
|
FREE_PPTP_BLOCK(Mapping);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Insert the mapping in the inbound list
|
|
//
|
|
|
|
InsertTailList(InsertionPoint, &Mapping->Link[NatInboundDirection]);
|
|
|
|
//
|
|
// We can't insert the mapping in the outbound list
|
|
// until we have the remote call-ID; for now leave it off the list.
|
|
// Note that the mapping was marked 'half-open' above.
|
|
//
|
|
|
|
InitializeListHead(&Mapping->Link[NatOutboundDirection]);
|
|
|
|
} else {
|
|
|
|
//
|
|
// The call id refers to the remote call id. All that needed to
|
|
// be done here is to place the mapping on the outbound list
|
|
//
|
|
|
|
Mapping->RemoteCallId = CallId;
|
|
|
|
if (NatLookupOutboundPptpMapping(
|
|
Mapping->PrivateKey,
|
|
Mapping->RemoteCallId,
|
|
&InsertionPoint
|
|
)) {
|
|
|
|
//
|
|
// Duplicate mapping
|
|
//
|
|
|
|
TRACE(
|
|
PPTP, (
|
|
"NatCreatePptpMapping: Duplicate mapping 0x%016I64X/%i\n",
|
|
Mapping->PrivateKey,
|
|
Mapping->RemoteCallId
|
|
));
|
|
|
|
FREE_PPTP_BLOCK(Mapping);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
InsertTailList(InsertionPoint, &Mapping->Link[NatOutboundDirection]);
|
|
|
|
//
|
|
// We can't insert the mapping in the inbound list
|
|
// until we have the public call-ID; for now leave it off the list.
|
|
// Note that the mapping was marked 'half-open' above.
|
|
//
|
|
|
|
InitializeListHead(&Mapping->Link[NatInboundDirection]);
|
|
}
|
|
|
|
*MappingCreated = Mapping;
|
|
return STATUS_SUCCESS;
|
|
|
|
} // NatCreatePptpMapping
|
|
|
|
|
|
PNAT_PPTP_MAPPING
|
|
NatLookupInboundPptpMapping(
|
|
ULONG64 PublicKey,
|
|
USHORT PublicCallId,
|
|
PLIST_ENTRY* InsertionPoint
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to lookup a PPTP session mapping in the list
|
|
which is sorted for inbound-access, using <RemoteAddress # PublicAddress>
|
|
as the primary key and 'PublicCallId' as the secondary key.
|
|
|
|
Arguments:
|
|
|
|
PublicKey - the primary search key
|
|
|
|
PublicCallId - the publicly visible call-ID, which is the secondary key
|
|
|
|
InsertionPoint - receives the point at which the mapping should be inserted
|
|
if the mapping is not found.
|
|
|
|
Return Value:
|
|
|
|
PNAT_PPTP_MAPPING - the mapping found, if any.
|
|
|
|
Environment:
|
|
|
|
Invoked with 'PptpMappingLock' held by the caller.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY Link;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
|
|
TRACE(PER_PACKET, ("NatLookupInboundPptpMapping\n"));
|
|
|
|
for (Link = PptpMappingList[NatInboundDirection].Flink;
|
|
Link != &PptpMappingList[NatInboundDirection]; Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatInboundDirection]
|
|
);
|
|
if (PublicKey > Mapping->PublicKey) {
|
|
continue;
|
|
} else if (PublicKey < Mapping->PublicKey) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Primary keys equal, check secondary keys
|
|
//
|
|
|
|
if (PublicCallId > Mapping->PublicCallId) {
|
|
continue;
|
|
} else if (PublicCallId < Mapping->PublicCallId) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Secondary keys equal, we got it.
|
|
//
|
|
|
|
return Mapping;
|
|
}
|
|
|
|
//
|
|
// Mapping not found, return insertion point
|
|
//
|
|
|
|
if (InsertionPoint) { *InsertionPoint = Link; }
|
|
return NULL;
|
|
|
|
} // NatLookupInboundPptpMapping
|
|
|
|
|
|
PNAT_PPTP_MAPPING
|
|
NatLookupOutboundPptpMapping(
|
|
ULONG64 PrivateKey,
|
|
USHORT RemoteCallId,
|
|
PLIST_ENTRY* InsertionPoint
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to lookup a PPTP session mapping in the list
|
|
which is sorted for outbound-access, using <RemoteAddress # PrivateAddress>
|
|
as the primary key and 'RemoteCallId' as the secondary key.
|
|
|
|
Arguments:
|
|
|
|
PrivateKey - the primary search key
|
|
|
|
RemoteCallId - the remote endpoint's call-ID, which is the secondary key
|
|
|
|
InsertionPoint - receives the point at which the mapping should be inserted
|
|
if the mapping is not found.
|
|
|
|
Return Value:
|
|
|
|
PNAT_PPTP_MAPPING - the mapping found, if any.
|
|
|
|
Environment:
|
|
|
|
Invoked with 'PptpMappingLock' held by the caller.
|
|
|
|
--*/
|
|
|
|
{
|
|
PLIST_ENTRY Link;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
|
|
TRACE(PER_PACKET, ("NatLookupOutboundPptpMapping\n"));
|
|
|
|
for (Link = PptpMappingList[NatOutboundDirection].Flink;
|
|
Link != &PptpMappingList[NatOutboundDirection]; Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatOutboundDirection]
|
|
);
|
|
if (PrivateKey > Mapping->PrivateKey) {
|
|
continue;
|
|
} else if (PrivateKey < Mapping->PrivateKey) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Primary keys equal, check secondary keys
|
|
//
|
|
|
|
if (RemoteCallId > Mapping->RemoteCallId) {
|
|
continue;
|
|
} else if (RemoteCallId < Mapping->RemoteCallId) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Secondary keys equal, we got it.
|
|
//
|
|
|
|
return Mapping;
|
|
}
|
|
|
|
|
|
//
|
|
// Mapping not found, return insertion point
|
|
//
|
|
|
|
if (InsertionPoint) { *InsertionPoint = Link; }
|
|
return NULL;
|
|
|
|
} // NatLookupOutboundPptpMapping
|
|
|
|
|
|
//
|
|
// PPTP EDITOR ROUTINES (alphabetically)
|
|
//
|
|
|
|
#define RECVBUFFER ((IPRcvBuf*)ReceiveBuffer)
|
|
|
|
//
|
|
// Macro: PPTP_HEADER_FIELD
|
|
//
|
|
|
|
#define PPTP_HEADER_FIELD(ReceiveBuffer, DataOffsetp, Header, Field, Type) \
|
|
FIND_HEADER_FIELD( \
|
|
ReceiveBuffer, DataOffsetp, Header, Field, PPTP_HEADER, Type \
|
|
)
|
|
|
|
|
|
IPRcvBuf*
|
|
NatBuildPseudoHeaderPptp(
|
|
IPRcvBuf* ReceiveBuffer,
|
|
PLONG DataOffset,
|
|
PPPTP_PSEUDO_HEADER Header
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to initialize a pseudo-header with pointers
|
|
to the fields of a PPTP header.
|
|
|
|
Arguments:
|
|
|
|
ReceiveBuffer - the buffer-chain containing the PPTP message
|
|
|
|
DataOffset - on input, contains the offset to the beginning of the
|
|
PPTP header. On output, contains a (negative) value indicating
|
|
the offset to the beginning of the same PPTP header in the returned
|
|
'IPRcvBuf'. Adding the 'PacketLength' to the value gives the beginning
|
|
of the next PPTP message in the buffer chain.
|
|
|
|
Header - receives the header-field pointers.
|
|
|
|
Return Value:
|
|
|
|
IPRcvBuf* - a pointer to the buffer in the chain from which the last
|
|
header-field was read. Returns NULL on failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
|
|
//
|
|
// Note that the pseudo-header's fields must be initialized in-order,
|
|
// i.e. fields appearing earlier in PPTP header must be set before
|
|
// fields appearing later in the PPTP header.
|
|
// (See comment on 'PPTP_HEADER_FIELD' for more details).
|
|
//
|
|
|
|
PPTP_HEADER_FIELD(ReceiveBuffer, DataOffset, Header, PacketLength, PUSHORT);
|
|
if (!ReceiveBuffer) { return NULL; }
|
|
if (!*Header->PacketLength) {return NULL;}
|
|
|
|
PPTP_HEADER_FIELD(ReceiveBuffer, DataOffset, Header, MagicCookie, PULONG);
|
|
if (!ReceiveBuffer) { return NULL; }
|
|
if (*Header->MagicCookie != PPTP_MAGIC_COOKIE) { return NULL; }
|
|
|
|
PPTP_HEADER_FIELD(ReceiveBuffer, DataOffset, Header, MessageType, PUSHORT);
|
|
if (!ReceiveBuffer) { return NULL; }
|
|
|
|
PPTP_HEADER_FIELD(ReceiveBuffer, DataOffset, Header, CallId, PUSHORT);
|
|
if (!ReceiveBuffer) { return NULL; }
|
|
|
|
PPTP_HEADER_FIELD(ReceiveBuffer, DataOffset, Header, PeerCallId, PUSHORT);
|
|
if (!ReceiveBuffer) { return NULL; }
|
|
|
|
//
|
|
// Return the updated 'ReceiveBuffer'.
|
|
// Note that any call to 'PPTP_HEADER_FIELD' above may fail
|
|
// if it hits the end of the buffer chain while looking for a field.
|
|
//
|
|
|
|
return ReceiveBuffer;
|
|
|
|
} // NatBuildPseudoHeaderPptp
|
|
|
|
|
|
NTSTATUS
|
|
NatClientToServerDataHandlerPptp(
|
|
IN PVOID InterfaceHandle,
|
|
IN PVOID SessionHandle,
|
|
IN PVOID DataHandle,
|
|
IN PVOID EditorContext,
|
|
IN PVOID EditorSessionContext,
|
|
IN PVOID ReceiveBuffer,
|
|
IN ULONG DataOffset,
|
|
IN IP_NAT_DIRECTION Direction
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked for each TCP segment sent from the client
|
|
to the server of a PPTP control channel.
|
|
|
|
The routine is responsible for creating PPTP mappings to allow
|
|
the NAT to translate PPTP data-connections, and for translating
|
|
the 'CallId' field of PPTP control-messages.
|
|
|
|
We also use the messages seen to detect when tunnels are to be torn down.
|
|
|
|
Arguments:
|
|
|
|
InterfaceHandle - handle to the outgoing NAT_INTERFACE
|
|
|
|
SessionHandle - the connection's NAT_DYNAMIC_MAPPING
|
|
|
|
DataHandle - the packet's NAT_XLATE_CONTEXT
|
|
|
|
EditorContext - unused
|
|
|
|
EditorSessionContext - unused
|
|
|
|
ReceiveBuffer - contains the received packet
|
|
|
|
DataOffset - offset of the protocol data in 'ReceiveBuffer
|
|
|
|
Direction - the direction of the packet (inbound or outbound)
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
--*/
|
|
|
|
{
|
|
PPTP_PSEUDO_HEADER Header;
|
|
PLIST_ENTRY Link;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
ULONG64 PortKey;
|
|
ULONG PrivateAddress;
|
|
ULONG64 PrivateKey;
|
|
USHORT PrivatePort;
|
|
ULONG PublicAddress;
|
|
USHORT PublicPort;
|
|
ULONG RemoteAddress;
|
|
USHORT RemotePort;
|
|
ULONG64 Key;
|
|
NTSTATUS status;
|
|
|
|
CALLTRACE(("NatClientToServerDataHandlerPptp\n"));
|
|
|
|
//
|
|
// Perform processing for each PPTP control message in the packet
|
|
//
|
|
|
|
for (ReceiveBuffer =
|
|
NatBuildPseudoHeaderPptp(RECVBUFFER, &DataOffset, &Header);
|
|
ReceiveBuffer;
|
|
ReceiveBuffer =
|
|
NatBuildPseudoHeaderPptp(RECVBUFFER, &DataOffset, &Header)
|
|
) {
|
|
|
|
//
|
|
// Process any client-to-server messages which require translation
|
|
//
|
|
|
|
switch(NTOHS(*Header.MessageType)) {
|
|
|
|
case PPTP_OUTGOING_CALL_REQUEST: {
|
|
TRACE(PPTP, ("OutgoingCallRequest\n"));
|
|
|
|
//
|
|
// Create a NAT_PPTP_MAPPING for the PPTP session.
|
|
//
|
|
|
|
PptpRegisterEditorClient.QueryInfoSession(
|
|
SessionHandle,
|
|
&PrivateAddress,
|
|
&PrivatePort,
|
|
&RemoteAddress,
|
|
&RemotePort,
|
|
&PublicAddress,
|
|
&PublicPort,
|
|
NULL
|
|
);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
status =
|
|
NatCreatePptpMapping(
|
|
RemoteAddress,
|
|
PrivateAddress,
|
|
*Header.CallId,
|
|
PublicAddress,
|
|
NULL,
|
|
Direction,
|
|
PrivatePort,
|
|
PublicPort,
|
|
RemotePort,
|
|
&Mapping
|
|
);
|
|
if (NT_SUCCESS(status) && NatOutboundDirection == Direction) {
|
|
|
|
//
|
|
// For outbound messages, 'CallId' corresponds here to
|
|
// 'PrivateCallId': replace the private call-ID in the message
|
|
// with the public call-ID allocated in the new mapping.
|
|
//
|
|
|
|
NatEditorEditShortSession(
|
|
DataHandle, Header.CallId, Mapping->PublicCallId
|
|
);
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
if (!NT_SUCCESS(status)) { return STATUS_UNSUCCESSFUL; }
|
|
break;
|
|
}
|
|
|
|
case PPTP_CALL_CLEAR_REQUEST: {
|
|
|
|
BOOLEAN Found = FALSE;
|
|
TRACE(PPTP, ("CallClearRequest\n"));
|
|
|
|
//
|
|
// Look up the NAT_PPTP_MAPPING for the PPTP session.
|
|
|
|
PptpRegisterEditorClient.QueryInfoSession(
|
|
SessionHandle,
|
|
&PrivateAddress,
|
|
NULL,
|
|
&RemoteAddress,
|
|
NULL,
|
|
&PublicAddress,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if( NatOutboundDirection == Direction) {
|
|
|
|
//
|
|
// 'CallId' corresponds here to 'PrivateCallId',
|
|
// so we retrieve 'PrivateAddress' and 'RemoteAddress',
|
|
// which together comprise 'PrivateKey', and use that key
|
|
// to search the inbound-list.
|
|
//
|
|
|
|
Key = MAKE_PPTP_KEY(RemoteAddress, PrivateAddress);
|
|
|
|
//
|
|
// Search exhaustively for PrivateCallId in the inbound list.
|
|
//
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
for (Link = PptpMappingList[NatInboundDirection].Flink;
|
|
Link != &PptpMappingList[NatInboundDirection];
|
|
Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatInboundDirection]
|
|
);
|
|
if (Key != Mapping->PrivateKey ||
|
|
*Header.CallId != Mapping->PrivateCallId) {
|
|
continue;
|
|
}
|
|
Found = TRUE; break;
|
|
}
|
|
if (Found) {
|
|
NatEditorEditShortSession(
|
|
DataHandle, Header.CallId, Mapping->PublicCallId
|
|
);
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
if (!Found) { return STATUS_UNSUCCESSFUL; }
|
|
|
|
} else {
|
|
|
|
//
|
|
// 'CallId' corresponds here to 'RemoteCallId',
|
|
// so we retrieve 'PublicAddress' and 'RemoteAddress',
|
|
// which together comprise 'PublicKey', and use that key
|
|
// to search the outbound-list.
|
|
//
|
|
|
|
Key = MAKE_PPTP_KEY(RemoteAddress, PublicAddress);
|
|
|
|
//
|
|
// Search exhaustively for RemoteCallId in the outbound list.
|
|
//
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
for (Link = PptpMappingList[NatOutboundDirection].Flink;
|
|
Link != &PptpMappingList[NatOutboundDirection];
|
|
Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatOutboundDirection]
|
|
);
|
|
if (Key != Mapping->PublicKey ||
|
|
*Header.CallId != Mapping->RemoteCallId) {
|
|
continue;
|
|
}
|
|
Found = TRUE; break;
|
|
}
|
|
if (Found) {
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
if (!Found) { return STATUS_UNSUCCESSFUL; }
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PPTP_ECHO_REQUEST:
|
|
case PPTP_ECHO_REPLY: {
|
|
TRACE(PPTP, ("EchoRequest / EchoReply\n"));
|
|
|
|
//
|
|
// Search for all PPTP mappings that belong to this
|
|
// control connection in order to update their
|
|
// last access time.
|
|
//
|
|
|
|
PptpRegisterEditorClient.QueryInfoSession(
|
|
SessionHandle,
|
|
&PrivateAddress,
|
|
&PrivatePort,
|
|
&RemoteAddress,
|
|
&RemotePort,
|
|
NULL,
|
|
&PublicPort,
|
|
NULL
|
|
);
|
|
|
|
PrivateKey = MAKE_PPTP_KEY(RemoteAddress, PrivateAddress);
|
|
PortKey =
|
|
MAKE_PPTP_PORT_KEY(PrivatePort, PublicPort, RemotePort);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
|
|
//
|
|
// Since we don't care about updating the timestamp for
|
|
// half-open connections we only need to search the inbound
|
|
// list. (It's OK that this may update the timestamp on some
|
|
// half-open entries, though.)
|
|
//
|
|
|
|
for (Link = PptpMappingList[NatInboundDirection].Flink;
|
|
Link != &PptpMappingList[NatInboundDirection];
|
|
Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatInboundDirection]
|
|
);
|
|
|
|
if (PrivateKey != Mapping->PrivateKey
|
|
|| PortKey != Mapping->PortKey) {
|
|
continue;
|
|
}
|
|
|
|
KeQueryTickCount((PLARGE_INTEGER)&Mapping->LastAccessTime);
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Advance to the next message, if any
|
|
//
|
|
|
|
DataOffset += NTOHS(*Header.PacketLength);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // NatClientToServerDataHandlerPptp
|
|
|
|
|
|
NTSTATUS
|
|
NatDeleteHandlerPptp(
|
|
IN PVOID InterfaceHandle,
|
|
IN PVOID SessionHandle,
|
|
IN PVOID EditorContext,
|
|
IN PVOID EditorSessionContext
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked when a PPTP control connection is deleted.
|
|
At this point we queue an executive work-item to mark all PPTP tunnel
|
|
mappings as disconnected. Note that we cannot mark them during this call,
|
|
because to do so we need to acquire the interface lock, and we cannot
|
|
do so from this context, because we are a 'DeleteHandler' and hence
|
|
we may be called with the interface-lock already held.
|
|
|
|
Arguments:
|
|
|
|
SessionHandle - used to obtain information on the session.
|
|
|
|
EditorContext - unused
|
|
|
|
EditorSessionContext - unused
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
ULONG PrivateAddress;
|
|
ULONG64 PrivateKey;
|
|
USHORT PrivatePort;
|
|
USHORT PublicPort;
|
|
ULONG RemoteAddress;
|
|
USHORT RemotePort;
|
|
PPPTP_DELETE_WORK_ITEM WorkItem;
|
|
|
|
TRACE(PPTP, ("NatDeleteHandlerPptp\n"));
|
|
|
|
//
|
|
// Get information about the session being deleted
|
|
//
|
|
|
|
NatQueryInformationMapping(
|
|
(PNAT_DYNAMIC_MAPPING)SessionHandle,
|
|
NULL,
|
|
&PrivateAddress,
|
|
&PrivatePort,
|
|
&RemoteAddress,
|
|
&RemotePort,
|
|
NULL,
|
|
&PublicPort,
|
|
NULL
|
|
);
|
|
|
|
PrivateKey = MAKE_PPTP_KEY(RemoteAddress, PrivateAddress);
|
|
|
|
//
|
|
// Allocate a work-queue item which will mark as disconnected
|
|
// all the tunnels of the control session being deleted
|
|
//
|
|
|
|
WorkItem =
|
|
ExAllocatePoolWithTag(
|
|
NonPagedPool, sizeof(PPTP_DELETE_WORK_ITEM), NAT_TAG_WORK_ITEM
|
|
);
|
|
if (!WorkItem) { return STATUS_UNSUCCESSFUL; }
|
|
|
|
WorkItem->PrivateKey = PrivateKey;
|
|
WorkItem->PortKey = MAKE_PPTP_PORT_KEY(PrivatePort, PublicPort, RemotePort);
|
|
WorkItem->IoWorkItem = IoAllocateWorkItem(NatDeviceObject);
|
|
if (!WorkItem->IoWorkItem) {
|
|
ExFreePool(WorkItem);
|
|
return STATUS_UNSUCCESSFUL;
|
|
}
|
|
|
|
//
|
|
// Queue the work item, and return
|
|
//
|
|
|
|
IoQueueWorkItem(
|
|
WorkItem->IoWorkItem,
|
|
NatDeleteHandlerWorkItemPptp,
|
|
DelayedWorkQueue,
|
|
WorkItem
|
|
);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // NatDeleteHandlerPptp
|
|
|
|
|
|
VOID
|
|
NatDeleteHandlerWorkItemPptp(
|
|
IN PDEVICE_OBJECT DeviceObject,
|
|
IN PVOID Context
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to complete the deletion of a PPTP control session.
|
|
It marks all the control-session's tunnels as disconnected.
|
|
|
|
Arguments:
|
|
|
|
DeviceObject - unused.
|
|
|
|
Context - contains a PPTP_DELETE_WORK_ITEM from 'NatDeleteHandlerPptp'
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
--*/
|
|
|
|
{
|
|
KIRQL Irql;
|
|
PLIST_ENTRY Link;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
PPPTP_DELETE_WORK_ITEM WorkItem;
|
|
|
|
TRACE(PPTP, ("NatDeleteHandlerWorkItemPptp\n"));
|
|
|
|
WorkItem = (PPPTP_DELETE_WORK_ITEM)Context;
|
|
IoFreeWorkItem(WorkItem->IoWorkItem);
|
|
|
|
//
|
|
// All tunnel mappings over the given control connection
|
|
// must now be marked as disconnected.
|
|
// Walk the inbound and outbound list.
|
|
//
|
|
|
|
KeAcquireSpinLock(&PptpMappingLock, &Irql);
|
|
|
|
for (Link = PptpMappingList[NatInboundDirection].Flink;
|
|
Link != &PptpMappingList[NatInboundDirection]; Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatInboundDirection]
|
|
);
|
|
|
|
if (WorkItem->PrivateKey != Mapping->PrivateKey
|
|
|| WorkItem->PortKey != Mapping->PortKey) {
|
|
continue;
|
|
}
|
|
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
}
|
|
|
|
for (Link = PptpMappingList[NatOutboundDirection].Flink;
|
|
Link != &PptpMappingList[NatOutboundDirection]; Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatOutboundDirection]
|
|
);
|
|
|
|
if (WorkItem->PrivateKey != Mapping->PrivateKey
|
|
|| WorkItem->PortKey != Mapping->PortKey) {
|
|
continue;
|
|
}
|
|
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
}
|
|
|
|
KeReleaseSpinLock(&PptpMappingLock, Irql);
|
|
|
|
//
|
|
// Delete the work-item
|
|
//
|
|
|
|
ExFreePool(WorkItem);
|
|
|
|
} // NatDeleteHandlerWorkItemPptp
|
|
|
|
|
|
NTSTATUS
|
|
NatInboundDataHandlerPptpClient(
|
|
IN PVOID InterfaceHandle,
|
|
IN PVOID SessionHandle,
|
|
IN PVOID DataHandle,
|
|
IN PVOID EditorContext,
|
|
IN PVOID EditorSessionContext,
|
|
IN PVOID RecvBuffer,
|
|
IN ULONG DataOffset
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked for each TCP segment received by a private
|
|
PPTP client machine
|
|
|
|
Arguments:
|
|
|
|
InterfaceHandle - the receiving NAT_INTERFACE
|
|
|
|
SessionHandle - the connection's NAT_DYNAMIC_MAPPING
|
|
|
|
DataHandle - the packet's NAT_XLATE_CONTEXT
|
|
|
|
EditorContext - unused
|
|
|
|
EditorSessionContext - unused
|
|
|
|
RecvBuffer - contains the received packet
|
|
|
|
DataOffset - offset of the protocol data in 'ReceiveBuffer'
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
--*/
|
|
|
|
{
|
|
CALLTRACE(("NatInboundDataHandlerPptpClient\n"));
|
|
|
|
return
|
|
NatServerToClientDataHandlerPptp(
|
|
InterfaceHandle,
|
|
SessionHandle,
|
|
DataHandle,
|
|
EditorContext,
|
|
EditorSessionContext,
|
|
RecvBuffer,
|
|
DataOffset,
|
|
NatInboundDirection
|
|
);
|
|
} // NatInboundDataHandlerPptpClient
|
|
|
|
|
|
NTSTATUS
|
|
NatInboundDataHandlerPptpServer(
|
|
IN PVOID InterfaceHandle,
|
|
IN PVOID SessionHandle,
|
|
IN PVOID DataHandle,
|
|
IN PVOID EditorContext,
|
|
IN PVOID EditorSessionContext,
|
|
IN PVOID RecvBuffer,
|
|
IN ULONG DataOffset
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked for each TCP segment received by a private
|
|
PPTP server machine
|
|
|
|
Arguments:
|
|
|
|
InterfaceHandle - the receiving NAT_INTERFACE
|
|
|
|
SessionHandle - the connection's NAT_DYNAMIC_MAPPING
|
|
|
|
DataHandle - the packet's NAT_XLATE_CONTEXT
|
|
|
|
EditorContext - unused
|
|
|
|
EditorSessionContext - unused
|
|
|
|
RecvBuffer - contains the received packet
|
|
|
|
DataOffset - offset of the protocol data in 'ReceiveBuffer'
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
--*/
|
|
|
|
{
|
|
CALLTRACE(("NatInboundDataHandlerPptpServer\n"));
|
|
|
|
return
|
|
NatClientToServerDataHandlerPptp(
|
|
InterfaceHandle,
|
|
SessionHandle,
|
|
DataHandle,
|
|
EditorContext,
|
|
EditorSessionContext,
|
|
RecvBuffer,
|
|
DataOffset,
|
|
NatInboundDirection
|
|
);
|
|
} // NatInboundDataHandlerPptpServer
|
|
|
|
|
|
NTSTATUS
|
|
NatInitializePptpManagement(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine initializes the PPTP management module and, in the process,
|
|
registers the PPTP control-session editor with the NAT.
|
|
|
|
Arguments:
|
|
|
|
none.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure.
|
|
|
|
--*/
|
|
|
|
{
|
|
NTSTATUS Status;
|
|
CALLTRACE(("NatInitializePptpManangement\n"));
|
|
|
|
KeInitializeSpinLock(&PptpMappingLock);
|
|
InitializeListHead(&PptpMappingList[NatInboundDirection]);
|
|
InitializeListHead(&PptpMappingList[NatOutboundDirection]);
|
|
|
|
ExInitializeNPagedLookasideList(
|
|
&PptpLookasideList,
|
|
NatAllocateFunction,
|
|
NULL,
|
|
0,
|
|
sizeof(NAT_PPTP_MAPPING),
|
|
NAT_TAG_PPTP,
|
|
PPTP_LOOKASIDE_DEPTH
|
|
);
|
|
|
|
PptpRegisterEditorClient.Version = IP_NAT_VERSION;
|
|
PptpRegisterEditorClient.Flags = 0;
|
|
PptpRegisterEditorClient.Protocol = NAT_PROTOCOL_TCP;
|
|
PptpRegisterEditorClient.Port = NTOHS(PPTP_CONTROL_PORT);
|
|
PptpRegisterEditorClient.Direction = NatOutboundDirection;
|
|
PptpRegisterEditorClient.EditorContext = NULL;
|
|
PptpRegisterEditorClient.CreateHandler = NULL;
|
|
PptpRegisterEditorClient.DeleteHandler = NatDeleteHandlerPptp;
|
|
PptpRegisterEditorClient.ForwardDataHandler = NatOutboundDataHandlerPptpClient;
|
|
PptpRegisterEditorClient.ReverseDataHandler = NatInboundDataHandlerPptpClient;
|
|
Status = NatCreateEditor(&PptpRegisterEditorClient);
|
|
if (!NT_SUCCESS(Status)) {
|
|
return Status;
|
|
}
|
|
|
|
PptpRegisterEditorServer.Version = IP_NAT_VERSION;
|
|
PptpRegisterEditorServer.Flags = 0;
|
|
PptpRegisterEditorServer.Protocol = NAT_PROTOCOL_TCP;
|
|
PptpRegisterEditorServer.Port = NTOHS(PPTP_CONTROL_PORT);
|
|
PptpRegisterEditorServer.Direction = NatInboundDirection;
|
|
PptpRegisterEditorServer.EditorContext = NULL;
|
|
PptpRegisterEditorServer.CreateHandler = NULL;
|
|
PptpRegisterEditorServer.DeleteHandler = NatDeleteHandlerPptp;
|
|
PptpRegisterEditorServer.ForwardDataHandler = NatInboundDataHandlerPptpServer;
|
|
PptpRegisterEditorServer.ReverseDataHandler = NatOutboundDataHandlerPptpServer;
|
|
return NatCreateEditor(&PptpRegisterEditorServer);
|
|
} // NatInitializePptpManagement
|
|
|
|
|
|
NTSTATUS
|
|
NatOutboundDataHandlerPptpClient(
|
|
IN PVOID InterfaceHandle,
|
|
IN PVOID SessionHandle,
|
|
IN PVOID DataHandle,
|
|
IN PVOID EditorContext,
|
|
IN PVOID EditorSessionContext,
|
|
IN PVOID RecvBuffer,
|
|
IN ULONG DataOffset
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked for each TCP segment sent from a private
|
|
PPTP client machine
|
|
|
|
Arguments:
|
|
|
|
InterfaceHandle - the receiving NAT_INTERFACE
|
|
|
|
SessionHandle - the connection's NAT_DYNAMIC_MAPPING
|
|
|
|
DataHandle - the packet's NAT_XLATE_CONTEXT
|
|
|
|
EditorContext - unused
|
|
|
|
EditorSessionContext - unused
|
|
|
|
RecvBuffer - contains the received packet
|
|
|
|
DataOffset - offset of the protocol data in 'ReceiveBuffer'
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
--*/
|
|
|
|
{
|
|
CALLTRACE(("NatOutboundDataHandlerPptpClient\n"));
|
|
|
|
return
|
|
NatClientToServerDataHandlerPptp(
|
|
InterfaceHandle,
|
|
SessionHandle,
|
|
DataHandle,
|
|
EditorContext,
|
|
EditorSessionContext,
|
|
RecvBuffer,
|
|
DataOffset,
|
|
NatOutboundDirection
|
|
);
|
|
} // NatOutboundDataHandlerPptpClient
|
|
|
|
|
|
NTSTATUS
|
|
NatOutboundDataHandlerPptpServer(
|
|
IN PVOID InterfaceHandle,
|
|
IN PVOID SessionHandle,
|
|
IN PVOID DataHandle,
|
|
IN PVOID EditorContext,
|
|
IN PVOID EditorSessionContext,
|
|
IN PVOID RecvBuffer,
|
|
IN ULONG DataOffset
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked for each TCP segment sent from a private
|
|
PPTP server machine
|
|
|
|
Arguments:
|
|
|
|
InterfaceHandle - the receiving NAT_INTERFACE
|
|
|
|
SessionHandle - the connection's NAT_DYNAMIC_MAPPING
|
|
|
|
DataHandle - the packet's NAT_XLATE_CONTEXT
|
|
|
|
EditorContext - unused
|
|
|
|
EditorSessionContext - unused
|
|
|
|
RecvBuffer - contains the received packet
|
|
|
|
DataOffset - offset of the protocol data in 'ReceiveBuffer'
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
--*/
|
|
|
|
{
|
|
CALLTRACE(("NatOutboundDataHandlerPptpServer\n"));
|
|
|
|
return
|
|
NatServerToClientDataHandlerPptp(
|
|
InterfaceHandle,
|
|
SessionHandle,
|
|
DataHandle,
|
|
EditorContext,
|
|
EditorSessionContext,
|
|
RecvBuffer,
|
|
DataOffset,
|
|
NatOutboundDirection
|
|
);
|
|
} // NatOutboundDataHandlerPptpServer
|
|
|
|
|
|
NTSTATUS
|
|
NatServerToClientDataHandlerPptp(
|
|
IN PVOID InterfaceHandle,
|
|
IN PVOID SessionHandle,
|
|
IN PVOID DataHandle,
|
|
IN PVOID EditorContext,
|
|
IN PVOID EditorSessionContext,
|
|
IN PVOID ReceiveBuffer,
|
|
IN ULONG DataOffset,
|
|
IN IP_NAT_DIRECTION Direction
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is invoked for each TCP segment sent from the server
|
|
to the client on a PPTP control channel
|
|
|
|
The routine is responsible for translating inbound PPTP control messages.
|
|
It also uses the messages seen to detect when tunnels are to be torn down.
|
|
|
|
|
|
Arguments:
|
|
|
|
InterfaceHandle - the receiving NAT_INTERFACE
|
|
|
|
SessionHandle - the connection's NAT_DYNAMIC_MAPPING
|
|
|
|
DataHandle - the packet's NAT_XLATE_CONTEXT
|
|
|
|
EditorContext - unused
|
|
|
|
EditorSessionContext - unused
|
|
|
|
ReceiveBuffer - contains the received packet
|
|
|
|
DataOffset - offset of the protocol data in 'ReceiveBuffer'
|
|
|
|
Direction - the direction of the segment (inbound or outbound)
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - indicates success/failure
|
|
|
|
--*/
|
|
|
|
{
|
|
PPTP_PSEUDO_HEADER Header;
|
|
PLIST_ENTRY InsertionPoint;
|
|
PLIST_ENTRY Link;
|
|
BOOLEAN Found = FALSE;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
ULONG64 PortKey;
|
|
ULONG PrivateAddress;
|
|
ULONG64 PrivateKey;
|
|
USHORT PrivatePort;
|
|
ULONG PublicAddress;
|
|
ULONG64 PublicKey;
|
|
USHORT PublicPort;
|
|
ULONG RemoteAddress;
|
|
USHORT RemotePort;
|
|
NTSTATUS status;
|
|
|
|
CALLTRACE(("NatServerToClientDataHandlerPptp\n"));
|
|
|
|
//
|
|
// Perform processing for each PPTP control message in the packet
|
|
//
|
|
|
|
for (ReceiveBuffer =
|
|
NatBuildPseudoHeaderPptp(RECVBUFFER, &DataOffset, &Header);
|
|
ReceiveBuffer;
|
|
ReceiveBuffer =
|
|
NatBuildPseudoHeaderPptp(RECVBUFFER, &DataOffset, &Header)) {
|
|
|
|
//
|
|
// Process any server-to-client messages which require translation
|
|
//
|
|
|
|
switch(NTOHS(*Header.MessageType)) {
|
|
|
|
case PPTP_OUTGOING_CALL_REPLY: {
|
|
TRACE(PPTP, ("OutgoingCallReply\n"));
|
|
|
|
//
|
|
// Look up the NAT_PPTP_MAPPING for the PPTP session,
|
|
// record the peer's call ID, mark the session as open,
|
|
// and possibly translate the 'PeerCallId'
|
|
//
|
|
|
|
PptpRegisterEditorClient.QueryInfoSession(
|
|
SessionHandle,
|
|
&PrivateAddress,
|
|
NULL,
|
|
&RemoteAddress,
|
|
NULL,
|
|
&PublicAddress,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
PublicKey = MAKE_PPTP_KEY(RemoteAddress, PublicAddress);
|
|
|
|
if (NatInboundDirection == Direction) {
|
|
|
|
//
|
|
// 'PeerCallId' corresponds here to 'PublicCallId',
|
|
// so search for a mapping using 'PublicKey'
|
|
//
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
Mapping =
|
|
NatLookupInboundPptpMapping(
|
|
PublicKey,
|
|
*Header.PeerCallId,
|
|
NULL
|
|
);
|
|
|
|
if (Mapping
|
|
&& !NAT_PPTP_DISCONNECTED(Mapping)
|
|
&& NAT_PPTP_HALF_OPEN(Mapping)) {
|
|
|
|
ASSERT(0 == Mapping->RemoteCallId);
|
|
ASSERT(IsListEmpty(&Mapping->Link[NatOutboundDirection]));
|
|
|
|
Mapping->RemoteCallId = *Header.CallId;
|
|
if (NatLookupOutboundPptpMapping(
|
|
Mapping->PrivateKey,
|
|
Mapping->RemoteCallId,
|
|
&InsertionPoint
|
|
)) {
|
|
|
|
//
|
|
// A duplicate exists; disconnect the mapping.
|
|
//
|
|
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
Mapping = NULL;
|
|
} else {
|
|
|
|
//
|
|
// Insert the mapping on the outbound list.
|
|
//
|
|
|
|
InsertTailList(
|
|
InsertionPoint,
|
|
&Mapping->Link[NatOutboundDirection]
|
|
);
|
|
Mapping->Flags &= ~NAT_PPTP_FLAG_HALF_OPEN;
|
|
}
|
|
}
|
|
|
|
if (Mapping && !NAT_PPTP_DISCONNECTED(Mapping)) {
|
|
|
|
//
|
|
// Replace the public call-ID in the message
|
|
// with the original private call-ID
|
|
//
|
|
|
|
NatEditorEditShortSession(
|
|
DataHandle,
|
|
Header.PeerCallId,
|
|
Mapping->PrivateCallId
|
|
);
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
|
|
} else {
|
|
|
|
//
|
|
// 'PeerCallId' corresponds here to 'RemoteCallId',
|
|
// so we search for a mapping using 'PrivateKey'
|
|
//
|
|
|
|
PrivateKey = MAKE_PPTP_KEY(RemoteAddress, PrivateAddress);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
Mapping =
|
|
NatLookupOutboundPptpMapping(
|
|
PrivateKey,
|
|
*Header.PeerCallId,
|
|
NULL
|
|
);
|
|
|
|
if (Mapping
|
|
&& !NAT_PPTP_DISCONNECTED(Mapping)
|
|
&& NAT_PPTP_HALF_OPEN(Mapping)) {
|
|
|
|
ASSERT(0 == Mapping->PrivateCallId);
|
|
ASSERT(0 == Mapping->PublicCallId);
|
|
ASSERT(IsListEmpty(&Mapping->Link[NatInboundDirection]));
|
|
|
|
Mapping->PrivateCallId = *Header.CallId;
|
|
|
|
//
|
|
// Allocate a public call ID for the mapping.
|
|
//
|
|
|
|
status = NatAllocatePublicPptpCallId(
|
|
PublicKey,
|
|
&Mapping->PublicCallId,
|
|
&InsertionPoint
|
|
);
|
|
|
|
if (NT_SUCCESS(status)) {
|
|
|
|
//
|
|
// Insert the mapping on the inbound list and
|
|
// mark the mapping as fully open
|
|
//
|
|
|
|
InsertTailList(
|
|
InsertionPoint,
|
|
&Mapping->Link[NatInboundDirection]
|
|
);
|
|
Mapping->Flags &= ~NAT_PPTP_FLAG_HALF_OPEN;
|
|
|
|
} else {
|
|
|
|
//
|
|
// Unable to allocate the public call ID --
|
|
// disconnect the mapping
|
|
//
|
|
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
Mapping = NULL;
|
|
}
|
|
}
|
|
|
|
if (Mapping && !NAT_PPTP_DISCONNECTED(Mapping)) {
|
|
|
|
//
|
|
// Translate the call-ID
|
|
//
|
|
|
|
NatEditorEditShortSession(
|
|
DataHandle,
|
|
Header.CallId,
|
|
Mapping->PublicCallId
|
|
);
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
}
|
|
|
|
if (!Mapping) { return STATUS_UNSUCCESSFUL; }
|
|
break;
|
|
}
|
|
|
|
case PPTP_SET_LINK_INFO:
|
|
case PPTP_INCOMING_CALL_CONNECTED:
|
|
case PPTP_WAN_ERROR_NOTIFY: {
|
|
TRACE(PPTP, ("SetLinkInfo|IncomingCallConnected|WanErrorNotify\n"));
|
|
|
|
if (NatInboundDirection == Direction) {
|
|
|
|
//
|
|
// Look up the NAT_PPTP_MAPPING for the PPTP session
|
|
// and translate the 'CallId' field with the private call-ID
|
|
// 'CallId' corresponds here to 'PublicCallId',
|
|
// so we search for a mapping using 'PublicKey'.
|
|
//
|
|
|
|
PptpRegisterEditorClient.QueryInfoSession(
|
|
SessionHandle,
|
|
NULL,
|
|
NULL,
|
|
&RemoteAddress,
|
|
NULL,
|
|
&PublicAddress,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
PublicKey = MAKE_PPTP_KEY(RemoteAddress, PublicAddress);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
Mapping =
|
|
NatLookupInboundPptpMapping(
|
|
PublicKey,
|
|
*Header.CallId,
|
|
NULL
|
|
);
|
|
if (Mapping) {
|
|
NatEditorEditShortSession(
|
|
DataHandle, Header.CallId, Mapping->PrivateCallId
|
|
);
|
|
|
|
//
|
|
// Also, update the timestamp on the mapping.
|
|
//
|
|
|
|
KeQueryTickCount(
|
|
(PLARGE_INTEGER)&Mapping->LastAccessTime
|
|
);
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
if (!Mapping) { return STATUS_UNSUCCESSFUL; }
|
|
}
|
|
|
|
//
|
|
// For the outbound case, CallId refers to the remote call ID,
|
|
// so no translation is necessary.
|
|
//
|
|
|
|
break;
|
|
}
|
|
|
|
case PPTP_CALL_DISCONNECT_NOTIFY: {
|
|
TRACE(PPTP, ("CallDisconnectNotify\n"));
|
|
|
|
//
|
|
// Look up the NAT_PPTP_MAPPING for the PPTP session
|
|
// and mark it for deletion.
|
|
//
|
|
|
|
PptpRegisterEditorClient.QueryInfoSession(
|
|
SessionHandle,
|
|
&PrivateAddress,
|
|
NULL,
|
|
&RemoteAddress,
|
|
NULL,
|
|
&PublicAddress,
|
|
NULL,
|
|
NULL
|
|
);
|
|
|
|
if (NatOutboundDirection == Direction) {
|
|
|
|
//
|
|
// 'CallId' corresponds here to 'PrivateCallId',
|
|
// so we retrieve 'PrivateAddress' and 'RemoteAddress',
|
|
// which together comprise 'PrivateKey', and use that key
|
|
// to search the inbound-list.
|
|
//
|
|
|
|
PrivateKey = MAKE_PPTP_KEY(RemoteAddress, PrivateAddress);
|
|
|
|
//
|
|
// Search exhaustively for PrivateCallId in the inbound list.
|
|
//
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
for (Link = PptpMappingList[NatInboundDirection].Flink;
|
|
Link != &PptpMappingList[NatInboundDirection];
|
|
Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatInboundDirection]
|
|
);
|
|
if (PrivateKey != Mapping->PrivateKey ||
|
|
*Header.CallId != Mapping->PrivateCallId) {
|
|
continue;
|
|
}
|
|
Found = TRUE; break;
|
|
}
|
|
if (Found) {
|
|
NatEditorEditShortSession(
|
|
DataHandle, Header.CallId, Mapping->PublicCallId
|
|
);
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
if (!Found) { return STATUS_UNSUCCESSFUL; }
|
|
|
|
} else {
|
|
|
|
//
|
|
// 'CallId' corresponds here to 'RemoteCallId',
|
|
// so we retrieve 'PublicAddress' and 'RemoteAddress',
|
|
// which together comprise 'PublicKey', and use that key
|
|
// to search the outbound-list.
|
|
//
|
|
|
|
PublicKey = MAKE_PPTP_KEY(RemoteAddress, PublicAddress);
|
|
|
|
//
|
|
// Search exhaustively for RemoteCallId in the outbound list.
|
|
//
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
for (Link = PptpMappingList[NatOutboundDirection].Flink;
|
|
Link != &PptpMappingList[NatOutboundDirection];
|
|
Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatOutboundDirection]
|
|
);
|
|
if (PublicKey != Mapping->PublicKey ||
|
|
*Header.CallId != Mapping->RemoteCallId) {
|
|
continue;
|
|
}
|
|
Found = TRUE; break;
|
|
}
|
|
if (Found) {
|
|
Mapping->Flags |= NAT_PPTP_FLAG_DISCONNECTED;
|
|
}
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
if (!Found) { return STATUS_UNSUCCESSFUL; }
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case PPTP_ECHO_REQUEST:
|
|
case PPTP_ECHO_REPLY: {
|
|
TRACE(PPTP, ("EchoRequest / EchoReply\n"));
|
|
|
|
//
|
|
// Search for all PPTP mappings that belong to this
|
|
// control connection in order to update their
|
|
// last access time.
|
|
//
|
|
|
|
PptpRegisterEditorClient.QueryInfoSession(
|
|
SessionHandle,
|
|
&PrivateAddress,
|
|
&PrivatePort,
|
|
&RemoteAddress,
|
|
&RemotePort,
|
|
NULL,
|
|
&PublicPort,
|
|
NULL
|
|
);
|
|
|
|
PrivateKey = MAKE_PPTP_KEY(RemoteAddress, PrivateAddress);
|
|
PortKey =
|
|
MAKE_PPTP_PORT_KEY(PrivatePort, PublicPort, RemotePort);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
|
|
//
|
|
// Since we don't care about updating the timestamp for
|
|
// half-open connections we only need to search the inbound
|
|
// list. (It's OK that this may update the timestamp on some
|
|
// half-open entries, though.)
|
|
//
|
|
|
|
for (Link = PptpMappingList[NatInboundDirection].Flink;
|
|
Link != &PptpMappingList[NatInboundDirection];
|
|
Link = Link->Flink) {
|
|
Mapping =
|
|
CONTAINING_RECORD(
|
|
Link, NAT_PPTP_MAPPING, Link[NatInboundDirection]
|
|
);
|
|
|
|
if (PrivateKey != Mapping->PrivateKey
|
|
|| PortKey != Mapping->PortKey) {
|
|
continue;
|
|
}
|
|
|
|
KeQueryTickCount((PLARGE_INTEGER)&Mapping->LastAccessTime);
|
|
}
|
|
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Advance to the next message, if any
|
|
//
|
|
|
|
DataOffset += NTOHS(*Header.PacketLength);
|
|
}
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // NatInboundDataHandlerPptp
|
|
|
|
|
|
|
|
VOID
|
|
NatShutdownPptpManagement(
|
|
VOID
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to shutdown operations for the PPTP editor module.
|
|
It handles cleanup of all existing data structures.
|
|
|
|
Arguments:
|
|
|
|
none.
|
|
|
|
Return Value:
|
|
|
|
none.
|
|
|
|
--*/
|
|
|
|
{
|
|
CALLTRACE(("NatShutdownPptpManagement"));
|
|
ExDeleteNPagedLookasideList(&PptpLookasideList);
|
|
} // NatShutdownPptpManagement
|
|
|
|
|
|
FORWARD_ACTION
|
|
NatTranslatePptp(
|
|
PNAT_INTERFACE Interfacep OPTIONAL,
|
|
IP_NAT_DIRECTION Direction,
|
|
PNAT_XLATE_CONTEXT Contextp,
|
|
IPRcvBuf** InReceiveBuffer,
|
|
IPRcvBuf** OutReceiveBuffer
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine is called to translate a PPTP data packet,
|
|
which is encapsulated inside a GRE header.
|
|
|
|
Arguments:
|
|
|
|
Interfacep - the boundary interface over which to translate, or NULL
|
|
if the packet is received on an interface unknown to the NAT.
|
|
|
|
Direction - the direction in which the packet is traveling
|
|
|
|
Contextp - initialized with context-information for the packet
|
|
|
|
InReceiveBuffer - input buffer-chain
|
|
|
|
OutReceiveBuffer - receives modified buffer-chain.
|
|
|
|
Return Value:
|
|
|
|
FORWARD_ACTION - indicates action to take on the packet.
|
|
|
|
Environment:
|
|
|
|
Invoked with reference made to 'Interfacep' by the caller.
|
|
|
|
--*/
|
|
|
|
{
|
|
FORWARD_ACTION act;
|
|
ULONG Checksum;
|
|
ULONG ChecksumDelta = 0;
|
|
PGRE_HEADER GreHeader;
|
|
ULONG i;
|
|
PIP_HEADER IpHeader;
|
|
PNAT_PPTP_MAPPING Mapping;
|
|
ULONG64 PrivateKey;
|
|
ULONG64 PublicKey;
|
|
BOOLEAN FirewallMode;
|
|
TRACE(XLATE, ("NatTranslatePptp\n"));
|
|
|
|
FirewallMode = Interfacep && NAT_INTERFACE_FW(Interfacep);
|
|
|
|
IpHeader = Contextp->Header;
|
|
GreHeader = (PGRE_HEADER)Contextp->ProtocolHeader;
|
|
|
|
if (Direction == NatInboundDirection) {
|
|
|
|
//
|
|
// Look for the PPTP mapping for the data packet
|
|
//
|
|
|
|
PublicKey =
|
|
MAKE_PPTP_KEY(
|
|
Contextp->SourceAddress,
|
|
Contextp->DestinationAddress
|
|
);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
Mapping =
|
|
NatLookupInboundPptpMapping(
|
|
PublicKey,
|
|
GreHeader->CallId,
|
|
NULL
|
|
);
|
|
|
|
if (!Mapping) {
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
return ((*Contextp->DestinationType < DEST_REMOTE) && !FirewallMode
|
|
? FORWARD : DROP);
|
|
}
|
|
|
|
//
|
|
// Translate the private call-ID
|
|
//
|
|
|
|
GreHeader->CallId = Mapping->PrivateCallId;
|
|
|
|
if (!Contextp->ChecksumOffloaded) {
|
|
|
|
CHECKSUM_LONG(ChecksumDelta, ~IpHeader->DestinationAddress);
|
|
IpHeader->DestinationAddress = PPTP_KEY_PRIVATE(Mapping->PrivateKey);
|
|
CHECKSUM_LONG(ChecksumDelta, IpHeader->DestinationAddress);
|
|
|
|
CHECKSUM_UPDATE(IpHeader->Checksum);
|
|
|
|
} else {
|
|
|
|
IpHeader->DestinationAddress = PPTP_KEY_PRIVATE(Mapping->PrivateKey);
|
|
NatComputeIpChecksum(IpHeader);
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Look for the PPTP mapping for the data packet
|
|
//
|
|
|
|
PrivateKey =
|
|
MAKE_PPTP_KEY(
|
|
Contextp->DestinationAddress,
|
|
Contextp->SourceAddress
|
|
);
|
|
|
|
KeAcquireSpinLockAtDpcLevel(&PptpMappingLock);
|
|
Mapping =
|
|
NatLookupOutboundPptpMapping(
|
|
PrivateKey,
|
|
GreHeader->CallId,
|
|
NULL
|
|
);
|
|
|
|
if (!Mapping) {
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
|
|
if (NULL != Interfacep) {
|
|
|
|
//
|
|
// Make sure this packet has a valid source address
|
|
// for this interface.
|
|
//
|
|
|
|
act = DROP;
|
|
for (i = 0; i < Interfacep->AddressCount; i++) {
|
|
if (Contextp->SourceAddress ==
|
|
Interfacep->AddressArray[i].Address
|
|
) {
|
|
act = FORWARD;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
act = FORWARD;
|
|
}
|
|
|
|
return act;
|
|
}
|
|
|
|
//
|
|
// For outbound packets the Call-ID is the remote ID,
|
|
// and hence needs no translation.
|
|
//
|
|
|
|
if (!Contextp->ChecksumOffloaded) {
|
|
|
|
CHECKSUM_LONG(ChecksumDelta, ~IpHeader->SourceAddress);
|
|
IpHeader->SourceAddress = PPTP_KEY_PUBLIC(Mapping->PublicKey);
|
|
CHECKSUM_LONG(ChecksumDelta, IpHeader->SourceAddress);
|
|
|
|
CHECKSUM_UPDATE(IpHeader->Checksum);
|
|
|
|
} else {
|
|
|
|
IpHeader->SourceAddress = PPTP_KEY_PUBLIC(Mapping->PublicKey);
|
|
NatComputeIpChecksum(IpHeader);
|
|
}
|
|
}
|
|
|
|
KeQueryTickCount((PLARGE_INTEGER)&Mapping->LastAccessTime);
|
|
KeReleaseSpinLockFromDpcLevel(&PptpMappingLock);
|
|
|
|
*OutReceiveBuffer = *InReceiveBuffer; *InReceiveBuffer = NULL;
|
|
*Contextp->DestinationType = DEST_INVALID;
|
|
return FORWARD;
|
|
|
|
} // NatTranslatePptp
|
|
|
|
|