/*++ Copyright (c) 1997 Microsoft Corporation Module Name: director.c Abstract: This module contains the code for director management. Author: Abolade Gbadegesin (t-abolag) 16-Feb-1998 Revision History: Abolade Gbadegesin (aboladeg) 19-Apr-1998 Added support for wildcards in protocol/port of a director registration. --*/ #include "precomp.h" #pragma hdrstop // // GLOBAL DATA DEFINITIONS // // // Count of NAT directors // ULONG DirectorCount; // // List of NAT directors // LIST_ENTRY DirectorList; // // Spin-lock controlling access to 'DirectorList' // KSPIN_LOCK DirectorLock; // // Spin-lock controlling access to the 'MappingList' field of all directors // KSPIN_LOCK DirectorMappingLock; VOID NatCleanupDirector( PNAT_DIRECTOR Director ) /*++ Routine Description: Called to perform final cleanup for an director. Arguments: Director - the director to be cleaned up. Return Value: none. --*/ { KIRQL Irql; PLIST_ENTRY Link; PNAT_DYNAMIC_MAPPING Mapping; CALLTRACE(("NatCleanupDirector\n")); // // Detach the director from all of its mappings // KeAcquireSpinLock(&DirectorLock, &Irql); KeAcquireSpinLockAtDpcLevel(&DirectorMappingLock); for (Link = Director->MappingList.Flink; Link != &Director->MappingList; Link = Link->Flink) { Mapping = CONTAINING_RECORD(Link, NAT_DYNAMIC_MAPPING, DirectorLink); Link = Link->Blink; NatMappingDetachDirector( Director, Mapping->DirectorContext, Mapping, NatCleanupDirectorDeleteReason ); } KeReleaseSpinLockFromDpcLevel(&DirectorMappingLock); KeReleaseSpinLock(&DirectorLock, Irql); if (Director->UnloadHandler) { Director->UnloadHandler(Director->Context); } ExFreePool(Director); } // NatCleanupDirector NTSTATUS NatCreateDirector( PIP_NAT_REGISTER_DIRECTOR RegisterContext ) /*++ Routine Description: This routine is invoked when an director attempts to register. It handles creation of a context-block for the director. Arguments: RegisterContext - information about the registering director Return Value: NTSTATUS - status code. --*/ { PNAT_DIRECTOR Director; PLIST_ENTRY InsertionPoint; KIRQL Irql; ULONG Key; CALLTRACE(("NatCreateDirector\n")); RegisterContext->DirectorHandle = NULL; // // Validate the registration information // if (!RegisterContext->QueryHandler && !RegisterContext->CreateHandler && !RegisterContext->DeleteHandler && !RegisterContext->UnloadHandler) { ERROR(("NatCreateDirector: bad argument\n")); return STATUS_INVALID_PARAMETER; } // // Allocate a new director-struct // Director = ExAllocatePoolWithTag( NonPagedPool, sizeof(NAT_DIRECTOR), NAT_TAG_DIRECTOR ); if (!Director) { ERROR(("NatCreateDirector: allocation failed\n")); return STATUS_NO_MEMORY; } KeInitializeSpinLock(&Director->Lock); Director->ReferenceCount = 1; Director->Flags = RegisterContext->Flags; Director->Key = MAKE_DIRECTOR_KEY(RegisterContext->Protocol, RegisterContext->Port); InitializeListHead(&Director->MappingList); Director->Context = RegisterContext->DirectorContext; Director->CreateHandler = RegisterContext->CreateHandler; Director->DeleteHandler = RegisterContext->DeleteHandler; Director->QueryHandler = RegisterContext->QueryHandler; Director->UnloadHandler = RegisterContext->UnloadHandler; KeAcquireSpinLock(&DirectorLock, &Irql); if (NatLookupDirector(Director->Key, &InsertionPoint)) { KeReleaseSpinLock(&DirectorLock, Irql); ERROR( ("NatCreateDirector: duplicate director %d/%d\n", RegisterContext->Protocol, RegisterContext->Port) ); ExFreePool(Director); return STATUS_UNSUCCESSFUL; } InsertTailList(InsertionPoint, &Director->Link); KeReleaseSpinLock(&DirectorLock, Irql); InterlockedIncrement(&DirectorCount); // // Supply the caller with 'out' information // RegisterContext->DirectorHandle = (PVOID)Director; RegisterContext->QueryInfoSession = NatDirectorQueryInfoSession; RegisterContext->Deregister = NatDirectorDeregister; RegisterContext->DissociateSession = NatDirectorDissociateSession; return STATUS_SUCCESS; } // NatCreateDirector NTSTATUS NatDeleteDirector( PNAT_DIRECTOR Director ) /*++ Routine Description: Handles director deletion. Arguments: Director - specifies the director to be deleted. Return Value NTSTATUS - status code. --*/ { KIRQL Irql; CALLTRACE(("NatDeleteDirector\n")); if (!Director) { return STATUS_INVALID_PARAMETER; } InterlockedDecrement(&DirectorCount); // // Remove the director from the list // KeAcquireSpinLock(&DirectorLock, &Irql); RemoveEntryList(&Director->Link); Director->Flags |= NAT_DIRECTOR_FLAG_DELETED; KeReleaseSpinLock(&DirectorLock, Irql); // // Drop its reference count and cleanup if necessary // if (InterlockedDecrement(&Director->ReferenceCount) > 0) { return STATUS_PENDING; } NatCleanupDirector(Director); return STATUS_SUCCESS; } // NatDeleteDirector VOID NatInitializeDirectorManagement( VOID ) /*++ Routine Description: This routine prepares the director-management module for operation. Arguments: none. Return Value: none. --*/ { CALLTRACE(("NatInitializeDirectorManagement\n")); DirectorCount = 0; KeInitializeSpinLock(&DirectorLock); InitializeListHead(&DirectorList); KeInitializeSpinLock(&DirectorMappingLock); } // NatInitializeDirectorManagement PNAT_DIRECTOR NatLookupAndReferenceDirector( UCHAR Protocol, USHORT Port ) /*++ Routine Description: This routine is called to search for a director for the given incoming protocol and port, and to obtain a referenced pointer to such a director. This routine must be invoked at DISPATCH_LEVEL. Arguments: Protocol - the protocol of the director to be looked up Port - the port-number of the director to be looked up Return Value: PNAT_DIRECTOR - the references director if found; NULL otherwise. --*/ { PNAT_DIRECTOR Director; ULONG Key; PLIST_ENTRY Link; KeAcquireSpinLockAtDpcLevel(&DirectorLock); if (IsListEmpty(&DirectorList)) { KeReleaseSpinLockFromDpcLevel(&DirectorLock); return NULL; } Key = MAKE_DIRECTOR_KEY(Protocol, Port); // // Our support for wildcards takes advantage of the fact that // all wildcards are designated by zero; hence, since our list // is in descending order we only need to look for wildcards // at the point where we would break off a normal search. // for (Link = DirectorList.Flink; Link != &DirectorList; Link = Link->Flink) { Director = CONTAINING_RECORD(Link, NAT_DIRECTOR, Link); if (Key < Director->Key) { continue; } else if (Key > Director->Key) { // // End of normal search. Now look for wildcards // do { if ((!DIRECTOR_KEY_PROTOCOL(Director->Key) || Protocol == DIRECTOR_KEY_PROTOCOL(Director->Key)) && (!DIRECTOR_KEY_PORT(Director->Key) || Port == DIRECTOR_KEY_PORT(Director->Key))) { // // We have a matching wildcard. // break; } Link = Link->Flink; } while (Link != &DirectorList); if (Link == &DirectorList) { break; } } // // We've found it. Reference it and return. // if (!NatReferenceDirector(Director)) { Director = NULL; } KeReleaseSpinLockFromDpcLevel(&DirectorLock); return Director; } KeReleaseSpinLockFromDpcLevel(&DirectorLock); return NULL; } // NatLookupAndReferenceDirector PNAT_DIRECTOR NatLookupDirector( ULONG Key, PLIST_ENTRY* InsertionPoint ) /*++ Routine Description: This routine is called to retrieve the director corresponding to the given key. Arguments: Key - the key for which an director is to be found InsertionPoint - receives the point at which the director should be inserted if not found Return Value: PNAT_DIRECTOR - the required director, if found --*/ { PNAT_DIRECTOR Director; PLIST_ENTRY Link; for (Link = DirectorList.Flink; Link != &DirectorList; Link = Link->Flink) { Director = CONTAINING_RECORD(Link, NAT_DIRECTOR, Link); if (Key < Director->Key) { continue; } else if (Key > Director->Key) { break; } return Director; } if (InsertionPoint) { *InsertionPoint = Link; } return NULL; } // NatLookupDirector VOID NatMappingAttachDirector( PNAT_DIRECTOR Director, PVOID DirectorSessionContext, PNAT_DYNAMIC_MAPPING Mapping ) /*++ Routine Description: This routine is invoked to attach a mapping to a director. It serves as a notification that there is one more mapping associated with the director. Arguments: Director - the director for the mapping DirectorSessionContext - context associated with the mapping by the director Mapping - the mapping to be attached. Return Value: none. Environment: Always invoked at dispatch level with 'DirectorLock' and 'DirectorMappingLock' held by the caller. --*/ { Mapping->Director = Director; Mapping->DirectorContext = DirectorSessionContext; InsertTailList(&Director->MappingList, &Mapping->DirectorLink); if (Director->CreateHandler) { Director->CreateHandler( Mapping, Director->Context, DirectorSessionContext ); } } // NatMappingAttachDirector VOID NatMappingDetachDirector( PNAT_DIRECTOR Director, PVOID DirectorSessionContext, PNAT_DYNAMIC_MAPPING Mapping, IP_NAT_DELETE_REASON DeleteReason ) /*++ Routine Description: This routine is invoked to detach a mapping from a director. It serves as a notification that there is one less mapping associated with the director. Arguments: Director - director to be detached DirectorSessionContext - context associated with the director Mapping - the mapping to be detached, or NULL if a mapping could not be created. Return Value: none. Environment: Always invoked at dispatch level with 'DirectorLock' and 'DirectorMappingLock' held, in that order. --*/ { KIRQL Irql; if (!Mapping) { if (Director->DeleteHandler) { Director->DeleteHandler( NULL, Director->Context, DirectorSessionContext, DeleteReason ); } } else { if (Director->DeleteHandler) { Director->DeleteHandler( Mapping, Director->Context, Mapping->DirectorContext, DeleteReason ); } RemoveEntryList(&Mapping->DirectorLink); Mapping->Director = NULL; Mapping->DirectorContext = NULL; } } // NatMappingDetachDirector NTSTATUS NatQueryDirectorTable( IN PIP_NAT_ENUMERATE_DIRECTORS InputBuffer, IN PIP_NAT_ENUMERATE_DIRECTORS OutputBuffer, IN PULONG OutputBufferLength ) /*++ Routine Description: This routine is used for enumerating the registered directors. Arguments: InputBuffer - supplies context information for the information OutputBuffer - receives the result of the enumeration OutputBufferLength - size of the i/o buffer Return Value: STATUS_SUCCESS if successful, error code otherwise. --*/ { ULONG Count; ULONG i; KIRQL Irql; ULONG Key; PLIST_ENTRY Link; PNAT_DIRECTOR Director; NTSTATUS status; PIP_NAT_DIRECTOR Table; CALLTRACE(("NatQueryDirectorTable\n")); Key = InputBuffer->EnumerateContext; KeAcquireSpinLock(&DirectorLock, &Irql); // // See if this is a new enumeration or a continuation of an old one. // if (!Key) { // // This is a new enumeration. We start with the first item // in the list of entries // Director = IsListEmpty(&DirectorList) ? NULL : CONTAINING_RECORD(DirectorList.Flink, NAT_DIRECTOR, Link); } else { // // This is a continuation. The context therefore contains // the key for the next entry. // Director = NatLookupDirector(Key, NULL); } if (!Director) { OutputBuffer->EnumerateCount = 0; OutputBuffer->EnumerateContext = 0; OutputBuffer->EnumerateTotalHint = DirectorCount; *OutputBufferLength = FIELD_OFFSET(IP_NAT_ENUMERATE_DIRECTORS, EnumerateTable); KeReleaseSpinLock(&DirectorLock, Irql); return STATUS_SUCCESS; } // // Compute the maximum number of entries we can store // Count = *OutputBufferLength - FIELD_OFFSET(IP_NAT_ENUMERATE_DIRECTORS, EnumerateTable); Count /= sizeof(IP_NAT_DIRECTOR); // // Walk the list storing entries in the caller's buffer // Table = OutputBuffer->EnumerateTable; for (i = 0, Link = &Director->Link; i < Count && Link != &DirectorList; i++, Link = Link->Flink) { Director = CONTAINING_RECORD(Link, NAT_DIRECTOR, Link); Table[i].Protocol = DIRECTOR_KEY_PROTOCOL(Director->Key); Table[i].Port = DIRECTOR_KEY_PORT(Director->Key); } // // The enumeration is over; update the output structure // *OutputBufferLength = i * sizeof(IP_NAT_DIRECTOR) + FIELD_OFFSET(IP_NAT_ENUMERATE_DIRECTORS, EnumerateTable); OutputBuffer->EnumerateCount = i; OutputBuffer->EnumerateTotalHint = DirectorCount; if (Link == &DirectorList) { // // We reached the end of the list // OutputBuffer->EnumerateContext = 0; } else { // // Save the continuation context // OutputBuffer->EnumerateContext = CONTAINING_RECORD(Link, NAT_DIRECTOR, Link)->Key; } KeReleaseSpinLock(&DirectorLock, Irql); return STATUS_SUCCESS; } // NatQueryDirectorTable VOID NatShutdownDirectorManagement( VOID ) /*++ Routine Description: This routine shuts down the director-management module. Arguments: none. Return Value: none. --*/ { PNAT_DIRECTOR Director; KIRQL Irql; CALLTRACE(("NatShutdownDirectorManagement\n")); // // Delete all directors // KeAcquireSpinLock(&DirectorLock, &Irql); while (!IsListEmpty(&DirectorList)) { Director = CONTAINING_RECORD(DirectorList.Flink, NAT_DIRECTOR, Link); RemoveEntryList(&Director->Link); KeReleaseSpinLockFromDpcLevel(&DirectorLock); NatCleanupDirector(Director); KeAcquireSpinLockAtDpcLevel(&DirectorLock); } KeReleaseSpinLock(&DirectorLock, Irql); } // NatShutdownDirectorManagement // // DIRECTOR HELPER ROUTINES // // The caller is assumed to be running at DISPATCH_LEVEL. // NTSTATUS NatDirectorDeregister( IN PVOID DirectorHandle ) /*++ Routine Description: This routine is called by a director to remove itself from the director list. Arguments: DirectorHandle - handle of the director to be removed. Return Value: NTSTATUS - status code. --*/ { CALLTRACE(("NatDirectorDeregister\n")); return NatDeleteDirector((PNAT_DIRECTOR)DirectorHandle); } // NatDirectorDeregister NTSTATUS NatDirectorDissociateSession( IN PVOID DirectorHandle, IN PVOID SessionHandle ) /*++ Routine Description: This routine is called by a director to dissociate itself from a specific session. Arguments: DirectorHandle - the director which wishes to dissociate itself. SessionHandle - the session from which the director is disssociating itself. Return Value: NTSTATUS - indicates success/failure Environment: Invoked at dispatch level with neither 'DirectorLock' nor 'DirectorMappingLock' held by the caller. --*/ { PNAT_DIRECTOR Director = (PNAT_DIRECTOR)DirectorHandle; KIRQL Irql; PNAT_DYNAMIC_MAPPING Mapping = (PNAT_DYNAMIC_MAPPING)SessionHandle; CALLTRACE(("NatDirectorDissociateSession\n")); KeAcquireSpinLock(&DirectorLock, &Irql); if (Mapping->Director != Director) { KeReleaseSpinLock(&DirectorLock, Irql); return STATUS_INVALID_PARAMETER; } KeAcquireSpinLockAtDpcLevel(&DirectorMappingLock); NatMappingDetachDirector( Director, Mapping->DirectorContext, Mapping, NatDissociateDirectorDeleteReason ); KeReleaseSpinLockFromDpcLevel(&DirectorMappingLock); if (!NAT_MAPPING_DELETE_ON_DISSOCIATE_DIRECTOR(Mapping)) { KeReleaseSpinLock(&DirectorLock, Irql); } else { KeReleaseSpinLockFromDpcLevel(&DirectorLock); KeAcquireSpinLockAtDpcLevel(&MappingLock); NatDeleteMapping(Mapping); KeReleaseSpinLock(&MappingLock, Irql); } return STATUS_SUCCESS; } // NatDirectorDissociateSession VOID NatDirectorQueryInfoSession( IN PVOID SessionHandle, OUT PIP_NAT_SESSION_MAPPING_STATISTICS Statistics OPTIONAL ) /*++ Routine Description: This routine is invoked by a director to obtain information about a session. Arguments: SessionHandle - the session for which information is required Statistics - receives statistics for the session Return Value: none. Environment: Invoked --*/ { KIRQL Irql; KeAcquireSpinLock(&MappingLock, &Irql); NatQueryInformationMapping( (PNAT_DYNAMIC_MAPPING)SessionHandle, NULL, NULL, NULL, NULL, NULL, NULL, NULL, Statistics ); KeReleaseSpinLock(&MappingLock, Irql); } // NatDirectorQueryInfoSession