/*++ 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 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 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