/*++ Copyright (c) 1998 Microsoft Corporation Module Name: portmgmt.cpp Abstract: Functions for allocating and freeing ports from the Port pool PortPoolAllocRTPPort() PortPoolFreeRTPPort() Environment: User Mode - Win32 --*/ /////////////////////////////////////////////////////////////////////////////// // // // Functions dealing with the TCP device to reserve/unreserve port ranges. // // // /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // // // Include files // // // /////////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #define NUM_DWORD_BITS (sizeof(DWORD)*8) /////////////////////////////////////////////////////////////////////////////// // // // Global Variables // // // /////////////////////////////////////////////////////////////////////////////// #define NUM_PORTS_PER_RANGE 100 struct PORT_RANGE { LIST_ENTRY ListEntry; // This is the actual lower port. In case, the range allocated // by TCP starts with an odd port, we ignore the first port in // which case (low == AllocatedLow + 1). But when we free the // port range we should pass in AllocatedLow and not low. WORD AllocatedLow; WORD low; // high is the last port we can use and not the allocated high. // In some cases high will be one less than the actual allocated high. WORD high; //Each bit in this bitmap indicates the status of 2 consecutive ports DWORD *AllocList; DWORD dwAllocListSize; }; class PORT_POOL : public SIMPLE_CRITICAL_SECTION_BASE { private: HANDLE TcpDevice; LIST_ENTRY PortRangeList; // contains PORT_RANGE.ListEntry private: HRESULT OpenTcpDevice (void); HRESULT StartLocked (void); void FreeAll (void); HRESULT CreatePortRange ( OUT PORT_RANGE ** ReturnPortRange); HRESULT ReservePortRange ( IN ULONG RangeLength, OUT WORD * ReturnStartPort); HRESULT UnReservePortRange ( IN WORD StartPort); public: PORT_POOL (void); ~PORT_POOL (void); HRESULT Start (void); void Stop (void); HRESULT AllocPort ( OUT WORD * ReturnPort); void FreePort ( IN WORD Port); }; // global data ------------------------------------------------------------------------- static PORT_POOL PortPool; // extern code ----------------------------------------------------------------------- HRESULT PortPoolStart (void) { return PortPool.Start(); } void PortPoolStop (void) { PortPool.Stop(); } HRESULT PortPoolAllocRTPPort ( OUT WORD * ReturnPort) { return PortPool.AllocPort (ReturnPort); } HRESULT PortPoolFreeRTPPort ( IN WORD Port) { PortPool.FreePort (Port); return S_OK; } HRESULT PORT_POOL::ReservePortRange ( IN ULONG RangeLength, OUT WORD * ReturnStartPort) { TCP_BLOCKPORTS_REQUEST PortRequest; DWORD BytesTransferred; ULONG StartPort; AssertLocked(); *ReturnStartPort = 0; if (!TcpDevice) { Debug (_T("H323: Cannot allocate port range, TCP device could not be opened.\n")); return E_UNEXPECTED; } assert (TcpDevice != INVALID_HANDLE_VALUE); PortRequest.ReservePorts = TRUE; PortRequest.NumberofPorts = RangeLength; if (!DeviceIoControl (TcpDevice, IOCTL_TCP_BLOCK_PORTS, &PortRequest, sizeof PortRequest, &StartPort, sizeof StartPort, &BytesTransferred, NULL)) { DebugLastError (_T("H323: Failed to allocate TCP port range.\n")); return GetLastError(); } DebugF (_T("H323: Reserved port range: [%04X - %04X)\n"), StartPort, StartPort + PortRequest.NumberofPorts); *ReturnStartPort = (WORD) StartPort; return S_OK; } HRESULT PORT_POOL::UnReservePortRange ( IN WORD StartPort) { TCP_BLOCKPORTS_REQUEST PortRequest; DWORD BytesTransferred; DWORD Status; AssertLocked(); if (!TcpDevice) { Debug (_T("H323: Cannot free TCP port range, TCP device is not open.\n")); return E_UNEXPECTED; } assert (TcpDevice != INVALID_HANDLE_VALUE); PortRequest.ReservePorts = FALSE; PortRequest.StartHandle = (ULONG) StartPort; if (!DeviceIoControl(TcpDevice, IOCTL_TCP_BLOCK_PORTS, &PortRequest, sizeof PortRequest, &Status, sizeof Status, &BytesTransferred, NULL)) { DebugLastError (_T("H323: Failed to free TCP port range.\n")); return GetLastError(); } return S_OK; } /////////////////////////////////////////////////////////////////////////////// // // // Port Pool Functions. // // // /////////////////////////////////////////////////////////////////////////////// // PORT_POOL ----------------------------------------------------------------------- PORT_POOL::PORT_POOL (void) { TcpDevice = NULL; InitializeListHead (&PortRangeList); } PORT_POOL::~PORT_POOL (void) { assert (!TcpDevice); assert (IsListEmpty (&PortRangeList)); } HRESULT PORT_POOL::Start (void) { HRESULT Result; Lock(); Result = OpenTcpDevice(); Unlock(); return Result; } HRESULT PORT_POOL::OpenTcpDevice (void) { UNICODE_STRING DeviceName; IO_STATUS_BLOCK IoStatusBlock; OBJECT_ATTRIBUTES ObjectAttributes; NTSTATUS Status; if (TcpDevice) return S_OK; RtlInitUnicodeString (&DeviceName, (PCWSTR) DD_TCP_DEVICE_NAME); InitializeObjectAttributes (&ObjectAttributes, &DeviceName, OBJ_CASE_INSENSITIVE, NULL, NULL); Status = NtCreateFile ( &TcpDevice, SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA , &ObjectAttributes, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, 0, NULL, 0); if (Status != STATUS_SUCCESS) { TcpDevice = NULL; DebugError (Status, _T("H323: Failed to open TCP device.\n")); return (HRESULT) Status; } return S_OK; } void PORT_POOL::Stop (void) { Lock(); FreeAll(); if (TcpDevice) { assert (TcpDevice != INVALID_HANDLE_VALUE); CloseHandle (TcpDevice); TcpDevice = NULL; } Unlock(); } void PORT_POOL::FreeAll (void) { LIST_ENTRY * ListEntry; PORT_RANGE * PortRange; while (!IsListEmpty (&PortRangeList)) { ListEntry = RemoveHeadList (&PortRangeList); PortRange = CONTAINING_RECORD (ListEntry, PORT_RANGE, ListEntry); // Free the port range PortRange->AllocatedLow UnReservePortRange (PortRange -> AllocatedLow); EM_FREE (PortRange); } } /*++ Routine Description: This function allocates a pair of RTP/RTCP ports from the port pool. Arguments: rRTPport - This is an OUT parameter. If the function succeeds rRTPport will contain the RTP port (which is even). rRTPport+1 should be used as the RTCP port. Return Values: This function returns S_OK on success and E_FAIL if it fails to allocate a port range. --*/ HRESULT PORT_POOL::AllocPort ( OUT WORD * ReturnPort) { DWORD i, j; DWORD bitmap = 0x80000000; LIST_ENTRY * ListEntry; PORT_RANGE * PortRange; WORD Port; HRESULT Result; Lock(); for (ListEntry = PortRangeList.Flink; ListEntry != &PortRangeList; ListEntry = ListEntry -> Flink) { PortRange = CONTAINING_RECORD (ListEntry, PORT_RANGE, ListEntry); for (i = 0; i < PortRange->dwAllocListSize; i++) { // traverse through AllocList of this portRange if ((PortRange->AllocList[i] & 0xffffffff) != 0xffffffff) { // at least one entry is free bitmap = 0x80000000; for (j = 0; j < NUM_DWORD_BITS; j++) { // traverse through each bit of the DWORD if ((PortRange->AllocList[i] & bitmap) == 0) { // found a free pair of ports Port = (WORD) (PortRange -> low + (i*NUM_DWORD_BITS*2) + (j*2)); if (Port > PortRange -> high) { // This check is needed because the last DWORD // in the AllocList may contain bits which are // actually not included in the AllocList. goto noports; } // set the bit to show the pair of ports is allocated PortRange -> AllocList[i] |= bitmap; // Leave the global critical section for the Port pool Unlock(); DebugF (_T("H323: Allocated port pair (%04X, %04X).\n"), Port, Port + 1); *ReturnPort = Port; return S_OK; } bitmap = bitmap >> 1; } } } } noports: // CODEWORK: Once we get the new ioctl() for dynamically reserving // port ranges, we need to allocate a new port range here. If the // ioctl() fails we need to return E_FAIL or another error which // says we have run out of ports. // Allocate a new port range Result = CreatePortRange (&PortRange); if (PortRange) { InsertHeadList (&PortRangeList, &PortRange -> ListEntry); // allocate the first port in the range and Port = PortRange -> low; PortRange->AllocList[0] |= 0x80000000; DebugF (_T("H323: Allocated port pair (%04X, %04X).\n"), Port, Port + 1); *ReturnPort = Port; Result = S_OK; } else { Debug (_T("H323: Failed to allocate port range.\n")); *ReturnPort = 0; Result = E_FAIL; } Unlock(); return Result; } /*++ Routine Description: This function frees a pair of RTP/RTCP ports. The data structure is changed to show that the pair of ports is now available. CODEWORK: If an entire port range becomes free, do we release the port range to the operating system ? We probably need a heuristic to do this because allocating a port range again could be an expensive operation. Arguments: wRTPport - This gives the RTP port to be freed. (RTCP port is RTPport+1 which is implicitly freed because we use one bit store the status of both these ports.) Return Values: Returns S_OK on success or E_FAIL if the port is not found in the port pool list. --*/ void PORT_POOL::FreePort ( IN WORD Port) { HRESULT Result; // assert RTP port is even _ASSERTE ((Port & 1) == 0); DWORD Index = 0; DWORD Bitmap = 0x80000000; LIST_ENTRY * ListEntry; PORT_RANGE * PortRange; Lock(); // find the port range that this port belongs to // simple linear scan -- suboptimal Result = E_FAIL; for (ListEntry = PortRangeList.Flink; ListEntry != &PortRangeList; ListEntry = ListEntry -> Flink) { PortRange = CONTAINING_RECORD (ListEntry, PORT_RANGE, ListEntry); if (PortRange -> low <= Port && PortRange -> high >= Port) { Result = S_OK; break; } } if (Result == S_OK) { Index = (Port - PortRange -> low) / (NUM_DWORD_BITS * 2); // assert index is less than the size of the array _ASSERTE (Index < PortRange -> dwAllocListSize); // CODEWORK: make sure that the bit is set i.e. the port has // been previously allocated. Otherwise return an error and print // a warning. // zero the bit to show the pair of ports is now free PortRange -> AllocList [Index] &= ~(Bitmap >> (((Port - PortRange -> low) / 2) % NUM_DWORD_BITS)); DebugF (_T("H323: Deallocated port pair (%04X, %04X).\n"), Port, Port + 1); } else { DebugF (_T("H323: warning, attempted to free port pair (%04X, %04X), but it did not belong to any port range.\n"), Port, Port + 1); } Unlock(); } HRESULT PORT_POOL::CreatePortRange ( OUT PORT_RANGE ** ReturnPortRange) { // CODEWORK: Once we get the new ioctl() for dynamically reserving // port ranges, we need to allocate a new port range here. If the // ioctl() fails we need to return E_FAIL or another error which // says we have run out of ports. // assert low is even and high is odd // _ASSERTE((low % 2) == 0); // _ASSERTE((high % 2) == 1); HRESULT Result; WORD AllocatedLowerPort; WORD LowerPort; DWORD NumPortsInRange; PORT_RANGE * PortRange; DWORD dwAllocListSize; assert (ReturnPortRange); *ReturnPortRange = NULL; Result = ReservePortRange (NUM_PORTS_PER_RANGE, &AllocatedLowerPort); if (FAILED (Result)) return Result; // If the allocated lower port is odd we do not use the lower port // and the range we use starts with the next higher port. if ((AllocatedLowerPort & 1) == 1) { // the allocated region is ODD // don't use the first entry NumPortsInRange = NUM_PORTS_PER_RANGE - 1 - ((NUM_PORTS_PER_RANGE) & 1); LowerPort = AllocatedLowerPort + 1; } else { // the allocated region is EVEN // don't use the last entry NumPortsInRange = NUM_PORTS_PER_RANGE; LowerPort = AllocatedLowerPort; } // If NumPortsInRange is odd, we can not use the last port if ((NumPortsInRange & 1) == 1) { NumPortsInRange--; } // Each bit gives the status (free/allocated) of two consecutive // ports. So, each DWORD can store the status of NUM_DWORD_BITS*2 // ports. We add (NUM_DWORD_BITS*2 - 1) to round up the number of // DWORDS required. dwAllocListSize = (NumPortsInRange + NUM_DWORD_BITS*2 - 1) / (NUM_DWORD_BITS * 2); // allocate space for the AllocList also // Since we do not anticipate too many port ranges being allocated, // we do not require a separate heap for these structures. PortRange = (PORT_RANGE *) EM_MALLOC ( sizeof (PORT_RANGE) + dwAllocListSize * sizeof (DWORD)); if (PortRange == NULL) { Debug (_T("H323: Allocation failure, cannot allocate PORT_RANGE and associated bit map\n")); UnReservePortRange (AllocatedLowerPort); return E_OUTOFMEMORY; } _ASSERTE((LowerPort + NumPortsInRange - 1) <= 0xFFFF); PortRange -> AllocatedLow = AllocatedLowerPort; PortRange -> low = LowerPort; PortRange -> high = (WORD) (LowerPort + NumPortsInRange - 1); PortRange -> dwAllocListSize = dwAllocListSize; PortRange -> AllocList = (DWORD *) (PortRange + 1); DebugF (_T("H323: Allocated port block: [%04X - %04X].\n"), PortRange -> low, PortRange -> high, PortRange -> dwAllocListSize); // Initialize the AllocList to show all the ports are free ZeroMemory (PortRange -> AllocList, (PortRange -> dwAllocListSize) * sizeof (DWORD)); *ReturnPortRange = PortRange; return S_OK; }