// Copyright (c) 1997, Microsoft Corporation, all rights reserved // // util.c // RAS L2TP WAN mini-port/call-manager driver // General utility routines // // 01/07/97 Steve Cobb #include "l2tpp.h" #include "util.tmh" // Debug counts of oddities that should not be happening. // ULONG g_ulAllocTwFailures = 0; //----------------------------------------------------------------------------- // Local prototypes (alphabetically) //----------------------------------------------------------------------------- ULONG atoul( IN CHAR* pszNumber ); VOID ReversePsz( IN OUT CHAR* psz ); VOID TunnelWork( IN NDIS_WORK_ITEM* pWork, IN VOID* pContext ); VOID ultoa( IN ULONG ul, OUT CHAR* pszBuf ); //----------------------------------------------------------------------------- // General utility routines (alphabetically) //----------------------------------------------------------------------------- #if 0 ULONGLONG g_llLastTime2 = 0; ULONGLONG g_llLastTime1 = 0; ULONGLONG g_llLastTime = 0; NDIS_SPIN_LOCK g_lockX; VOID XNdisGetCurrentSystemTime( IN LARGE_INTEGER* plrgTime ) { static BOOLEAN f = 0; if (!f) { NdisAllocateSpinLock( &g_lockX ); f = 1; } NdisGetCurrentSystemTime( plrgTime ); NdisAcquireSpinLock( &g_lockX ); { LONGLONG ll; g_llLastTime2 = g_llLastTime1; g_llLastTime1 = g_llLastTime; g_llLastTime = plrgTime->QuadPart; ll = g_llLastTime - g_llLastTime1; TRACE( TL_I, TM_Spec, ( "Time delta=%d", *((LONG* )&ll) ) ); ASSERT( g_llLastTime >= g_llLastTime1 ); } NdisReleaseSpinLock( &g_lockX ); } #endif VOID AddHostRoute( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs ) // A PTUNNELWORK routine to change an existing host route. // // This routine is called only at PASSIVE IRQL. // { ADAPTERCB* pAdapter; TRACE( TL_N, TM_Misc, ( "AddHostRoute" ) ); // Unpack context information then free the work item. // pAdapter = pTunnel->pAdapter; FREE_TUNNELWORK( pAdapter, pWork ); // Add the host route, noting success for clean-up later, or closing the // tunnel on failure. // pTunnel->pRoute = TdixAddHostRoute( &pAdapter->tdix, pTunnel->address.ulIpAddress, pTunnel->localaddress.ifindex); if (pTunnel->pRoute != NULL) { NDIS_STATUS status; // Setup the connection to do connected udp // if required // pTunnel->pRoute->sPort = pTunnel->address.sUdpPort; status = TdixSetupConnection( &pAdapter->tdix, pTunnel->pRoute, pTunnel->localaddress.ulIpAddress, &pTunnel->udpContext); if(status != STATUS_SUCCESS) { TdixDestroyConnection(&pTunnel->udpContext); TdixDeleteHostRoute(&pAdapter->tdix, pTunnel->address.ulIpAddress); pTunnel->pRoute = NULL; ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_GeneralWithError, (ULONG_PTR )GERR_NoResources, 0, 0, FALSE, FALSE ); } SetFlags( &pTunnel->ulFlags, TCBF_HostRouteAdded ); if (pTunnel->udpContext.hCtrlAddr != NULL) { SetFlags (&pTunnel->ulFlags, TCBF_SendConnected); } } else { ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_GeneralWithError, (ULONG_PTR )GERR_NoResources, 0, 0, FALSE, FALSE ); } } BOOLEAN AdjustSendWindowAtAckReceived( IN ULONG ulMaxSendWindow, IN OUT ULONG* pulAcksSinceSendTimeout, IN OUT ULONG* pulSendWindow ) // Adjust send window/factors for the acknowledge just received. // // Returns true if the send window was changed, false if not. // { // Update the "ack streak" counter and, if a full windows worth has been // received since timing out, bump up the send window. // ++(*pulAcksSinceSendTimeout); if (*pulAcksSinceSendTimeout >= *pulSendWindow && *pulSendWindow < ulMaxSendWindow) { TRACE( TL_N, TM_Send, ( "SW open to %d, %d acks", (*pulSendWindow), *pulAcksSinceSendTimeout ) ); *pulAcksSinceSendTimeout = 0; ++(*pulSendWindow); return TRUE; } return FALSE; } VOID AdjustTimeoutsAtAckReceived( IN LONGLONG llSendTime, IN ULONG ulMaxSendTimeoutMs, OUT ULONG* pulSendTimeoutMs, IN OUT ULONG* pulRoundTripMs, IN OUT LONG* plDeviationMs ) // Adjust send timeout/factors for the acknowledge just received. // { LARGE_INTEGER lrgTime; LONGLONG llSampleMs; ULONG ulSampleMs; LONG lDiff; LONG lDif8; LONG lAbsDif8; LONG lDev8; ULONG ulAto; // First, calculate the "sample", i.e. the time that was actually required // for the round trip. // NdisGetCurrentSystemTime( &lrgTime ); if (llSendTime > lrgTime.QuadPart) { // This shouldn't happen but once it appeared that it did, so this // defensive conditional is included. Maybe NdisGetCurrentSystemTime // has a bug? // TRACE( TL_A, TM_Misc, ( "Future send time?" ) ); llSendTime = lrgTime.QuadPart; } llSampleMs = (lrgTime.QuadPart - llSendTime) / 10000; ASSERT( ((LARGE_INTEGER* )(&llSampleMs))->HighPart == 0 ); ulSampleMs = (ULONG )(((LARGE_INTEGER* )(&llSampleMs))->LowPart); // The typical 'alpha' of 1/8, 'beta' of 1/4, and 'chi' of 4 are used, per // the suggestion in the draft/RFC. To eliminate multiplication and // division, the factors are scaled by 8, calculated, and scaled back. // // Find the intermediate DIFF value, representing the difference between // the estimated and actual round trip times, and the scaled and absolute // scaled values of same. // lDiff = (LONG )ulSampleMs - (LONG )(*pulRoundTripMs); lDif8 = lDiff << 3; lAbsDif8 = (lDif8 < 0) ? -lDif8 : lDif8; // Calculate the scaled new DEV value, representing the approximate // standard deviation. // lDev8 = *plDeviationMs << 3; lDev8 = lDev8 + ((lAbsDif8 - lDev8) << 1); *plDeviationMs = lDev8 >> 3; // Find the scaled new RTT value, representing the estimated round trip // time. The draft/RFC shows the calculation "old RTT + diff", but that's // just the "sample" we found earlier, i.e. the actual round trip time of // this packet. // *pulRoundTripMs = ulSampleMs; // Calculate the ATO value, representing the new send timeout. Because of // clock granularity the timeout might come out 0, which is converted to // the more reasonable 1. // ulAto = (ULONG )(((LONG )*pulRoundTripMs) + (*plDeviationMs << 2)); if (ulAto == 0) { ulAto = 1; } *pulSendTimeoutMs = min( ulAto, ulMaxSendTimeoutMs ); } VOID AdjustTimeoutsAndSendWindowAtTimeout( IN ULONG ulMaxSendTimeoutMs, IN LONG lDeviationMs, OUT ULONG* pulSendTimeoutMs, IN OUT ULONG* pulRoundTripMs, IN OUT ULONG* pulSendWindow, OUT ULONG* pulAcksSinceSendTimeout ) // Adjust send timeout/factors and send window for the timeout that just // occurred. // // Returns true if the send window was changed, false if not. // { ULONG ulNew; // Using the suggested 'delta' of 2, the round trip estimate is doubled. // *pulRoundTripMs <<= 1; // Using the typical 'chi' of 4, the send timeout is increased. Because // of clock granularity the timeout might come out 0, which is converted // to the more reasonable 1. // ulNew = (ULONG )(((LONG )*pulRoundTripMs) + (lDeviationMs << 2)); *pulSendTimeoutMs = min( ulNew, ulMaxSendTimeoutMs ); if (*pulSendTimeoutMs == 0) { *pulSendTimeoutMs = 1; } // The send window is halved. // ulNew = *pulSendWindow >> 1; *pulSendWindow = max( ulNew, 1 ); // Consecutive acknowledge counter is reset. // *pulAcksSinceSendTimeout = 0; } VOID CalculateResponse( IN UCHAR* puchChallenge, IN ULONG ulChallengeLength, IN CHAR* pszPassword, IN UCHAR uchId, OUT UCHAR* puchResponse ) // Loads caller's 16-byte challenge response buffer, 'puchResponse', with // the CHAP-style MD5ed response based on packet ID 'uchId', the // 'ulChallengeLength' byte challenge 'puchChallenge', and the null // terminated password 'pszPassword'. // { ULONG ul; MD5_CTX md5ctx; MD5Init( &md5ctx ); MD5Update( &md5ctx, &uchId, 1 ); MD5Update( &md5ctx, pszPassword, strlen( pszPassword ) ); MD5Update( &md5ctx, puchChallenge, ulChallengeLength ); MD5Final( &md5ctx ); NdisMoveMemory( puchResponse, md5ctx.digest, 16 ); } VOID ChangeHostRoute( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs ) // A PTUNNELWORK routine to change an existing host route. Arg0 is the IP // address of the existing host route to be deleted. Arg1 is the IP // address of the host route to add. // // This routine is called only at PASSIVE IRQL. // { ADAPTERCB* pAdapter; ULONG ulOldIpAddress; ULONG ulNewIpAddress; TRACE( TL_N, TM_Misc, ( "ChangeHostRoute" ) ); // Unpack context information then free the work item. // pAdapter = pTunnel->pAdapter; ulOldIpAddress = (ULONG )(punpArgs[ 0 ]); ulNewIpAddress = (ULONG )(punpArgs[ 1 ]); FREE_TUNNELWORK( pAdapter, pWork ); // Add the new host route, then delete the old one. // if (TdixAddHostRoute( &pAdapter->tdix, ulNewIpAddress, pTunnel->localaddress.ifindex)) { ClearFlags( &pTunnel->ulFlags, TCBF_HostRouteAdded ); TdixDestroyConnection(&pTunnel->udpContext); TdixDeleteHostRoute( &pAdapter->tdix, ulOldIpAddress); } else { ScheduleTunnelWork( pTunnel, NULL, CloseTunnel, 0, 0, 0, 0, FALSE, FALSE ); } } VOID ClearFlags( IN OUT ULONG* pulFlags, IN ULONG ulMask ) // Set 'ulMask' bits in '*pulFlags' flags as an interlocked operation. // { ULONG ulFlags; ULONG ulNewFlags; do { ulFlags = ReadFlags( pulFlags ); ulNewFlags = ulFlags & ~(ulMask); } while (InterlockedCompareExchange( pulFlags, ulNewFlags, ulFlags ) != (LONG )ulFlags); } VOID CloseTdix( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs ) // A PTUNNELWORK routine to close the TDIX context associated with a // tunnel. // // This routine is called only at PASSIVE IRQL. // { ADAPTERCB* pAdapter; TRACE( TL_N, TM_Misc, ( "CloseTdix" ) ); // Unpack context information then free the work item. // pAdapter = pTunnel->pAdapter; FREE_TUNNELWORK( pAdapter, pWork ); // Delete the old host route, and note same in tunnel flags. // TdixClose( &pAdapter->tdix ); ClearFlags( &pTunnel->ulFlags, TCBF_TdixReferenced ); } VOID DeleteHostRoute( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* pulArgs ) // A PTUNNELWORK routine to change an existing host route. // // This routine is called only at PASSIVE IRQL. // { ADAPTERCB* pAdapter; TRACE( TL_N, TM_Misc, ( "DeleteHostRoute" ) ); // Unpack context information then free the work item. // pAdapter = pTunnel->pAdapter; FREE_TUNNELWORK( pAdapter, pWork ); // Destroy the connected udp context // TdixDestroyConnection(&pTunnel->udpContext); // Delete the old host route, and note same in tunnel flags. // TdixDeleteHostRoute( &pAdapter->tdix, pTunnel->address.ulIpAddress); ClearFlags( &pTunnel->ulFlags, TCBF_HostRouteAdded ); } VOID DottedFromIpAddress( IN ULONG ulIpAddress, OUT CHAR* pszIpAddress, IN BOOLEAN fUnicode ) // Converts network byte-ordered IP addresss 'ulIpAddress' to a string in // the a.b.c.d form and returns same in caller's 'pszIpAddress' buffer. // The buffer should be at least 16 characters long. If 'fUnicode' is set // the returned 'pszIpAddress' is in Unicode and must be at least 16 wide // characters long. // { CHAR szBuf[ 3 + 1 ]; ULONG ulA = (ulIpAddress & 0x000000FF); ULONG ulB = (ulIpAddress & 0x0000FF00) >> 8; ULONG ulC = (ulIpAddress & 0x00FF0000) >> 16; ULONG ulD = (ulIpAddress & 0xFF000000) >> 24; ultoa( ulA, szBuf ); strcpy( pszIpAddress, szBuf ); strcat( pszIpAddress, "." ); ultoa( ulB, szBuf ); strcat( pszIpAddress, szBuf ); strcat( pszIpAddress, "." ); ultoa( ulC, szBuf ); strcat( pszIpAddress, szBuf ); strcat( pszIpAddress, "." ); ultoa( ulD, szBuf ); strcat( pszIpAddress, szBuf ); if (fUnicode) { WCHAR* psz; psz = StrDupAsciiToUnicode( pszIpAddress, strlen( pszIpAddress ) ); if (psz) { NdisMoveMemory( pszIpAddress, psz, (StrLenW( psz ) + 1) * sizeof(WCHAR) ); FREE_NONPAGED( psz ); } else { *((WCHAR*)pszIpAddress) = L'\0'; } } } #if 0 NDIS_STATUS ExecuteWork( IN ADAPTERCB* pAdapter, IN NDIS_PROC pProc, IN PVOID pContext, IN ULONG ulArg1, IN ULONG ulArg2, IN ULONG ulArg3, IN ULONG ulArg4 ) // This provides a way to call a routine designed to be called by the // ScheduleWork utility when caller is already at passive IRQL. The // 'pProc' routine is executed inline instead of scheduled. The context // 'pContext' is passed to 'pProc' The extra context arguments 'ulArg1' // and 'ulArg2' are stashed in extra space allocated on the end of the // NDIS_WORK_ITEM. 'PAdapter' is the adapter control block from which the // work item is allocated. // // Returns NDIS_STATUS_SUCCESS or an error code. // { NDIS_STATUS status; NDIS_WORK_ITEM* pWork; // TDI setup must be done at PASSIVE IRQL so schedule a routine to do it. // pWork = ALLOC_NDIS_WORK_ITEM( pAdapter ); if (!pWork) { return NDIS_STATUS_RESOURCES; } ((ULONG*)(pWork + 1))[ 0 ] = ulArg1; ((ULONG*)(pWork + 1))[ 1 ] = ulArg2; ((ULONG*)(pWork + 1))[ 2 ] = ulArg3; ((ULONG*)(pWork + 1))[ 3 ] = ulArg4; pProc( pWork, pContext ); } #endif USHORT GetNextTerminationCallId( IN ADAPTERCB* pAdapter ) // Returns the next unused termination Call-ID. Termination Call-IDs are // IDs out of the VC lookup table range that are used to gracefully // terminate failed incoming calls. // { do { ++pAdapter->usNextTerminationCallId; } while (pAdapter->usNextTerminationCallId < pAdapter->usMaxVcs + 1); return pAdapter->usNextTerminationCallId; } USHORT GetNextTunnelId( IN ADAPTERCB* pAdapter ) // Returns the next tunnel ID to be assigned. // // IMPORTANT: Caller must hold 'pAdapter->lockTunnels'. { while (++pAdapter->usNextTunnelId == 0) ; return pAdapter->usNextTunnelId; } CHAR* GetFullHostNameFromRegistry( VOID ) // Returns a heap block containing an ASCII string of the form // "hostname.domain", or if no domain of the form "hostname". Returns // NULL if none. Caller must eventually call FREE_NONPAGED on the // returned string. // { NTSTATUS status; OBJECT_ATTRIBUTES objattr; UNICODE_STRING uni; HANDLE hParams; CHAR* pszResult; WCHAR* pszFullHostName; KEY_VALUE_PARTIAL_INFORMATION* pHostNameValue; KEY_VALUE_PARTIAL_INFORMATION* pDomainValue; ULONG ulSize; TRACE( TL_I, TM_Cm, ( "GetFullHostNameFromRegistry" ) ); hParams = NULL; pszFullHostName = NULL; pHostNameValue = NULL; pDomainValue = NULL; pszResult = NULL; #define GFHNFR_BufSize 512 do { // Get a handle to the TCPIP Parameters registry key. // RtlInitUnicodeString( &uni, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Tcpip\\Parameters" ); InitializeObjectAttributes( &objattr, &uni, OBJ_CASE_INSENSITIVE, NULL, NULL ); status = ZwOpenKey( &hParams, KEY_QUERY_VALUE, &objattr ); if (status != STATUS_SUCCESS) { TRACE( TL_A, TM_Cm, ( "ZwOpenKey(ipp)=$%08x?", status ) ); break; } // Query the "Hostname" registry value. // pHostNameValue = ALLOC_NONPAGED( GFHNFR_BufSize, MTAG_UTIL ); if (!pHostNameValue) { break; } RtlInitUnicodeString( &uni, L"Hostname" ); status = ZwQueryValueKey( hParams, &uni, KeyValuePartialInformation, pHostNameValue, GFHNFR_BufSize, &ulSize ); if (status != STATUS_SUCCESS || pHostNameValue->Type != REG_SZ || pHostNameValue->DataLength < sizeof(WCHAR) * 2) { TRACE( TL_A, TM_Cm, ( "ZwQValueKey=$%08x?", status ) ); break; } ASSERT(pHostNameValue->DataLength < GFHNFR_BufSize); // Query the "Domain" registry value. // pDomainValue = ALLOC_NONPAGED( GFHNFR_BufSize, MTAG_UTIL ); if (pDomainValue) { RtlInitUnicodeString( &uni, L"Domain" ); status = ZwQueryValueKey( hParams, &uni, KeyValuePartialInformation, pDomainValue, GFHNFR_BufSize, &ulSize ); } else { status = !STATUS_SUCCESS; } // Build a Unicode version of the combined "hostname.domain" or // "hostname". // pszFullHostName = ALLOC_NONPAGED( GFHNFR_BufSize * 2, MTAG_UTIL ); if (!pszFullHostName) { break; } NdisMoveMemory(pszFullHostName, pHostNameValue->Data, pHostNameValue->DataLength); pszFullHostName[pHostNameValue->DataLength/2 - 1] = L'\0'; if (status == STATUS_SUCCESS && pDomainValue->Type == REG_SZ && pDomainValue->DataLength >= sizeof(WCHAR) * 2 && ((WCHAR* )pDomainValue->Data)[ 0 ] != L'\0') { WCHAR* pch; pch = &pszFullHostName[pHostNameValue->DataLength / 2 - 1]; *pch = L'.'; ++pch; NdisMoveMemory( pch, (WCHAR* )pDomainValue->Data, pDomainValue->DataLength); pch[pDomainValue->DataLength/2 - 1] = L'\0'; } // Convert the Unicode version to ASCII. // pszResult = StrDupUnicodeToAscii( pszFullHostName, StrLenW( pszFullHostName ) * sizeof(WCHAR) ); } while (FALSE); if (hParams) { ZwClose( hParams ); } if (pHostNameValue) { FREE_NONPAGED( pHostNameValue ); } if (pDomainValue) { FREE_NONPAGED( pDomainValue ); } if (pszFullHostName) { FREE_NONPAGED( pszFullHostName ); } return pszResult; } ULONG IpAddressFromDotted( IN CHAR* pchIpAddress ) // Convert caller's a.b.c.d IP address string to the network byte-order // numeric equivalent. // // Returns the numeric IP address or 0 if formatted incorrectly. // { INT i; ULONG ulResult; CHAR* pch; ulResult = 0; pch = pchIpAddress; for (i = 1; i <= 4; ++i) { ULONG ulField; ulField = atoul( pch ); if (ulField > 255) return 0; ulResult = (ulResult << 8) + ulField; while (*pch >= '0' && *pch <= '9') ++pch; if (i < 4 && *pch != '.') return 0; ++pch; } return htonl( ulResult ); } VOID IndicateLinkStatus( IN VCCB* pVc, IN LINKSTATUSINFO* pInfo ) // Indicate new WAN_CO_LINKPARAMS settings for 'pVc' to NDISWAN. Caller // should not be holding locks. // { ASSERT( pInfo->params.SendWindow > 0 ); TRACE( TL_I, TM_Mp, ( "NdisMCoIndStatus(LINK) bps=%d sw=%d", pInfo->params.TransmitSpeed, pInfo->params.SendWindow ) ); NdisMCoIndicateStatus( pInfo->MiniportAdapterHandle, pInfo->NdisVcHandle, NDIS_STATUS_WAN_CO_LINKPARAMS, &pInfo->params, sizeof(pInfo->params) ); TRACE( TL_N, TM_Mp, ( "NdisMCoIndStatus done" ) ); } CHAR* MsgTypePszFromUs( IN USHORT usMsgType ) // Debug utility to convert message type attribute code 'usMsgType' to a // corresponding display string. // { static CHAR szBuf[ 5 + 1 ]; static CHAR* aszMsgType[ 16 ] = { "SCCRQ", "SCCRP", "SCCCN", "StopCCN", "StopCCRP???", "Hello", "OCRQ", "OCRP", "OCCN", "ICRQ", "ICRP", "ICCN", "CCR???", "CDN", "WEN", "SLI" }; if (usMsgType >= 1 && usMsgType <= 16) { return aszMsgType[ usMsgType - 1 ]; } else { ultoa( (ULONG )usMsgType, szBuf ); return szBuf; } } #ifndef READFLAGSDIRECT ULONG ReadFlags( IN ULONG* pulFlags ) // Read the value of '*pulFlags' as an interlocked operation. // { return InterlockedExchangeAdd( pulFlags, 0 ); } #endif VOID ScheduleTunnelWork( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN PTUNNELWORK pHandler, IN ULONG_PTR unpArg0, IN ULONG_PTR unpArg1, IN ULONG_PTR unpArg2, IN ULONG_PTR unpArg3, IN BOOLEAN fTcbPreReferenced, IN BOOLEAN fHighPriority ) // Schedules caller's 'pHandler' to be executed in an APC serially with // other work scheduled via this routine. 'PTunnel' is the tunnel to // which the work is related. 'UnpArgX' are the context arguments passed // to caller's 'pHandler'. 'FPreRefenced' indicates caller has already // made the tunnel reference associated with a scheduled work item. This // is a convenience if he already holds 'ADAPTERCB.lockTunnels'. // 'FHighPriority' causes the item to be queued at the head rather than // the tail of the list. // { ADAPTERCB* pAdapter; TUNNELWORK* pWork; pAdapter = pTunnel->pAdapter; if (!fTcbPreReferenced) { // Each queued work item holds a tunnel reference. // ReferenceTunnel( pTunnel, FALSE ); } pWork = ALLOC_TUNNELWORK( pAdapter ); if (!pWork) { // Can't get memory to schedule an APC so there's no // way we'll ever get things cleaned up. // ++g_ulAllocTwFailures; if (!fTcbPreReferenced) { DereferenceTunnel( pTunnel ); } return; } if (pVc) { // Each queued work item that refers to a VC holds a VC reference. // ReferenceVc( pVc ); } pWork->pHandler = pHandler; pWork->pVc = pVc; pWork->aunpArgs[ 0 ] = unpArg0; pWork->aunpArgs[ 1 ] = unpArg1; pWork->aunpArgs[ 2 ] = unpArg2; pWork->aunpArgs[ 3 ] = unpArg3; NdisAcquireSpinLock( &pTunnel->lockWork ); { if (fHighPriority) { InsertHeadList( &pTunnel->listWork, &pWork->linkWork ); TRACE( TL_N, TM_TWrk, ( "Q-TunnelWork($%08x,HIGH)", pHandler ) ); } else { InsertTailList( &pTunnel->listWork, &pWork->linkWork ); TRACE( TL_N, TM_TWrk, ( "Q-TunnelWork($%08x)", pHandler ) ); } // Kickstart the tunnel worker if it's not running already. // if (!(ReadFlags( &pTunnel->ulFlags ) & TCBF_InWork )) { SetFlags( &pTunnel->ulFlags, TCBF_InWork ); TRACE( TL_N, TM_TWrk, ( "Schedule TunnelWork" ) ); ScheduleWork( pAdapter, TunnelWork, pTunnel ); } } NdisReleaseSpinLock( &pTunnel->lockWork ); } NDIS_STATUS ScheduleWork( IN ADAPTERCB* pAdapter, IN NDIS_PROC pProc, IN PVOID pContext ) // Schedules a PASSIVE IRQL callback to routine 'pProc' which will be // passed 'pContext'. 'PAdapter' is the adapter control block from which // the work item is allocated. This routine takes an adapter reference // that should be removed by the called 'pProc'. // // Returns NDIS_STATUS_SUCCESS or an error code. // { NDIS_STATUS status; NDIS_WORK_ITEM* pWork; pWork = ALLOC_NDIS_WORK_ITEM( pAdapter ); if (!pWork) { return NDIS_STATUS_RESOURCES; } NdisInitializeWorkItem( pWork, pProc, pContext ); ReferenceAdapter( pAdapter ); status = NdisScheduleWorkItem( pWork ); if (status != NDIS_STATUS_SUCCESS) { FREE_NDIS_WORK_ITEM( pAdapter, pWork ); DereferenceAdapter( pAdapter ); } return status; } VOID SetFlags( IN OUT ULONG* pulFlags, IN ULONG ulMask ) // Set 'ulMask' bits in '*pulFlags' flags as an interlocked operation. // { ULONG ulFlags; ULONG ulNewFlags; do { ulFlags = InterlockedExchangeAdd( pulFlags, 0 ); ulNewFlags = ulFlags | ulMask; } while (InterlockedCompareExchange( pulFlags, ulNewFlags, ulFlags ) != (LONG )ulFlags); } WCHAR* StrDupNdisString( IN NDIS_STRING* pNdisString ) // Returns null-terminated Unicode copy of the NDIS_STRING 'pNdisString' // Caller must eventually call FREE_NONPAGED on the returned string. // { WCHAR* pwszDup = NULL; if(pNdisString->Length >= sizeof(WCHAR) && (pNdisString->Length & 1) == 0 && pNdisString->Buffer[0] != L'\0') { pwszDup = ALLOC_NONPAGED( pNdisString->Length + sizeof(WCHAR), MTAG_UTIL ); if (pwszDup) { NdisMoveMemory( pwszDup, pNdisString->Buffer, pNdisString->Length ); pwszDup[pNdisString->Length / sizeof(WCHAR)] = L'\0'; } } return pwszDup; } CHAR* StrDupNdisStringToA( IN NDIS_STRING* pNdisString ) // Returns null-terminated ASCII copy of the NDIS_STRING 'pNdisString' // Caller must eventually call FREE_NONPAGED on the returned string. // { return StrDupUnicodeToAscii( pNdisString->Buffer, pNdisString->Length ); } CHAR* StrDupNdisVarDataDescStringToA( IN NDIS_VAR_DATA_DESC UNALIGNED* pDesc ) // Returns null-terminated ASCII copy of the NDIS_VAR_DATA_DESC string // 'pDesc'. Caller must eventually call FREE_NON-PAGED on the returned // string. // { return StrDupUnicodeToAscii( (WCHAR* )(((CHAR* )pDesc) + pDesc->Offset), pDesc->Length ); } CHAR* StrDupSized( IN CHAR* psz, IN ULONG ulLength, IN ULONG ulExtra ) // Return a duplicate of the first 'ulLength' bytes of 'psz' followed by a // null character and 'ulExtra' extra bytes, or NULL on error. Caller // must eventually call FREE_NONPAGED on the returned string. // { CHAR* pszDup = NULL; if(ulLength && psz[0] != '\0') { pszDup = ALLOC_NONPAGED( ulLength + 1 + ulExtra, MTAG_UTIL ); if (pszDup) { NdisMoveMemory( pszDup, psz, ulLength ); pszDup[ ulLength ] = '\0'; } } return pszDup; } CHAR* StrDupUnicodeToAscii( IN WCHAR* pwsz, IN ULONG ulPwszBytes ) // Returns an ASCII duplicate of Unicode string 'pwsz', where 'pwsz' is // 'ulPwszBytes' in length and not necessarily null terminated. A null // terminator is added to the ASCII result. The "conversion" consists of // picking out every other byte, hopefully all the non-zero ones. This is // not foolproof, but then Unicode doesn't convert to ASCII in any // foolproof way. It is caller's responsibility to FREE_NONPAGED the // returned string, if non-NULL. // { CHAR* pszDup = NULL; // Validate the input parameters // Don't allow empty string if(ulPwszBytes >= sizeof(WCHAR) && (ulPwszBytes & 1) == 0 && pwsz[0] != L'\0' && *((PCHAR)pwsz + 1) == '\0') { pszDup = ALLOC_NONPAGED( ulPwszBytes/2 + 1, MTAG_UTIL ); if (pszDup) { ULONG i; for (i = 0; i < ulPwszBytes / sizeof(WCHAR); ++i) { pszDup[ i ] = (CHAR)pwsz[ i ]; } pszDup[ulPwszBytes / sizeof(WCHAR)] = '\0'; } } return pszDup; } WCHAR* StrDupAsciiToUnicode( IN CHAR* psz, IN ULONG ulPszBytes ) // Returns a Unicode duplicate of ASCII string 'psz', where 'psz' is // 'ulPszBytes' in length and not necessarily null terminated. A null // terminator is added to the Unicode result. The "conversion" consists // of adding zero characters every other byte. This is not foolproof, but // is OK for numericals like IP address strings, avoiding the change to // PASSIVE IRQL required to use the real RTL conversions. It is caller's // responsibility to FREE_NONPAGED the returned string, if non-NULL. // { WCHAR* pwszDup = NULL; if(ulPszBytes >= sizeof(CHAR) && psz[0] != '\0') { pwszDup = (WCHAR* )ALLOC_NONPAGED( (ulPszBytes + 1) * sizeof(WCHAR), MTAG_UTIL ); if (pwszDup) { ULONG i; for (i = 0; i < ulPszBytes; ++i) { pwszDup[ i ] = (WCHAR )(psz[ i ]); } pwszDup[ i ] = L'\0'; } } return pwszDup; } ULONG StrLenW( IN WCHAR* psz ) // Return the length in characters of null terminated wide string 'psz'. // { ULONG ulLen; ulLen = 0; if (psz) { while (*psz++ != L'\0') { ++ulLen; } } return ulLen; } TUNNELCB* TunnelCbFromIpAddressAndAssignedTunnelId( IN ADAPTERCB* pAdapter, IN ULONG ulIpAddress, IN USHORT usUdpPort, IN USHORT usAssignedTunnelId ) // Return the tunnel control block associated with 'ulIpAddress' in // 'pAdapter's list of TUNNELCBs or NULL if not found. If // 'usAssignedTunnelId' is non-zero, that must match as well, otherwise it // is ignored. Tunnels in the process of closing are not returned. // // IMPORTANT: Caller must hold 'pAdapter->lockTunnels'. // { TUNNELCB* pTunnel; LIST_ENTRY* pLink; pTunnel = NULL; for (pLink = pAdapter->listTunnels.Flink; pLink != &pAdapter->listTunnels; pLink = pLink->Flink) { TUNNELCB* pThis; pThis = CONTAINING_RECORD( pLink, TUNNELCB, linkTunnels ); if (pThis->address.ulIpAddress == ulIpAddress && (!usUdpPort || usUdpPort == pThis->address.sUdpPort) && (!usAssignedTunnelId || usAssignedTunnelId == pThis->usAssignedTunnelId)) { BOOLEAN fClosing; fClosing = !!(ReadFlags( &pThis->ulFlags ) & TCBF_Closing); if (fClosing) { TRACE( TL_A, TM_Misc, ( "Closing pT=$%p skipped", pThis ) ); } else { pTunnel = pThis; break; } } } return pTunnel; } VOID TransferLinkStatusInfo( IN VCCB* pVc, OUT LINKSTATUSINFO* pInfo ) // Transfer information from 'pVc' to callers 'pInfo' block in preparation // for a call to IndicateLinkStatus after 'lockV' has been released. // // IMPORTANT: Caller must hold 'pVc->lockV'. // { ADAPTERCB* pAdapter; pAdapter = pVc->pAdapter; pInfo->MiniportAdapterHandle = pAdapter->MiniportAdapterHandle; pInfo->NdisVcHandle = pVc->NdisVcHandle; // // Convert to bytes per second // pInfo->params.TransmitSpeed = pVc->ulConnectBps/8; pInfo->params.ReceiveSpeed = pInfo->params.TransmitSpeed/8; pInfo->params.SendWindow = min( pVc->ulSendWindow, pAdapter->info.MaxSendWindow ); } VOID TunnelWork( IN NDIS_WORK_ITEM* pWork, IN VOID* pContext ) // An NDIS_PROC routine to execute work from a tunnel work queue. The // context passed is the TUNNELCB, which has been referenced for this // operation. // // This routine is called only at PASSIVE IRQL. // { ADAPTERCB* pAdapter; TUNNELCB* pTunnel; LIST_ENTRY* pLink; LONG lDerefTunnels; // Unpack context information then free the work item. // pTunnel = (TUNNELCB* )pContext; pAdapter = pTunnel->pAdapter; FREE_NDIS_WORK_ITEM( pAdapter, pWork ); // Execute all work queued on the tunnel serially. // lDerefTunnels = 0; NdisAcquireSpinLock( &pTunnel->lockWork ); { ASSERT( ReadFlags( &pTunnel->ulFlags ) & TCBF_InWork ); while (!IsListEmpty( &pTunnel->listWork )) { TUNNELWORK* pTunnelWork; pLink = RemoveHeadList( &pTunnel->listWork ); InitializeListHead( pLink ); pTunnelWork = CONTAINING_RECORD( pLink, TUNNELWORK, linkWork ); TRACE( TL_N, TM_TWrk, ( "\nL2TP: TUNNELWORK=$%08x", pTunnelWork->pHandler ) ); NdisReleaseSpinLock( &pTunnel->lockWork ); { VCCB* pVc; pVc = pTunnelWork->pVc; pTunnelWork->pHandler( pTunnelWork, pTunnel, pVc, pTunnelWork->aunpArgs ); if (pVc) { DereferenceVc( pVc ); } ++lDerefTunnels; } NdisAcquireSpinLock( &pTunnel->lockWork ); } ClearFlags( &pTunnel->ulFlags, TCBF_InWork ); } NdisReleaseSpinLock( &pTunnel->lockWork ); while (lDerefTunnels--) { DereferenceTunnel( pTunnel ); } // Remove the reference for scheduled work. // DereferenceAdapter( pAdapter ); } VOID UpdateGlobalCallStats( IN VCCB* pVc ) // Add the call statistics in 'pVc' to the global call statistics. // // IMPORTANT: Caller must hold 'pVc->lockV'. // { extern CALLSTATS g_stats; extern NDIS_SPIN_LOCK g_lockStats; CALLSTATS* pStats; pStats = &pVc->stats; if (pStats->ulSeconds == 0) { return; } NdisAcquireSpinLock( &g_lockStats ); { ++g_stats.llCallUp; g_stats.ulSeconds += pStats->ulSeconds; g_stats.ulDataBytesRecd += pStats->ulDataBytesRecd; g_stats.ulDataBytesSent += pStats->ulDataBytesSent; g_stats.ulRecdDataPackets += pStats->ulRecdDataPackets; g_stats.ulDataPacketsDequeued += pStats->ulDataPacketsDequeued; g_stats.ulRecdZlbs += pStats->ulRecdZlbs; g_stats.ulRecdResets += pStats->ulRecdResets; g_stats.ulRecdResetsIgnored += pStats->ulRecdResetsIgnored; g_stats.ulSentDataPacketsSeq += pStats->ulSentDataPacketsSeq; g_stats.ulSentDataPacketsUnSeq += pStats->ulSentDataPacketsUnSeq; g_stats.ulSentPacketsAcked += pStats->ulSentPacketsAcked; g_stats.ulSentPacketsTimedOut += pStats->ulSentPacketsTimedOut; g_stats.ulSentZAcks += pStats->ulSentZAcks; g_stats.ulSentResets += pStats->ulSentResets; g_stats.ulSendWindowChanges += pStats->ulSendWindowChanges; g_stats.ulSendWindowTotal += pStats->ulSendWindowTotal; g_stats.ulMaxSendWindow += pStats->ulMaxSendWindow; g_stats.ulMinSendWindow += pStats->ulMinSendWindow; g_stats.ulRoundTrips += pStats->ulRoundTrips; g_stats.ulRoundTripMsTotal += pStats->ulRoundTripMsTotal; g_stats.ulMaxRoundTripMs += pStats->ulMaxRoundTripMs; g_stats.ulMinRoundTripMs += pStats->ulMinRoundTripMs; } NdisReleaseSpinLock( &g_lockStats ); TRACE( TL_I, TM_Stat, ( ".--- CALL STATISTICS -------------------------" ) ); TRACE( TL_I, TM_Stat, ( "| Duration: %d minutes, %d seconds", pStats->ulSeconds / 60, pStats->ulSeconds % 60 ) ); TRACE( TL_I, TM_Stat, ( "| Data out: %d bytes, %d/sec, %d/pkt", pStats->ulDataBytesSent, AVGTRACE( pStats->ulDataBytesSent, pStats->ulSeconds ), AVGTRACE( pStats->ulDataBytesSent, pStats->ulRecdDataPackets ) ) ); TRACE( TL_I, TM_Stat, ( "| Data in: %d bytes, %d/sec, %d/pkt", pStats->ulDataBytesRecd, AVGTRACE( pStats->ulDataBytesRecd, pStats->ulSeconds ), AVGTRACE( pStats->ulDataBytesRecd, pStats->ulSentDataPacketsSeq + pStats->ulSentDataPacketsUnSeq ) ) ); TRACE( TL_I, TM_Stat, ( "| Acks in: %d/%d (%d%%) %d flushed", pStats->ulSentPacketsAcked, pStats->ulSentDataPacketsSeq, PCTTRACE( pStats->ulSentPacketsAcked, pStats->ulSentPacketsAcked + pStats->ulSentPacketsTimedOut ), pStats->ulSentDataPacketsSeq + pStats->ulSentDataPacketsUnSeq - pStats->ulSentPacketsAcked - pStats->ulSentPacketsTimedOut ) ); TRACE( TL_I, TM_Stat, ( "| Misordered: %d (%d%%)", pStats->ulDataPacketsDequeued, PCTTRACE( pStats->ulDataPacketsDequeued, pStats->ulRecdDataPackets ) ) ); TRACE( TL_I, TM_Stat, ( "| Out: Resets=%d ZAcks=%d UnSeqs=%d", pStats->ulSentResets, pStats->ulSentZAcks, pStats->ulSentDataPacketsUnSeq ) ); TRACE( TL_I, TM_Stat, ( "| In: Resets=%d (%d%% old) Zlbs=%d", pStats->ulRecdResets, PCTTRACE( pStats->ulRecdResetsIgnored, pStats->ulRecdResets ), pStats->ulRecdZlbs ) ); TRACE( TL_I, TM_Stat, ( "| Send window: Min=%d Avg=%d Max=%d Changes=%d", pStats->ulMinSendWindow, AVGTRACE( pStats->ulSendWindowTotal, pStats->ulSentDataPacketsSeq ), pStats->ulMaxSendWindow, pStats->ulSendWindowChanges ) ); TRACE( TL_I, TM_Stat, ( "| Trip in ms: Min=%d Avg=%d Max=%d", pStats->ulMinRoundTripMs, AVGTRACE( pStats->ulRoundTripMsTotal, pStats->ulRoundTrips ), pStats->ulMaxRoundTripMs ) ); TRACE( TL_I, TM_Stat, ( "'---------------------------------------------" ) ); } //----------------------------------------------------------------------------- // Local utility routines (alphabetically) //----------------------------------------------------------------------------- ULONG atoul( IN CHAR* pszNumber ) // Convert string of digits 'pszNumber' to it's ULONG value. // { ULONG ulResult; ulResult = 0; while (*pszNumber) { if(*pszNumber >= '0' && *pszNumber <= '9') { ulResult *= 10; ulResult += *pszNumber - '0'; } else { break; } ++pszNumber; } return ulResult; } VOID ReversePsz( IN OUT CHAR* psz ) // Reverse the order of the characters in 'psz' in place. // { CHAR* pchLeft; CHAR* pchRight; pchLeft = psz; pchRight = psz + strlen( psz ) - 1; while (pchLeft < pchRight) { CHAR ch; ch = *pchLeft; *pchLeft = *pchRight; *pchRight = ch; ++pchLeft; --pchRight; } } VOID ultoa( IN ULONG ul, OUT CHAR* pszBuf ) // Convert 'ul' to null-terminated string form in caller's 'pszBuf'. It's // caller job to make sure 'pszBuf' is long enough to hold the returned // string. // { CHAR* pch; pch = pszBuf; do { *pch++ = (CHAR )((ul % 10) + '0'); ul /= 10; } while (ul); *pch = '\0'; ReversePsz( pszBuf ); }