windows-nt/Source/XPSP1/NT/net/rras/ndis/rasl2tp/util.c
2020-09-26 16:20:57 +08:00

1672 lines
42 KiB
C

// 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"
// 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->address.sUdpPort);
if (pTunnel->pRoute != NULL)
{
NDIS_STATUS status;
// Setup the connection to do connected udp
// if required
//
status = TdixSetupConnection(
&pAdapter->tdix, pTunnel->address.ulIpAddress,
pTunnel->address.sUdpPort,
pTunnel->pRoute,
&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;
}
#if 0
VOID
BuildWanAddress(
IN CHAR* pArg1,
IN ULONG ulLength1,
IN CHAR* pArg2,
IN ULONG ulLength2,
IN CHAR* pArg3,
IN ULONG ulLength3,
OUT WAN_ADDRESS* pWanAddress )
// Builds a '\0' separated token list in WAN address 'pWanAddress',
// consisting of the 3 arguments. If the arguments are too long the last
// ones are truncated.
//
{
CHAR* pch;
ULONG ulLengthLeft;
ULONG ulCopyLength;
// Reserve room for 3 end-of-argument null characters, plus a final null.
//
NdisZeroMemory( &pWanAddress->Address[ MAX_WAN_ADDRESSLENGTH - 4 ], 4 );
pch = pWanAddress->Address;
ulLengthLeft = MAX_WAN_ADDRESSLENGTH - 4;
ulCopyLength = min( ulLength1, ulLengthLeft );
if (ulCopyLength)
{
NdisMoveMemory( pch, pArg1, ulCopyLength );
ulLengthLeft -= ulCopyLength;
pch += ulCopyLength;
}
*pch++ = '\0';
ulCopyLength = min( ulLength2, ulLengthLeft );
if (ulCopyLength)
{
NdisMoveMemory( pch, pArg2, ulCopyLength );
ulLengthLeft -= ulCopyLength;
pch += ulCopyLength;
}
*pch++ = '\0';
ulCopyLength = min( ulLength3, ulLengthLeft );
if (ulCopyLength)
{
NdisMoveMemory( pch, pArg3, ulCopyLength );
pch += ulCopyLength;
}
*pch++ = '\0';
*pch++ = '\0';
pWanAddress->AddressLength = (ULONG )(pch - pWanAddress->Address);
}
#endif
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->address.sUdpPort))
{
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)
{
ASSERT( !"Alloc work" );
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
#if 0
VOID
ExplodeWanAddress(
IN WAN_ADDRESS* pWanAddress,
OUT CHAR** ppArg1,
OUT ULONG* pulLength1,
OUT CHAR** ppArg2,
OUT ULONG* pulLength2,
OUT CHAR** ppArg3,
OUT ULONG* pulLength3 )
// Returns the '\0'-separated tokens in WAN address 'pWanAddress', and
// their lengths.
//
{
CHAR* pch;
// Make sure 3 null characters will be found before going off the end of
// the buffer.
//
pch = &pWanAddress->Address[ MAX_WAN_ADDRESSLENGTH - 3 ];
NdisZeroMemory( pch, 3 );
*ppArg1 = pWanAddress->Address;
*pulLength1 = (ULONG )strlen( *ppArg1 );
*ppArg2 = *ppArg1 + *pulLength1 + 1;
*pulLength2 = (ULONG )strlen( *ppArg2 );
*ppArg3 = *ppArg2 + *pulLength2 + 1;
*pulLength3 = (ULONG )strlen( *ppArg3 );
}
#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)
{
TRACE( TL_A, TM_Cm, ( "ZwQValueKey=$%08x?", status ) );
break;
}
// 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;
}
StrCpyW( pszFullHostName, (WCHAR* )pHostNameValue->Data );
if (status == STATUS_SUCCESS
&& pDomainValue->Type == REG_SZ
&& pDomainValue->DataLength > sizeof(WCHAR)
&& ((WCHAR* )pDomainValue->Data)[ 0 ] != L'\0')
{
WCHAR* pch;
pch = &pszFullHostName[ StrLenW( pszFullHostName ) ];
*pch = L'.';
++pch;
StrCpyW( pch, (WCHAR* )pDomainValue->Data );
}
// 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;
LONG lResult;
CHAR* pch;
lResult = 0;
pch = pchIpAddress;
for (i = 1; i <= 4; ++i)
{
LONG lField;
lField = atoul( pch );
if (lField > 255)
return 0;
lResult = (lResult << 8) + lField;
while (*pch >= '0' && *pch <= '9')
++pch;
if (i < 4 && *pch != '.')
return 0;
++pch;
}
return htonl( lResult );
}
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" ) );
}
#if DBG
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;
}
}
#endif
#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.
//
ASSERT( !"Alloc TWork?" );
++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)
{
ASSERT( !"Alloc work?" );
return NDIS_STATUS_RESOURCES;
}
NdisInitializeWorkItem( pWork, pProc, pContext );
ReferenceAdapter( pAdapter );
status = NdisScheduleWorkItem( pWork );
if (status != NDIS_STATUS_SUCCESS)
{
ASSERT( !"SchedWork?" );
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);
}
VOID
StrCpyW(
IN WCHAR* psz1,
IN WCHAR* psz2 )
// Copies 'psz2' to 'psz1'.
//
{
while (*psz2)
{
*psz1++ = *psz2++;
}
*psz1 = L'\0';
}
CHAR*
StrDup(
IN CHAR* psz )
// Return a duplicate of 'psz'. Caller must eventually call FREE_NONPAGED
// on the returned string.
//
{
return StrDupSized( psz, strlen( psz ), 0 );
}
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* pszDup;
pszDup = ALLOC_NONPAGED( pNdisString->Length + sizeof(WCHAR), MTAG_UTIL );
if (pszDup)
{
NdisZeroMemory( pszDup, pNdisString->Length + sizeof(WCHAR) );
if (pNdisString->Length)
{
NdisMoveMemory( pszDup, pNdisString->Buffer, pNdisString->Length );
}
}
return pszDup;
}
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 );
}
#if 0
CHAR*
StrDupNdisVarDataDescStringA(
IN NDIS_VAR_DATA_DESC* pDesc )
// Returns null-terminated copy of the NDIS_VAR_DATA_DESC ANSI/ASCII
// string 'pDesc'. Caller must eventually call FREE_NON-PAGED on the
// returned string.
//
{
CHAR* pszDup;
pszDup = ALLOC_NONPAGED( pDesc->Length + 1, MTAG_UTIL );
if (pszDup)
{
NdisZeroMemory( pszDup, pDesc->Length + 1 );
if (pDesc->Length)
{
NdisMoveMemory(
pszDup, ((CHAR* )pDesc) + pDesc->Offset, pDesc->Length );
}
}
return pszDup;
}
#endif
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;
pszDup = ALLOC_NONPAGED( ulLength + 1 + ulExtra, MTAG_UTIL );
if (pszDup)
{
if (ulLength)
{
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;
pszDup = ALLOC_NONPAGED( ulPwszBytes + 1, MTAG_UTIL );
if (pszDup)
{
*((WCHAR* )pszDup) = L'\0';
if (ulPwszBytes)
{
NdisMoveMemory( pszDup, pwsz, ulPwszBytes );
}
if (ulPwszBytes > 1 && pszDup[ 1 ] == '\0')
{
ULONG i;
for (i = 0; i * 2 < ulPwszBytes; ++i)
{
pszDup[ i ] = pszDup[ i * 2 ];
}
pszDup[ i ] = '\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* pszDup;
pszDup = (WCHAR* )ALLOC_NONPAGED(
(ulPszBytes + 1) * sizeof(WCHAR), MTAG_UTIL );
if (pszDup)
{
CHAR* pszDupA;
ULONG i;
pszDupA = (CHAR* )pszDup;
for (i = 0; i < ulPszBytes; ++i)
{
pszDup[ i ] = (WCHAR )(psz[ i ]);
}
pszDup[ i ] = L'\0';
}
return pszDup;
}
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 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
&& (!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* pWork;
pLink = RemoveHeadList( &pTunnel->listWork );
InitializeListHead( pLink );
pWork = CONTAINING_RECORD( pLink, TUNNELWORK, linkWork );
TRACE( TL_N, TM_TWrk,
( "\nL2TP: TUNNELWORK=$%08x", pWork->pHandler ) );
NdisReleaseSpinLock( &pTunnel->lockWork );
{
VCCB* pVc;
pVc = pWork->pVc;
pWork->pHandler( pWork, pTunnel, pVc, pWork->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 && *pszNumber >= '0' && *pszNumber <= '9')
{
ulResult *= 10;
ulResult += *pszNumber - '0';
++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 );
}