/*++ Copyright (c) 1999, Microsoft Corporation Module Name: portapi.c Abstract: This module contains code for API routines which provide port-reservation functionality to user-mode clients of TCP/IP. This functionality allows applications to 'reserve' blocks of TCP/UDP port-numbers for private use, preventing any other processes from binding to the reserved port-numbers. Author: Abolade Gbadegesin (aboladeg) 25-May-1999 Revision History: --*/ #include "precomp.h" #pragma hdrstop #include #include // // PRIVATE STRUCTURE DECLARATIONS // typedef struct _NAT_PORT_RESERVATION { CRITICAL_SECTION Lock; HANDLE TcpipHandle; USHORT BlockSize; USHORT PortBlockSize; LIST_ENTRY PortBlockList; } NAT_PORT_RESERVATION, *PNAT_PORT_RESERVATION; typedef struct _NAT_PORT_BLOCK { LIST_ENTRY Link; ULONG StartHandle; RTL_BITMAP Bitmap; ULONG BitmapBuffer[0]; } NAT_PORT_BLOCK, *PNAT_PORT_BLOCK; // // FORWARD DECLARATIONS // ULONG NatpCreatePortBlock( PNAT_PORT_RESERVATION PortReservation, PNAT_PORT_BLOCK* PortBlockCreated ); VOID NatpDeletePortBlock( PNAT_PORT_RESERVATION PortReservation, PNAT_PORT_BLOCK PortBlock ); ULONG NatAcquirePortReservation( HANDLE ReservationHandle, USHORT PortCount, OUT PUSHORT ReservedPortBase ) /*++ Routine Description: This routine is called to reserve one or more contiguous port-numbers from the port-reservation handle supplied. Arguments: ReservationHandle - supplies a port-reservation handle from which to acquire port-numbers PortCount - specifies the number of port-numbers required ReservedPortBase - receives the first port-number reserved, in network-order. Return Value: ULONG - Win32 status code. --*/ { ULONG Error; ULONG Index; PLIST_ENTRY Link; PNAT_PORT_BLOCK PortBlock; PNAT_PORT_RESERVATION PortReservation = (PNAT_PORT_RESERVATION)ReservationHandle; NTSTATUS Status; // // Fail immediately if the caller has requested more port-numbers // than would exist in a completely unallocated block. // Otherwise, traverse the list of port-blocks to see if any of the blocks // have enough contiguous port-numbers to satisfy the caller's request. // if (PortCount > PortReservation->BlockSize) { return ERROR_INVALID_PARAMETER; } EnterCriticalSection(&PortReservation->Lock); for (Link = PortReservation->PortBlockList.Flink; Link != &PortReservation->PortBlockList; Link = Link->Flink) { PortBlock = CONTAINING_RECORD(Link, NAT_PORT_BLOCK, Link); Index = RtlFindClearBitsAndSet(&PortBlock->Bitmap, PortCount, 0); if (Index != (ULONG)-1) { *ReservedPortBase = RtlUshortByteSwap((USHORT)(PortBlock->StartHandle + Index)); LeaveCriticalSection(&PortReservation->Lock); return NO_ERROR; } } // // No port-block had the required number of contiguous port-numbers. // Attempt to create a new port-block, and if that succeeds use it // to satisfy the caller's request. // Error = NatpCreatePortBlock(PortReservation, &PortBlock); if (NO_ERROR != Error) { LeaveCriticalSection(&PortReservation->Lock); return Error; } Index = RtlFindClearBitsAndSet(&PortBlock->Bitmap, PortCount, 0); *ReservedPortBase = RtlUshortByteSwap((USHORT)(PortBlock->StartHandle + Index)); LeaveCriticalSection(&PortReservation->Lock); return NO_ERROR; } // NatAcquirePortReservation ULONG NatInitializePortReservation( USHORT BlockSize, OUT PHANDLE ReservationHandle ) /*++ Routine Description: This routine is called to initialize a handle to the port-reservation module. The resulting handle is used to acquire and release ports from the dynamically-allocated block. Arguments: BlockSize - indicates the number of ports to request each time an additional block is requested from the TCP/IP driver. ReservationHandle - on output, receives a handle to be used for acquiring and releasing ports. Return Value: ULONG - Win32 status code. --*/ { ULONG BitmapSize; IO_STATUS_BLOCK IoStatus; OBJECT_ATTRIBUTES ObjectAttributes; PNAT_PORT_RESERVATION PortReservation; NTSTATUS Status; HANDLE TcpipHandle; UNICODE_STRING UnicodeString; do { // // Open a handle to the TCP/IP driver. // This handle will later be used to issue reservation-requests. // RtlInitUnicodeString(&UnicodeString, DD_TCP_DEVICE_NAME); InitializeObjectAttributes( &ObjectAttributes, &UnicodeString, OBJ_CASE_INSENSITIVE, NULL, NULL ); Status = NtCreateFile( &TcpipHandle, SYNCHRONIZE|FILE_READ_DATA|FILE_WRITE_DATA, &ObjectAttributes, &IoStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ|FILE_SHARE_WRITE, FILE_OPEN_IF, 0, NULL, 0 ); if (!NT_SUCCESS(Status)) { break; } // // Allocate and initialize a port-reservation context block. // PortReservation = MALLOC(sizeof(*PortReservation)); if (!PortReservation) { Status = STATUS_NO_MEMORY; break; } if (FALSE == InitializeCriticalSectionAndSpinCount(&PortReservation->Lock, 0)) { Status = STATUS_NO_MEMORY; break; } PortReservation->TcpipHandle = TcpipHandle; PortReservation->BlockSize = BlockSize; BitmapSize = (BlockSize + sizeof(ULONG) * 8 - 1) / (sizeof(ULONG) * 8); PortReservation->PortBlockSize = (USHORT)FIELD_OFFSET(NAT_PORT_BLOCK, BitmapBuffer[BitmapSize]); InitializeListHead(&PortReservation->PortBlockList); *ReservationHandle = (HANDLE)PortReservation; return NO_ERROR; } while(FALSE); if (TcpipHandle) { NtClose(TcpipHandle); } if (PortReservation) { FREE(PortReservation); } return RtlNtStatusToDosError(Status); } // NatInitializePortReservation ULONG NatpCreatePortBlock( PNAT_PORT_RESERVATION PortReservation, PNAT_PORT_BLOCK* PortBlockCreated ) /*++ Routine Description: This routine is called to create a new port-block when the existing port-numbers have been exhausted. Arguments: PortReservation - the reservation to which the port-block should be added PortBlockCreated - on output, receives the new port-block Return Value: ULONG - Win32 error code; return NO_ERROR if successful. Environment: PortReservation->Lock must be held by the caller. --*/ { IO_STATUS_BLOCK IoStatus; PLIST_ENTRY Link; PNAT_PORT_BLOCK PortBlock; TCP_BLOCKPORTS_REQUEST Request; ULONG StartHandle; NTSTATUS Status; HANDLE WaitEvent; // // Allocate memory for the new port-block and its bitmap of free ports // PortBlock = MALLOC(PortReservation->PortBlockSize); if (!PortBlock) { return ERROR_NOT_ENOUGH_MEMORY; } // // Request a new block of ports from the TCP/IP driver // WaitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (WaitEvent == NULL) { FREE(PortBlock); return ERROR_NOT_ENOUGH_MEMORY; } Request.ReservePorts = TRUE; Request.NumberofPorts = PortReservation->BlockSize; Status = NtDeviceIoControlFile( PortReservation->TcpipHandle, WaitEvent, NULL, NULL, &IoStatus, IOCTL_TCP_BLOCK_PORTS, &Request, sizeof(Request), &StartHandle, sizeof(StartHandle) ); if (Status == STATUS_PENDING) { WaitForSingleObject(WaitEvent, INFINITE); Status = IoStatus.Status; } CloseHandle(WaitEvent); if (!NT_SUCCESS(Status)) { FREE(PortBlock); return RtlNtStatusToDosError(Status); } // // Initialize the new port-block, and insert it in the list of ports. // PortBlock->StartHandle = StartHandle; RtlInitializeBitMap( &PortBlock->Bitmap, PortBlock->BitmapBuffer, PortReservation->BlockSize ); RtlClearAllBits(&PortBlock->Bitmap); for (Link = PortReservation->PortBlockList.Flink; Link != &PortReservation->PortBlockList; Link = Link->Flink) { PNAT_PORT_BLOCK Temp = CONTAINING_RECORD(Link, NAT_PORT_BLOCK, Link); if (PortBlock->StartHandle > Temp->StartHandle) { continue; } else { break; } ASSERTMSG("NatpCreatePortBlock: duplicate port range\n", TRUE); } InsertTailList(Link, &PortBlock->Link); if (PortBlockCreated) { *PortBlockCreated = PortBlock; } return NO_ERROR; } // NatpCreatePortBlock VOID NatpDeletePortBlock( PNAT_PORT_RESERVATION PortReservation, PNAT_PORT_BLOCK PortBlock ) /*++ Routine Description: This routine is called to delete a port-block when the port-numbers it contains have been released, or when the port-reservation is cleaned up. Arguments: PortReservation - the reservation to which the port-block belongs PortBlock - the port block to be deleted Return Value: none. Environment: PortReservation->Lock must be held by the caller. --*/ { IO_STATUS_BLOCK IoStatus; TCP_BLOCKPORTS_REQUEST Request; NTSTATUS Status; HANDLE WaitEvent; WaitEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (WaitEvent == NULL) { return; } // // Release the block of ports to the TCP/IP driver // Request.ReservePorts = FALSE; Request.StartHandle = PortBlock->StartHandle; Status = NtDeviceIoControlFile( PortReservation->TcpipHandle, WaitEvent, NULL, NULL, &IoStatus, IOCTL_TCP_BLOCK_PORTS, &Request, sizeof(Request), NULL, 0 ); if (Status == STATUS_PENDING) { WaitForSingleObject(WaitEvent, INFINITE); Status = IoStatus.Status; } RemoveEntryList(&PortBlock->Link); FREE(PortBlock); CloseHandle(WaitEvent); } // NatpDeletePortBlock ULONG NatReleasePortReservation( HANDLE ReservationHandle, USHORT ReservedPortBase, USHORT PortCount ) /*++ Routine Description: This routine is called to release all contiguous port-numbers obtained in a previous acquisition from the port-reservation handle supplied. Arguments: ReservationHandle - supplies a port-reservation handle to which to release port-numbers ReservedPortBase - receives the first port-number reserved, in network-order. PortCount - specifies the number of port-numbers acquired Return Value: ULONG - Win32 status code. --*/ { PLIST_ENTRY Link; USHORT PortBase; PNAT_PORT_BLOCK PortBlock; PNAT_PORT_RESERVATION PortReservation = (PNAT_PORT_RESERVATION)ReservationHandle; EnterCriticalSection(&PortReservation->Lock); // // Convert the caller's port-base into host-order, // and search the sorted list of port-blocks for the entry // from which the acquisition was made. // PortBase = RtlUshortByteSwap(ReservedPortBase); for (Link = PortReservation->PortBlockList.Flink; Link != &PortReservation->PortBlockList; Link = Link->Flink) { PortBlock = CONTAINING_RECORD(Link, NAT_PORT_BLOCK, Link); if (PortBase < PortBlock->StartHandle) { break; } else if (PortBase < (PortBlock->StartHandle + PortReservation->BlockSize)) { // // This should be the block from which the caller's port-numbers // were acquired. For good measure, check that the end of the // callers range also falls within this block. // if ((PortBase + PortCount - 1) >= (USHORT)(PortBlock->StartHandle + PortReservation->BlockSize)) { // // The caller has probably supplied an incorrect length, // or is releasing an allocation twice, or something. // LeaveCriticalSection(&PortReservation->Lock); return ERROR_INVALID_PARAMETER; } else { // // This is the caller's range. Clear the bits corresponding // to the caller's acquisition, and then see if there are // any bits left in the bitmap. If not, and if there are // other port-blocks, delete this port-block altogether. // RtlClearBits( &PortBlock->Bitmap, PortBase - PortBlock->StartHandle, PortCount ); if (RtlFindSetBits(&PortBlock->Bitmap, 1, 0) == (ULONG)-1 && (PortBlock->Link.Flink != &PortReservation->PortBlockList || PortBlock->Link.Blink != &PortReservation->PortBlockList) ) { NatpDeletePortBlock(PortReservation, PortBlock); } LeaveCriticalSection(&PortReservation->Lock); return NO_ERROR; } } else { continue; } } LeaveCriticalSection(&PortReservation->Lock); // // We could not find the port-block from which the caller // allegedly acquired this range of port-numbers. // return ERROR_CAN_NOT_COMPLETE; } // NatReleasePortReservation VOID NatShutdownPortReservation( HANDLE ReservationHandle ) /*++ Routine Description: This routine is called to clean up a handle to the port-reservation module. It releases all reservations acquired, and closes the handle to the TCP/IP driver. Arguments: ReservationHandle - the handle to be cleaned up Return Value: none. --*/ { PNAT_PORT_BLOCK PortBlock; PNAT_PORT_RESERVATION PortReservation = (PNAT_PORT_RESERVATION)ReservationHandle; while (!IsListEmpty(&PortReservation->PortBlockList)) { PortBlock = CONTAINING_RECORD( PortReservation->PortBlockList.Flink, NAT_PORT_BLOCK, Link ); NatpDeletePortBlock(PortReservation, PortBlock); } NtClose(PortReservation->TcpipHandle); DeleteCriticalSection(&PortReservation->Lock); FREE(PortReservation); }