|
|
// Copyright (c) 1997, Microsoft Corporation, all rights reserved
//
// receive.c
// RAS L2TP WAN mini-port/call-manager driver
// Receive routines
//
// 01/07/97 Steve Cobb
#include "l2tpp.h"
#include "receive.tmh"
extern LONG g_lPacketsIndicated;
//-----------------------------------------------------------------------------
// Local prototypes (alphabetically)
//-----------------------------------------------------------------------------
SHORT CompareSequence( USHORT us1, USHORT us2 );
VOID ControlAcknowledged( IN TUNNELCB* pTunnel, IN USHORT usReceivedNr );
VOID ControlAckTimerEvent( IN TIMERQITEM* pItem, IN VOID* pContext, IN TIMERQEVENT event );
USHORT ExplodeAvpHeader( IN CHAR* pAvp, IN USHORT usMaxAvpLength, OUT AVPINFO* pInfo );
VOID ExplodeControlAvps( IN CHAR* pFirstAvp, IN CHAR* pEndOfBuffer, OUT CONTROLMSGINFO* pControl );
USHORT ExplodeL2tpHeader( IN CHAR* pL2tpHeader, IN ULONG ulBufferLength, IN OUT L2TPHEADERINFO* pInfo );
USHORT GetAvpValueFixedAch( IN AVPINFO* pAvp, IN USHORT usArraySize, OUT CHAR** ppch );
USHORT GetAvpValueFixedAul( IN AVPINFO* pAvp, IN USHORT usArraySize, OUT UNALIGNED ULONG** paulArray );
USHORT GetAvpValueFlag( IN AVPINFO* pAvp, OUT UNALIGNED BOOLEAN* pf );
USHORT GetAvpValueUl( IN AVPINFO* pAvp, OUT UNALIGNED ULONG** ppul );
USHORT GetAvpValueUs( IN AVPINFO* pAvp, OUT UNALIGNED USHORT** ppus );
USHORT GetAvpValue2UsAndVariableAch( IN AVPINFO* pAvp, OUT UNALIGNED USHORT** ppus1, OUT UNALIGNED USHORT** ppus2, OUT CHAR** ppch, OUT USHORT* pusArraySize );
USHORT GetAvpValueVariableAch( IN AVPINFO* pAvp, OUT CHAR** ppch, OUT USHORT* pusArraySize );
VOID GetCcAvps( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl, OUT USHORT* pusResult, OUT USHORT* pusError );
VOID HelloTimerEvent( IN TIMERQITEM* pItem, IN VOID* pContext, IN TIMERQEVENT event );
VOID IndicateReceived( IN VCCB* pVc, IN CHAR* pBuffer, IN ULONG ulOffset, IN ULONG ulLength, IN LONGLONG llTimeReceived );
BOOLEAN LookUpTunnelAndVcCbs( IN ADAPTERCB* pAdapter, IN USHORT* pusTunnelId, IN USHORT* pusCallId, IN L2TPHEADERINFO* pHeader, IN CONTROLMSGINFO* pControl, OUT TUNNELCB** ppTunnel, OUT VCCB** ppVc );
VOID PayloadAcknowledged( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN USHORT usReceivedNr );
VOID PayloadAckTimerEvent( IN TIMERQITEM* pItem, IN VOID* pContext, IN TIMERQEVENT event );
BOOLEAN ReceiveControl( IN ADAPTERCB* pAdapter, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CHAR* pBuffer, IN ULONG ulAvpOffset, IN ULONG ulAvpLength, IN TDIXRDGINFO* pRdg, IN L2TPHEADERINFO* pInfo, IN CONTROLMSGINFO* pControl );
BOOLEAN ReceiveFromOutOfOrder( IN VCCB* pVc );
BOOLEAN ReceivePayload( IN ADAPTERCB* pAdapter, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CHAR* pBuffer, IN ULONG ulPayloadOffset, IN ULONG ulPayloadLength, IN L2TPHEADERINFO* pInfo );
VOID ScheduleControlAck( IN TUNNELCB* pTunnel, IN USHORT usMsgTypeToAcknowledge );
VOID SchedulePayloadAck( IN TUNNELCB* pTunnel, IN VCCB* pVc );
VCCB* VcCbFromCallId( IN TUNNELCB* pTunnel, IN USHORT usCallId );
VOID ZombieAckIfNecessary( IN TUNNELCB* pTunnel, IN L2TPHEADERINFO* pHeader, IN CONTROLMSGINFO* pControl );
//-----------------------------------------------------------------------------
// Main receive handlers
//-----------------------------------------------------------------------------
VOID L2tpReceive( IN TDIXCONTEXT* pTdix, IN TDIXRDGINFO* pRdg, IN CHAR* pBuffer, IN ULONG ulOffset, IN ULONG ulBufferLength )
// TDIXRECEIVEDG handler that receives all incoming L2TP traffic. 'PTdix'
// is our TDI extension context. 'PRdg' points to the RDGINFO context
// 'PBuffer' is the address of the virtual buffer associated with an NDIS
// buffer from our pool passed to TDIX during initialization. We are
// responsible for eventually calling FreeBufferToPool on 'pBuffer'.
// 'UlOffset' is the offset to the first usable data in 'pBuffer'.
// 'UlBufferLen' is the data byte count of 'pBuffer'.
//
{ USHORT usXError; NDIS_STATUS status; L2TPHEADERINFO info; CONTROLMSGINFO* pControl; ADAPTERCB* pAdapter; TUNNELCB* pTunnel; VCCB* pVc; BOOLEAN fFreeBuffer; ULONG ulAvpOffset = 0; ULONG ulAvpLength = 0; TDIXIPADDRESS* pAddress = &pRdg->source;
DUMPW( TL_A, TM_MDmp, pBuffer + ulOffset, 16 );
pAdapter = CONTAINING_RECORD( pTdix, ADAPTERCB, tdix );
fFreeBuffer = TRUE; pTunnel = NULL; pVc = NULL; pControl = NULL;
do { // Parse the packet's L2TP header into a conveniently usable form,
// checking that it is consistent with itself and indicates a protocol
// version we know.
//
if(ulOffset >= ulBufferLength || (ulBufferLength - ulOffset < L2TP_MinHeaderSize)) { // Invalid length
TRACE( TL_A, TM_Recv, ( "Discard: invalid recv buffer length" ) ); WPLOG( LL_A, LM_Recv, ( "Discard: invalid recv buffer length" ) ); break; } usXError = ExplodeL2tpHeader(pBuffer + ulOffset, ulBufferLength - ulOffset, &info ); if (usXError != GERR_None) { // Not a coherent L2TP header. Discard the packet.
//
TRACE( TL_A, TM_Recv, ( "Discard: invalid L2TP Header" ) ); WPLOG( LL_A, LM_Recv, ( "Discard: invalid L2TP Header" ) ); break; }
ASSERT( info.ulDataLength <= L2TP_MaxFrameSize );
if (*info.pusBits & HBM_T) { WPLOG( LL_M, LM_CMsg, ( "RECV <- %!IPADDR!/%d Length=%d, Tid %d, Cid %d, Ns=%d, Nr=%d", pAddress->ulIpAddress, ntohs(pAddress->sUdpPort), ulBufferLength - ulOffset, *info.pusTunnelId, *info.pusCallId, info.pusNs ? *info.pusNs : 0, info.pusNr ? *info.pusNr : 0) ); // Explode the control message into the conveniently usable
// 'control' form, while checking it for coherency. This must be
// done here so the LookUp routine can peek ahead at the assigned
// call ID in CallDisconnNotify, if necessary. Ugly, but that's
// the way L2TP is defined.
//
pControl = ALLOC_CONTROLMSGINFO( pAdapter ); if (pControl) { ulAvpOffset = (ULONG )(info.pData - pBuffer); ulAvpLength = info.ulDataLength;
if (ulAvpLength) { ExplodeControlAvps( pBuffer + ulAvpOffset, pBuffer + ulAvpOffset + ulAvpLength, pControl ); } else { // No AVPs. Most likely a ZACK.
//
pControl->usXError = GERR_BadValue; } } else { TRACE( TL_A, TM_Recv, ( "***Failed to allocate CONTROLMSGINFO" ) ); WPLOG( LL_A, LM_Recv, ( "***Failed to allocate CONTROLMSGINFO" ) ); break; } }
// Find the tunnel and VC control blocks based on the header values.
//
if (!LookUpTunnelAndVcCbs( pAdapter, info.pusTunnelId, info.pusCallId, &info, pControl, &pTunnel, &pVc )) { // Invalid Tunnel-ID/Call-ID combination. Discard the packet.
// Zombie acknowledge may have been performed if the packet was a
// CDN.
//
// The draft/RFC says the tunnel should be closed and restarted on
// receipt of a malformed Control Connection message. Seems
// pretty harsh. For now, just discard such packets.
//
break; }
if (pTunnel) { // Verify this packet comes from the right source address
if(pTunnel->address.ulIpAddress != pAddress->ulIpAddress) { // Drop this packet
break; }
// Any message received on a tunnel resets it's Hello timer.
//
ResetHelloTimer( pTunnel ); } if (*info.pusBits & HBM_T) { // It's a tunnel or call control packet.
//
if (pControl) { fFreeBuffer = ReceiveControl( pAdapter, pTunnel, pVc, pBuffer, ulAvpOffset, ulAvpLength, pRdg, &info, pControl ); } } else { // It's a VC payload packet.
//
if (!pVc) { TRACE( TL_A, TM_Recv, ( "Payload w/o VC?" ) ); WPLOG( LL_A, LM_Recv, ( "Payload w/o VC?" ) ); break; }
#if 0
// !!! This is a hack to force NDISWAN into PPP framing mode.
// Need a cleaner way to do this, or simply have NDISWAN assume it
// for L2TP links. (NDISWAN bug 152167)
//
if (pVc->usNr == 0) { CHAR* pBufferX;
pBufferX = GetBufferFromPool( &pAdapter->poolFrameBuffers ); if (pBufferX) { pBufferX[ 0 ] = (CHAR )0xFF; pBufferX[ 1 ] = (CHAR )0x03; pBufferX[ 2 ] = (CHAR )0xC0; pBufferX[ 3 ] = (CHAR )0x21; pBufferX[ 4 ] = (CHAR )0x01; pBufferX[ 5 ] = (CHAR )0x06;
IndicateReceived( pVc, pBufferX, 0, 6, (ULONGLONG )0 ); } } #endif
if (ReferenceCall( pVc )) { fFreeBuffer = ReceivePayload( pAdapter, pTunnel, pVc, pBuffer, (ULONG )(info.pData - pBuffer), info.ulDataLength, &info );
DereferenceCall( pVc ); } else { TRACE( TL_A, TM_Recv, ( "Discard: Call $%p not active", pVc ) ); WPLOG( LL_A, LM_Recv, ( "Discard: Call $%p not active", pVc ) ); } } } while (FALSE);
if (pControl) { FREE_CONTROLMSGINFO( pAdapter, pControl ); }
if (fFreeBuffer) { FreeBufferToPool( &pAdapter->poolFrameBuffers, pBuffer, TRUE ); }
if (pTunnel) { DereferenceTunnel( pTunnel ); }
if (pVc) { DereferenceVc( pVc ); } }
BOOLEAN ReceiveControl( IN ADAPTERCB* pAdapter, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CHAR* pBuffer, IN ULONG ulAvpOffset, IN ULONG ulAvpLength, IN TDIXRDGINFO* pRdg, IN L2TPHEADERINFO* pInfo, IN CONTROLMSGINFO* pControl )
// Receive processing for control packet in 'pBuffer'. The AVPs following
// the header start at 'ulAvpOffset' and are 'ulAvpLength' bytes long.
// 'PBuffer' is the receive buffer TDIX retrieved with
// 'GetBufferFromPool'. 'PAdapter' is the adapter control block.
// 'PTunnel' and 'pVc' are the tunnel and VC control blocks associated
// with the received buffer, or NULL if none. 'pAddress' is the IP
// address/port of the sending peer. 'PInfo' is the exploded header
// information. 'PControl' is the control message information, which was
// exploded earlier.
//
// Returns true if caller should free 'pBuffer', or false if this routine
// has taken ownership of the buffer and will see it's freed.
//
{ LIST_ENTRY* pLink; BOOLEAN fCallerFreesBuffer; SHORT sDiff; VCCB** ppVcs; ULONG ulcpVcs;
TDIXIPADDRESS* pAddress = &pRdg->source;
TRACE( TL_V, TM_Recv, ( "ReceiveControl" ) );
ASSERT( !(pVc && !pTunnel) );
if (ulAvpLength > 0) { if (pControl->usXError != GERR_None) { // The message was incoherent or contained "mandatory" AVPs we
// don't recognize.
//
if (pVc && pControl->usXError == GERR_BadValue) { // "Bad values", which includes unrecognized mandatories,
// terminate the call.
//
ScheduleTunnelWork( pTunnel, pVc, FsmCloseCall, (ULONG_PTR )CRESULT_GeneralWithError, (ULONG_PTR )pControl->usXError, 0, 0, FALSE, FALSE ); } else if (pTunnel) { // Any other corruption terminates the tunnel.
//
ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_GeneralWithError, (ULONG_PTR )pControl->usXError, 0, 0, FALSE, FALSE ); }
return TRUE; }
if (!pTunnel) { // IPSec should ensure this is valid source IP address.
ASSERT(pAddress->ulIpAddress != 0 && !IPADDR_IS_BROADCAST(pAddress->ulIpAddress) && !IPADDR_IS_MULTICAST(pAddress->ulIpAddress)); if (*(pControl->pusMsgType) == CMT_SCCRQ && pControl->pusAssignedTunnelId && *(pControl->pusAssignedTunnelId) != 0) { // Peer wants to start a new tunnel. Find a tunnel block with
// peer's IP address and assigned Tunnel-ID, or create, if
// necessary. The returned block is linked in the adapter's
// list and and referenced. The reference is the one for peer
// initiation, i.e. case (b).
//
// If this is a retransmit SCCRQ, this is undone after the
// sequence check below. It must be done/undone rather than
// never done because each message, including retransmits,
// must have Ns/Nr processing performed and that processing
// requires a tunnel control block.
//
pTunnel = SetupTunnel( pAdapter, pAddress->ulIpAddress, pAddress->sUdpPort, *(pControl->pusAssignedTunnelId), FALSE );
if (!pTunnel) { return TRUE; } } else { // Don't know what tunnel the message if for and it's not a
// "create new tunnel" request, so there's nothing useful to
// do. Ignore it.
//
TRACE( TL_A, TM_Recv, ( "CMT %d w/o tunnel?", *(pControl->pusMsgType) ) ); WPLOG( LL_A, LM_Recv, ( "CMT %d w/o tunnel?", *(pControl->pusMsgType) ) ); return TRUE; } }
if (*(pControl->pusMsgType) == CMT_SCCRQ || *(pControl->pusMsgType) == CMT_SCCRP) { // The source UDP port of the received message is recorded for
// SCCRQ and SCCRP only, i.e. for the first message received
// from peer.
//
pTunnel->address.sUdpPort = pAddress->sUdpPort; TRACE( TL_I, TM_Recv, ( "Peer UDP=%d", (UINT )ntohs( pAddress->sUdpPort ) ) );
pTunnel->localaddress.ulIpAddress = pRdg->dest.ulIpAddress; pTunnel->localaddress.ifindex = pRdg->dest.ifindex; pTunnel->localaddress.sUdpPort = (SHORT)( htons( L2TP_UdpPort )); pTunnel->ulFlags |= TCBF_LocalAddrSet;
TRACE( TL_I, TM_CMsg, ("L2TP-- dest %d.%d.%d.%d ifindex %d", IPADDRTRACE(pRdg->dest.ulIpAddress), pRdg->dest.ifindex)); WPLOG( LL_M, LM_CMsg, ("Received on %!IPADDR!", pRdg->dest.ulIpAddress)); } } else if (!pTunnel) { // Peer messed up and sent an ACK on tunnel ID 0, which is impossible
// according to the protocol.
//
TRACE( TL_A, TM_Recv, ( "ZACK w/o tunnel?" ) ); WPLOG( LL_A, LM_Recv, ( "ZACK w/o tunnel?" ) ); return TRUE; }
ASSERT( pTunnel );
NdisAcquireSpinLock( &pTunnel->lockT ); { // Do "acknowledged" handling on sends acknowledged by peer in the
// received packet.
//
ControlAcknowledged( pTunnel, *(pInfo->pusNr) );
if (ulAvpLength == 0) { // There are no AVPs so this was an acknowledgement only. We're
// done.
//
NdisReleaseSpinLock( &pTunnel->lockT ); return TRUE; }
fCallerFreesBuffer = TRUE; do { // Further packet processing depends on where the packet's
// sequence number falls relative to what we've already received.
//
sDiff = CompareSequence( *(pInfo->pusNs), pTunnel->usNr ); if (sDiff == 0) { // It's the expected packet. Process it, setting up the VC
// and popping from the out-of-order list as indicated. The
// 'Next Received' is incremented outside, because that step
// should not happen on a SetupVcAsynchronously restart.
//
++pTunnel->usNr; fCallerFreesBuffer = ReceiveControlExpected( pTunnel, pVc, pBuffer, pControl ); break; } else if (sDiff < 0) { // The received 'Next Sent' is before our 'Next Receive'.
// Peer may have retransmitted while our acknowledge was in
// transit, or the acknowledge may have been lost. Schedule
// another acknowledge.
//
TRACE( TL_A, TM_Recv, ( "Control re-ack" ) ); WPLOG( LL_A, LM_Recv, ( "Control re-ack" ) ); ScheduleControlAck( pTunnel, 0 );
if (*(pControl->pusMsgType) == CMT_SCCRQ) { // Since SCCRQ is a duplicate, the reference added by
// SetupTunnel above must be undone. In this special case
// the TCBF_PeerInitRef flag was never set and so need not
// be cleared.
//
DereferenceTunnel( pTunnel ); } break; } else if (sDiff < pAdapter->sMaxOutOfOrder) { CONTROLRECEIVED* pCr; BOOLEAN fDiscard;
// The packet is beyond the one we expected, but within our
// out-of-order window.
//
if (ReadFlags( &pTunnel->ulFlags ) & TCBF_Closing) { // The tunnel is closing and the out-of-order queue has
// been flushed, so just discard the packet.
//
TRACE( TL_A, TM_Recv, ( "Control discarded: ooo but closing" ) ); WPLOG( LL_A, LM_Recv, ( "Control discarded: ooo but closing" ) ); break; }
// Allocate a control-received context
// and queue the packet on the out-of-order list.
//
pCr = ALLOC_CONTROLRECEIVED( pAdapter ); if (!pCr) { break; }
// Fill in the context with the relevant packet information.
//
pCr->usNs = *(pInfo->pusNs); pCr->pVc = pVc; pCr->pBuffer = pBuffer; NdisMoveMemory( &pCr->control, pControl, sizeof(pCr->control) );
if (pCr->pVc) { // Add a VC reference covering the reference stored in the
// context, which will be removed when the context is
// freed.
//
ReferenceVc( pCr->pVc ); }
// Find the first link on the out-of-order list with an 'Ns'
// greater than that in the received message, or the head if
// none.
//
fDiscard = FALSE; for (pLink = pTunnel->listOutOfOrder.Flink; pLink != &pTunnel->listOutOfOrder; pLink = pLink->Flink) { CONTROLRECEIVED* pThisCr; SHORT sThisDiff;
pThisCr = CONTAINING_RECORD( pLink, CONTROLRECEIVED, linkOutOfOrder );
sThisDiff = CompareSequence( pCr->usNs, pThisCr->usNs );
if (sThisDiff < 0) { break; }
if (sThisDiff == 0) { // It's a retransmit that's already on our queue.
//
if (pCr->pVc) { DereferenceVc( pCr->pVc ); }
FREE_CONTROLRECEIVED( pAdapter, pCr ); fDiscard = TRUE; break; } }
if (fDiscard) { break; }
// Queue up the context as out-of-order.
//
TRACE( TL_I, TM_Recv, ( "Control %d out-of-order %d", *(pInfo->pusNs), (LONG )sDiff ) ); InsertBefore( &pCr->linkOutOfOrder, pLink ); fCallerFreesBuffer = FALSE; break; } DBG_else { TRACE( TL_A, TM_Recv, ( "Control discarded: Beyond ooo" ) ); } } while (FALSE);
// Complete any VCs listed as completing.
//
CompleteVcs( pTunnel ); } NdisReleaseSpinLock( &pTunnel->lockT );
return fCallerFreesBuffer; }
BOOLEAN ReceiveControlExpected( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CHAR* pBuffer, IN CONTROLMSGINFO* pControl )
// Called to do packet processing when the packet received is the expected
// 'Next Receive' packet. 'PBuffer' is the receive buffer. 'PTunnel' is
// the valid tunnel control block. 'PVc' is the call's VC control block
// and may be NULL, if the VC for the call has not yet been set up.
// 'PControl' is the expoded control message information.
//
// Returns true if the buffer should be freed by caller, false if it was
// queued for further processing.
//
// IMPORTANT: Caller must hold the 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter; BOOLEAN fProcessed; SHORT sDiff;
pAdapter = pTunnel->pAdapter;
// Schedule an acknowledge-only packet to be sent if no outgoing traffic
// appears to piggyback on within a reasonable time. Note this occurs
// even if the asynchronous VC set up was invoked. Ns/Nr processing must
// occur before any data processing that may cause delays.
//
ScheduleControlAck( pTunnel, *(pControl->pusMsgType) );
// Pass the packet to the control FSMs.
//
fProcessed = FsmReceive( pTunnel, pVc, pBuffer, pControl ); if (fProcessed) { // The VC is setup and the packet has been processed. See if any
// packets on the received out-of-order queue can now be processed.
//
for (;;) { LIST_ENTRY* pFirstLink; CONTROLRECEIVED* pFirstCr; BOOLEAN fOutOfOrderProcessed;
pFirstLink = pTunnel->listOutOfOrder.Flink; if (pFirstLink == &pTunnel->listOutOfOrder) { break; }
pFirstCr = CONTAINING_RECORD( pFirstLink, CONTROLRECEIVED, linkOutOfOrder );
sDiff = CompareSequence( pFirstCr->usNs, pTunnel->usNr ); if (sDiff == 0) { // Yes, it's the next expected packet. Update 'Next Receive'
// and pass the packet to the control FSMs.
//
TRACE( TL_I, TM_Recv, ( "Control %d from queue", (UINT )pFirstCr->usNs ) ); RemoveEntryList( pFirstLink ); InitializeListHead( pFirstLink );
++pTunnel->usNr; fOutOfOrderProcessed = FsmReceive( pTunnel, pFirstCr->pVc, pFirstCr->pBuffer, &pFirstCr->control );
ScheduleControlAck( pTunnel, *(pFirstCr->control.pusMsgType) );
if (fOutOfOrderProcessed) { FreeBufferToPool( &pAdapter->poolFrameBuffers, pFirstCr->pBuffer, TRUE ); }
if (pFirstCr->pVc) { DereferenceVc( pFirstCr->pVc ); }
FREE_CONTROLRECEIVED( pAdapter, pFirstCr ); } else if (sDiff > 0) { // No, there's still some missing.
//
TRACE( TL_I, TM_Recv, ( "Control %d still missing", pTunnel->usNr ) ); break; } else { ASSERT( "Old control queued?" ); break; } } }
return fProcessed; }
BOOLEAN ReceivePayload( IN ADAPTERCB* pAdapter, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CHAR* pBuffer, IN ULONG ulPayloadOffset, IN ULONG ulPayloadLength, IN L2TPHEADERINFO* pInfo )
// Receive processing for payload in 'pBuffer' of 'ulPayloadLength' bytes
// starting at offset 'ulPayloadOffset'. 'PBuffer' is the receive buffer
// TDIX retrieved with 'GetBufferFromPool'. 'PAdapter, 'pTunnel' and
// 'PVc' are the adapter, tunnel, and VC control blocks associated with
// the received buffer. 'PInfo' is the exploded header information.
//
// Returns true if caller should free 'pBuffer', or false if this routine
// has taken ownership of the buffer and will see it's freed.
//
{ LONGLONG llTimeReceived; BOOLEAN fCallerFreesBuffer;
TRACE( TL_V, TM_Recv, ( "ReceivePayload" ) );
if (!pTunnel || !pVc) { // Both control blocks are always required to receive payload.
//
TRACE( TL_A, TM_Recv, ( "Discard: No CB" ) ); WPLOG( LL_A, LM_Recv, ( "Discard: No CB" ) ); return TRUE; }
// Note the time if client's call parameters indicated interest in time
// received.
//
if (ReadFlags( &pVc->ulFlags ) & VCBF_IndicateTimeReceived) { NdisGetCurrentSystemTime( (LARGE_INTEGER* )&llTimeReceived ); } else { llTimeReceived = 0; }
if (!(ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing) || !pInfo->pusNr) { DBG_if (ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing) TRACE( TL_A, TM_Recv, ( "No Nr field?" ) );
if (ulPayloadLength > 0) { // Flow control was disabled during negotiation. This should be
// extremely rare, since a compliant peer MUST implement flow
// control.
//
IndicateReceived( pVc, pBuffer, ulPayloadOffset, ulPayloadLength, llTimeReceived ); return FALSE; } else { NdisAcquireSpinLock( &pVc->lockV ); { ++pVc->stats.ulRecdZlbs; } NdisReleaseSpinLock( &pVc->lockV ); return TRUE; } }
fCallerFreesBuffer = TRUE; NdisAcquireSpinLock( &pVc->lockV ); do { SHORT sDiff;
// All R-bit handling occurs first. Peer sends a packet with the
// R-bit set to indicate that all packets expected between the last
// packet and this packet should be assumed lost.
//
if (*(pInfo->pusBits) & HBM_R) { ++pVc->stats.ulRecdResets;
sDiff = CompareSequence( *(pInfo->pusNs), pVc->usNr ); if (sDiff > 0) { TRACE( TL_I, TM_Recv, ( "Reset Nr=%d from %d", (LONG )*(pInfo->pusNs), (LONG )pVc->usNr ) );
pVc->usNr = *(pInfo->pusNs); } else { ++pVc->stats.ulRecdResetsIgnored;
TRACE( TL_I, TM_Recv, ( "Reset Nr=%d from %d ignored", (LONG )*(pInfo->pusNs), (LONG )pVc->usNr ) ); } }
// Do "acknowledged" handling on sends acknowledged by peer in the
// received packet.
//
PayloadAcknowledged( pTunnel, pVc, *(pInfo->pusNr) );
// If there's no payload and the R-bit is not set, this was an
// acknowledgement only and we're done.
//
if (ulPayloadLength == 0) { ++pVc->stats.ulRecdZlbs;
if (*(pInfo->pusBits) & HBM_R) { BOOLEAN fReceivedFromOutOfOrder;
// Indicate up any packet on the out-of-order list made
// receivable by the R-bit reset.
//
fReceivedFromOutOfOrder = FALSE; while (ReceiveFromOutOfOrder( pVc )) { fReceivedFromOutOfOrder = TRUE; }
if (fReceivedFromOutOfOrder) { // Schedule an acknowledge-only packet to be sent if no
// outgoing traffic appears to piggyback on within a
// reasonable time.
//
SchedulePayloadAck( pTunnel, pVc ); } }
break; }
DBG_if (pInfo->pusNs && pInfo->pusNr) { TRACE( TL_N, TM_Recv, ( "len=%d Ns=%d Nr=%d", (ULONG )*(pInfo->pusLength), (ULONG )*(pInfo->pusNs), (ULONG )*(pInfo->pusNr) ) ); }
// Further packet processing depends on where the packet's sequence
// number falls relative to what we've already received.
//
sDiff = CompareSequence( *(pInfo->pusNs), pVc->usNr ); if (sDiff == 0) { // It's the next expected packet. Update 'Next Receive' and
// indicate the payload received to the driver above.
//
pVc->usNr = *(pInfo->pusNs) + 1;
NdisReleaseSpinLock( &pVc->lockV ); { IndicateReceived( pVc, pBuffer, ulPayloadOffset, ulPayloadLength, llTimeReceived ); } NdisAcquireSpinLock( &pVc->lockV );
// Indicate up any packets on the out-of-order list that were
// waiting for this one.
//
while (ReceiveFromOutOfOrder( pVc )) ;
// Schedule an acknowledge-only packet to be sent if no outgoing
// traffic appears to piggyback on within a reasonable time.
//
SchedulePayloadAck( pTunnel, pVc ); } else if (sDiff < 0) { // The received 'Next Sent' is before our 'Next Receive'. Maybe
// an out-of-order packet we didn't wait for long enough. It's
// useless at this point.
//
TRACE( TL_A, TM_Recv, ( "Payload discarded: Old Ns" ) ); WPLOG( LL_A, LM_Recv, ( "Payload discarded: Old Ns" ) ); break; } else if (sDiff < pAdapter->sMaxOutOfOrder) { LIST_ENTRY* pLink; PAYLOADRECEIVED* pPr; BOOLEAN fDiscard;
TRACE( TL_I, TM_Recv, ( "%d out-of-order %d", *(pInfo->pusNs), (LONG )sDiff ) );
// The packet is beyond the one we expected, but within our
// out-of-order window. Allocate a payload-received context and
// queue it up on the out-of-order list.
//
pPr = ALLOC_PAYLOADRECEIVED( pAdapter ); if (!pPr) { TRACE( TL_A, TM_Recv, ( "Alloc PR?" ) ); WPLOG( LL_A, LM_Recv, ( "Failed to allocate PAYLOADRECEIVED" ) ); break; }
// Fill in the context with the relevant packet information.
//
pPr->usNs = *(pInfo->pusNs); pPr->pBuffer = pBuffer; pPr->ulPayloadOffset = ulPayloadOffset; pPr->ulPayloadLength = ulPayloadLength; pPr->llTimeReceived = llTimeReceived;
// Queue up the context on the out-of-order list, keeping the list
// correctly sorted by 'Ns'.
//
fDiscard = FALSE; for (pLink = pVc->listOutOfOrder.Flink; pLink != &pVc->listOutOfOrder; pLink = pLink->Flink) { PAYLOADRECEIVED* pThisPr; SHORT sThisDiff;
pThisPr = CONTAINING_RECORD( pLink, PAYLOADRECEIVED, linkOutOfOrder );
sThisDiff = CompareSequence( pPr->usNs, pThisPr->usNs );
if (sThisDiff < 0) { break; }
if (sThisDiff == 0) { // This shouldn't happen because payloads are not
// retransmitted, but do the right thing just in case.
//
TRACE( TL_A, TM_Recv, ( "Payload on ooo queue?" ) ); fDiscard = TRUE; break; } }
if (fDiscard) { FREE_PAYLOADRECEIVED( pAdapter, pPr ); break; }
InsertBefore( &pPr->linkOutOfOrder, pLink ); } else { // The packet is beyond the one we expected and outside our
// out-of-order window. Discard it.
//
TRACE( TL_A, TM_Recv, ( "Out-of-order %d too far" , (LONG )sDiff ) ); WPLOG( LL_A, LM_Recv, ( "Out-of-order %d too far" , (LONG )sDiff ) );
break; }
fCallerFreesBuffer = FALSE; } while (FALSE); NdisReleaseSpinLock( &pVc->lockV );
return fCallerFreesBuffer; }
//-----------------------------------------------------------------------------
// Receive utility routines (alphabetically)
//-----------------------------------------------------------------------------
SHORT CompareSequence( USHORT us1, USHORT us2 )
// Returns the "logical" difference between sequence numbers 'us1' and
// 'us2' accounting for the possibility of rollover.
//
{ USHORT usDiff = us1 - us2;
if (usDiff == 0) return 0;
if (usDiff < 0x4000) return (SHORT )usDiff;
return -((SHORT )(0 - usDiff)); }
VOID ControlAcknowledged( IN TUNNELCB* pTunnel, IN USHORT usReceivedNr )
// Dequeues and cancels the timer of all control-sent contexts in the
// tunnel's 'listSendsOut' queue with 'Next Sent' less than
// 'usReceivedNr'.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter; BOOLEAN fFoundOne;
pAdapter = pTunnel->pAdapter; fFoundOne = FALSE;
while (!IsListEmpty( &pTunnel->listSendsOut )) { CONTROLSENT* pCs; LIST_ENTRY* pLink;
pLink = pTunnel->listSendsOut.Flink; pCs = CONTAINING_RECORD( pLink, CONTROLSENT, linkSendsOut );
// The list is in 'Ns' order so as soon as a non-acknowledge is hit
// we're done.
//
if (CompareSequence( pCs->usNs, usReceivedNr ) >= 0) { break; }
fFoundOne = TRUE;
// Remove the context from the "outstanding send" list and cancel the
// associated timer. Doesn't matter if the cancel fails because the
// expire handler will recognize that the context is not linked into
// the "out" list and do nothing.
//
RemoveEntryList( pLink ); InitializeListHead( pLink ); TimerQCancelItem( pTunnel->pTimerQ, pCs->pTqiSendTimeout );
// Per the draft/RFC, adjustments to the send window and send timeouts
// are necessary. Per Karn's Algorithm, if the packet was
// retransmitted it is useless for timeout adjustment because it's not
// known if peer responded to the original send or the retransmission.
//
if (pCs->ulRetransmits == 0) { AdjustTimeoutsAtAckReceived( pCs->llTimeSent, pAdapter->ulMaxSendTimeoutMs, &pTunnel->ulSendTimeoutMs, &pTunnel->ulRoundTripMs, &pTunnel->lDeviationMs ); }
// See if it's time to open the send window a bit further.
//
AdjustSendWindowAtAckReceived( pTunnel->ulMaxSendWindow, &pTunnel->ulAcksSinceSendTimeout, &pTunnel->ulSendWindow );
TRACE( TL_N, TM_Send, ( "T%d: ACK(%d) new rtt=%d dev=%d ato=%d sw=%d", (ULONG )pTunnel->usTunnelId, (ULONG )pCs->usNs, pTunnel->ulRoundTripMs, pTunnel->lDeviationMs, pTunnel->ulSendTimeoutMs, pTunnel->ulSendWindow ) );
// Execute any "on ACK" options and note that delayed action
// processing is now required.
//
if (pCs->ulFlags & CSF_TunnelIdleOnAck) { TRACE( TL_N, TM_Send, ( "Tunnel idle on ACK" ) ); ScheduleTunnelWork( pTunnel, NULL, CloseTunnel, 0, 0, 0, 0, FALSE, FALSE ); } else if (pCs->ulFlags & CSF_CallIdleOnAck) { TRACE( TL_N, TM_Send, ( "Call idle on ACK" ) ); ASSERT( pCs->pVc ); ScheduleTunnelWork( pTunnel, pCs->pVc, CloseCall, 0, 0, 0, 0, FALSE, FALSE ); }
if (pCs->ulFlags & CSF_Pending) { // The context is queued for retransmission, so de-queue it. In
// this state the context has already been assumed "not
// outstanding" so no need to adjust the counter as below.
//
pCs->ulFlags &= ~(CSF_Pending); } else { // The context is not queued for retranmission, so adjust the
// counter to indicate it is no longer outstanding.
//
--pTunnel->ulSendsOut; }
// Remove the reference corresponding to linkage in the "outstanding
// send" list.
//
DereferenceControlSent( pCs ); }
if (fFoundOne) { // See if any sends were pending on a closed send window.
//
ScheduleTunnelWork( pTunnel, NULL, SendPending, 0, 0, 0, 0, FALSE, FALSE ); } }
VOID ControlAckTimerEvent( IN TIMERQITEM* pItem, IN VOID* pContext, IN TIMERQEVENT event )
// PTIMERQEVENT handler that fires when it's time to stop waiting for an
// outgoing control packet on which to piggyback an acknowledge.
//
{ TUNNELCB* pTunnel; ADAPTERCB* pAdapter; BOOLEAN fSendAck;
TRACE( TL_N, TM_Recv, ( "ControlAckTimerEvent(%s)", TimerQPszFromEvent( event ) ) );
// Unpack context information.
//
pTunnel = (TUNNELCB* )pContext; pAdapter = pTunnel->pAdapter;
if (event == TE_Expire) { NdisAcquireSpinLock( &pTunnel->lockT ); { if (pItem == pTunnel->pTqiDelayedAck) { pTunnel->pTqiDelayedAck = NULL; fSendAck = TRUE; } else { fSendAck = FALSE; } } NdisReleaseSpinLock( &pTunnel->lockT );
if (fSendAck) { // The timer expired and was not been cancelled or terminated
// while the expire processing was being set up, meaning it's time
// to send a zero-AVP control packet to give peer the acknowledge
// we were hoping to piggyback onto a random outgoing control
// packet.
//
ScheduleTunnelWork( pTunnel, NULL, SendControlAck, 0, 0, 0, 0, FALSE, FALSE ); } DBG_else { TRACE( TL_I, TM_Send, ( "CAck aborted" ) ); } }
// Free the timer event descriptor and remove the reference covering the
// scheduled timer.
//
FREE_TIMERQITEM( pAdapter, pItem ); DereferenceTunnel( pTunnel ); }
VOID PayloadAckTimerEvent( IN TIMERQITEM* pItem, IN VOID* pContext, IN TIMERQEVENT event )
// PTIMERQEVENT handler that fires when it's time to stop waiting for an
// outgoing payload packet on which to piggyback an acknowledge.
//
{ VCCB* pVc; ADAPTERCB* pAdapter; BOOLEAN fSendAck;
TRACE( TL_N, TM_Recv, ( "PayloadAckTimerEvent(%s)=$%p", TimerQPszFromEvent( event ), pItem ) );
// Unpack context information.
//
pVc = (VCCB* )pContext; pAdapter = pVc->pAdapter;
if (event == TE_Expire) { if (ReferenceCall( pVc )) { NdisAcquireSpinLock( &pVc->lockV ); { if (pItem == pVc->pTqiDelayedAck) { fSendAck = TRUE; pVc->pTqiDelayedAck = NULL; ++pVc->stats.ulSentZAcks; } else { fSendAck = FALSE; } } NdisReleaseSpinLock( &pVc->lockV );
if (fSendAck) { // The timer expired and was not been cancelled or terminated
// while the expire processing was being set up, plus the call
// is still up, meaning it's time to send a zero-AVP control
// packet to give peer the acknowledge we were hoping to
// piggyback onto a random outgoing payload packet.
//
ScheduleTunnelWork( pVc->pTunnel, pVc, SendPayloadAck, 0, 0, 0, 0, FALSE, FALSE ); } else { TRACE( TL_I, TM_Send, ( "PAck aborted" ) ); DereferenceCall( pVc ); } } else { NdisAcquireSpinLock( &pVc->lockV ); { if (pItem == pVc->pTqiDelayedAck) { pVc->pTqiDelayedAck = NULL; } } NdisReleaseSpinLock( &pVc->lockV ); } }
// Free the timer event descriptor and remove the reference covering the
// scheduled timer.
//
FREE_TIMERQITEM( pAdapter, pItem ); DereferenceVc( pVc ); }
USHORT ExplodeAvpHeader( IN CHAR* pAvp, IN USHORT usMaxAvpLength, OUT AVPINFO* pInfo )
// Fills caller's '*pInfo' with the addresses of the various fields in the
// AVP header at 'pAvp'. The byte order of the fields in 'pAvpHeader',
// with the exception of the Value field, are flipped to host-byte-order
// in place. The length and value length are extracted. 'UsMaxAvpLength'
// is the maximum size of the AVP in bytes.
//
// Returns GERR_None if 'pAvpHeader' is a coherent AVP header, or a
// GERR_* failure code.
//
{ UNALIGNED USHORT* pusCur; USHORT usBits;
if (usMaxAvpLength < L2TP_AvpHeaderSize) { TRACE( TL_A, TM_Recv, ( "Avp: Short buffer?" ) ); WPLOG( LL_A, LM_Recv, ( "Avp: Short buffer?" ) ); return GERR_BadLength; }
pusCur = (UNALIGNED USHORT* )pAvp;
// The first 2 bytes contain bits that indicate the presence/absence of
// the other header fields.
//
*pusCur = ntohs( *pusCur ); pInfo->pusBits = pusCur; usBits = *pusCur; ++pusCur;
// As of draft-09, AVPs with reserved bits not set to zero MUST be treated
// as unrecognized.
//
if ((usBits & ABM_Reserved) != 0) { return GERR_BadValue; }
// Extract the Overall Length sub-field and verify that it says the AVP is
// at least as long as the fixed portion of the header.
//
pInfo->usOverallLength = (usBits & ABM_OverallLength); if (pInfo->usOverallLength > usMaxAvpLength || pInfo->usOverallLength < L2TP_AvpHeaderSize) { TRACE( TL_A, TM_Recv, ( "Avp: Bad length?" ) ); WPLOG( LL_A, LM_Recv, ( "Avp: Bad length?" ) ); return GERR_BadLength; }
// Vendor-ID field.
//
*pusCur = ntohs( *pusCur ); pInfo->pusVendorId = pusCur; ++pusCur;
// Attribute field.
//
*pusCur = ntohs( *pusCur ); pInfo->pusAttribute = pusCur; ++pusCur;
// Value field.
//
pInfo->pValue = (CHAR* )pusCur; pInfo->usValueLength = pInfo->usOverallLength - L2TP_AvpHeaderSize;
return GERR_None; }
#define MAX_NAME_LENGTH 64
VOID ExplodeControlAvps( IN CHAR* pFirstAvp, IN CHAR* pEndOfBuffer, OUT CONTROLMSGINFO* pControl )
// Fills caller's '*pControl' buffer with the exploded interpretation of
// the message with AVP list starting at 'pFirstAvp'. 'PEndOfBuffer'
// points to the first byte beyond the end of the received buffer. The
// AVP values are returned as addresses of the corresponding value field
// in the AVPs. Fields not present are returned as NULL. The byte order
// of the fields in 'pControl' is flipped to host-byte-order in place.
// The values themselves are not validated, only the message format. Sets
// 'pControl->usXError' to GERR_None if successful, or the GERR_* failure
// code.
//
{ USHORT usXError; AVPINFO avp; CHAR* pCur; CHAR szName[MAX_NAME_LENGTH]; int NameLength;
DUMPW( TL_A, TM_MDmp, pFirstAvp, (ULONG )(pEndOfBuffer - pFirstAvp) );
NdisZeroMemory( pControl, sizeof(*pControl) ); pCur = pFirstAvp;
// Read and validate the Message Type AVP, which is the first AVP of all
// control messages.
//
usXError = ExplodeAvpHeader( pCur, (USHORT )(pEndOfBuffer - pCur), &avp ); if (usXError != GERR_None) { TRACE( TL_A, TM_CMsg, ( "Bad AVP header" ) ); WPLOG( LL_A, LM_CMsg, ( "Bad AVP header" ) ); pControl->usXError = usXError; return; }
if (*(avp.pusAttribute) != ATTR_MsgType || *(avp.pusVendorId) != 0 || (*(avp.pusBits) & ABM_H)) { TRACE( TL_A, TM_CMsg, ( "Bad MsgType AVP" ) ); WPLOG( LL_A, LM_CMsg, ( "Bad MsgType AVP" ) ); pControl->usXError = GERR_BadValue; return; }
usXError = GetAvpValueUs( &avp, &pControl->pusMsgType ); if (usXError != GERR_None) { TRACE( TL_A, TM_CMsg, ( "Bad MsgType Us" ) ); WPLOG( LL_A, LM_CMsg, ( "Bad MsgType Us" ) ); pControl->usXError = usXError; return; }
pCur += avp.usOverallLength;
TRACE( TL_I, TM_CMsg, ( "*MsgType=%s", MsgTypePszFromUs( *(pControl->pusMsgType) ) ) ); WPLOG( LL_M, LM_CMsg, ( "*MsgType=%s", MsgTypePszFromUs( *(pControl->pusMsgType) ) ) );
// Make sure the message type code is valid, and if it is, explode any
// additional AVPs in the message.
//
switch (*(pControl->pusMsgType)) { case CMT_SCCRQ: case CMT_SCCRP: case CMT_SCCCN: case CMT_StopCCN: case CMT_Hello: { // Mark the messages above as tunnel control rather than call
// control.
//
pControl->fTunnelMsg = TRUE;
// ...fall thru...
}
case CMT_OCRQ: case CMT_OCRP: case CMT_OCCN: case CMT_ICRQ: case CMT_ICRP: case CMT_ICCN: case CMT_CDN: case CMT_WEN: case CMT_SLI: { // Walk the list of AVPs, exploding each AVP in turn. Excepting
// the Message Type, the order of the AVPs is not defined.
//
for ( ; pCur < pEndOfBuffer; pCur += avp.usOverallLength ) { usXError = ExplodeAvpHeader( pCur, (USHORT )(pEndOfBuffer - pCur), &avp ); if (usXError != GERR_None) { break; }
if (*avp.pusVendorId != 0) { // The AVP has a non-IETF vendor ID, and we don't
// recognize any. If the AVP is optional, just ignore it.
// If it's mandatory, then fail.
//
if (*avp.pusBits & ABM_M) { TRACE( TL_A, TM_CMsg, ( "Non-0 Vendor ID %d, M-bit is set", *avp.pusVendorId ) ); WPLOG( LL_A, LM_CMsg, ( "Non-0 Vendor ID %d, M-bit is set", *avp.pusVendorId ) ); usXError = GERR_BadValue; break; } continue; }
if (*avp.pusBits & ABM_H) { BOOLEAN fIgnore;
TRACE( TL_A, TM_CMsg, ( "Hidden bit on AVP %d", (LONG )(*avp.pusAttribute) ) );
// !!! Remove this when H-bit support is added.
//
switch (*avp.pusAttribute) { case ATTR_ProxyAuthName: case ATTR_ProxyAuthChallenge: case ATTR_ProxyAuthId: case ATTR_ProxyAuthResponse: case ATTR_DialedNumber: case ATTR_DialingNumber: case ATTR_SubAddress: case ATTR_InitialLcpConfig: case ATTR_LastSLcpConfig: case ATTR_LastRLcpConfig: case ATTR_Accm: case ATTR_PrivateGroupId: { fIgnore = TRUE; break; }
default: { fIgnore = FALSE; break; } }
if (fIgnore) { TRACE( TL_A, TM_CMsg, ( "Hidden AVP ignored" ) ); break; }
// The AVP has the "hidden" bit set meaning the value is
// hashed with MD5. This requires a shared secret from
// the tunnel authentication, which we don't do. If the
// AVP is optional, just ignore it. If it's mandatory,
// fail.
//
if (*avp.pusBits & ABM_M) { usXError = GERR_BadValue; break; } continue; }
switch (*avp.pusAttribute) { case ATTR_Result: { usXError = GetAvpValue2UsAndVariableAch( &avp, &pControl->pusResult, &pControl->pusError, &pControl->pchResultMsg, &pControl->usResultMsgLength );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*Result=%d", (ULONG )(*(pControl->pusResult)))); } WPLOG( LL_M, LM_CMsg, ( "*Result=%d", (usXError == GERR_None) ? (ULONG )(*(pControl->pusResult)) : -1 ));
break; }
case ATTR_HostName: { usXError = GetAvpValueVariableAch( &avp, &pControl->pchHostName, &pControl->usHostNameLength );
if(usXError == GERR_None) { NameLength = (pControl->usHostNameLength < MAX_NAME_LENGTH) ? pControl->usHostNameLength : MAX_NAME_LENGTH - 1; NdisMoveMemory(szName, pControl->pchHostName, NameLength); szName[NameLength] = '\0'; TRACE( TL_A, TM_CMsg, ( "*HostName=%s", szName)); WPLOG( LL_M, LM_CMsg, ( "*HostName=%s", szName)); } else { WPLOG( LL_M, LM_CMsg, ( "*HostName is bad")); } break; } case ATTR_VendorName: { PCHAR pchVendorName; USHORT usVendorNameLength; usXError = GetAvpValueVariableAch( &avp, &pchVendorName, &usVendorNameLength);
if(usXError == GERR_None) { NameLength = (usVendorNameLength < MAX_NAME_LENGTH) ? usVendorNameLength : MAX_NAME_LENGTH - 1; NdisMoveMemory(szName, pchVendorName, NameLength); szName[NameLength] = '\0'; TRACE( TL_A, TM_CMsg, ( "*VendorName=%s", szName)); WPLOG( LL_M, LM_CMsg, ( "*VendorName=%s", szName)); } else { WPLOG( LL_M, LM_CMsg, ( "*VendorName is bad")); } break; } case ATTR_FirmwareRevision: { usXError = GetAvpValueUs( &avp, &pControl->pusFirmwareRevision );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*FirmwareVer=$%04x", (ULONG )(*(pControl->pusFirmwareRevision)) ) ); } WPLOG( LL_M, LM_CMsg, ( "*FirmwareVer=%04x", (usXError == GERR_None) ? (ULONG )(*(pControl->pusFirmwareRevision)) : 0xBAD ));
break; } case ATTR_ProtocolVersion: { usXError = GetAvpValueUs( &avp, &pControl->pusProtocolVersion );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*ProtVer=$%04x", (ULONG )(*(pControl->pusProtocolVersion)) ) ); } WPLOG( LL_M, LM_CMsg, ( "*ProtVer=%04x", (usXError == GERR_None) ? (ULONG )(*(pControl->pusProtocolVersion)) : 0xBAD ));
break; }
case ATTR_FramingCaps: { usXError = GetAvpValueUl( &avp, &pControl->pulFramingCaps );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*FramingCaps=$%08x", *(pControl->pulFramingCaps) ) ); } WPLOG( LL_M, LM_CMsg, ( "*FramingCaps=%08x", (usXError == GERR_None) ? *(pControl->pulFramingCaps) : (ULONG) 0xBAD )); break; }
case ATTR_BearerCaps: { usXError = GetAvpValueUl( &avp, &pControl->pulBearerCaps );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*BearerCaps=$%08x", *(pControl->pulBearerCaps) ) ); } WPLOG( LL_M, LM_CMsg, ( "*BearerCaps=%08x", (usXError == GERR_None) ? *(pControl->pulBearerCaps) : (ULONG) 0xBAD ));
break; }
case ATTR_TieBreaker: { usXError = GetAvpValueFixedAch( &avp, 8, &pControl->pchTieBreaker );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*Tiebreaker" ) ); } WPLOG( LL_M, LM_CMsg, ( "*Tiebreaker %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_AssignedTunnelId: { usXError = GetAvpValueUs( &avp, &pControl->pusAssignedTunnelId );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*AssignTid=%d", (ULONG )(*(pControl->pusAssignedTunnelId)) ) ); } WPLOG( LL_M, LM_CMsg, ( "*AssignTid=%d", (usXError == GERR_None) ? (ULONG )(*(pControl->pusAssignedTunnelId)) : -1 ));
break; }
case ATTR_RWindowSize: { usXError = GetAvpValueUs( &avp, &pControl->pusRWindowSize );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*RWindow=%d", (ULONG )(*(pControl->pusRWindowSize)) ) ); } WPLOG( LL_M, LM_CMsg, ( "*RWindow=%d", (usXError == GERR_None) ? (ULONG )(*(pControl->pusRWindowSize)) : -1 ));
break; }
case ATTR_AssignedCallId: { usXError = GetAvpValueUs( &avp, &pControl->pusAssignedCallId );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*AssignCid=%d", (ULONG )(*(pControl->pusAssignedCallId)) ) ); } WPLOG( LL_M, LM_CMsg, ( "*AssignCid=%d", (usXError == GERR_None) ? (ULONG )(*(pControl->pusAssignedCallId)) : -1 ));
break; }
case ATTR_CallSerialNumber: { usXError = GetAvpValueUl( &avp, &pControl->pulCallSerialNumber );
if (usXError == GERR_BadLength) { // Be tolerant here because the meaning in the
// draft has changed a few times.
//
TRACE( TL_A, TM_CMsg, ( "Weird CallSerial# length ignored" ) ); usXError = GERR_None; }
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*CallSerial#" ) ); } WPLOG( LL_M, LM_CMsg, ( "*CallSerial# %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_MinimumBps: { usXError = GetAvpValueUl( &avp, &pControl->pulMinimumBps );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*MinBps=%d", *(pControl->pulMinimumBps) ) ); } WPLOG( LL_M, LM_CMsg, ( "*MinBps=%d", (usXError == GERR_None) ? *(pControl->pulMinimumBps) : -1 ));
break; }
case ATTR_MaximumBps: { usXError = GetAvpValueUl( &avp, &pControl->pulMaximumBps );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*MaxBps=%d", *(pControl->pulMaximumBps) ) ); } WPLOG( LL_M, LM_CMsg, ( "*MaxBps=%d", (usXError == GERR_None) ? *(pControl->pulMaximumBps) : -1 ));
break; }
case ATTR_BearerType: { usXError = GetAvpValueUl( &avp, &pControl->pulBearerType );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*BearerType=$%08x", *(pControl->pulBearerType) ) ); } WPLOG( LL_M, LM_CMsg, ( "*BearerType=%08x", (usXError == GERR_None) ? *(pControl->pulBearerType) : (ULONG)'BAD' ));
break; }
case ATTR_FramingType: { usXError = GetAvpValueUl( &avp, &pControl->pulFramingType );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*FramingType=$%08x", *(pControl->pulFramingType) ) ); }
WPLOG( LL_M, LM_CMsg, ( "*FramingType=$%08x", (usXError == GERR_None) ? *(pControl->pulFramingType) : (ULONG)'BAD' )); break; }
case ATTR_PacketProcDelay: { usXError = GetAvpValueUs( &avp, &pControl->pusPacketProcDelay );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*PPD=%d", (ULONG )(*(pControl->pusPacketProcDelay)) ) ); } WPLOG( LL_M, LM_CMsg, ( "*PPD=%d", (usXError == GERR_None) ? (ULONG )(*(pControl->pusPacketProcDelay)) : -1 ));
break; }
case ATTR_DialedNumber: { usXError = GetAvpValueVariableAch( &avp, &pControl->pchDialedNumber, &pControl->usDialedNumberLength );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*Dialed#" ) ); } WPLOG( LL_M, LM_CMsg, ( "*Dialed# %s", (usXError == GERR_None) ? "*" : "bad" )); break; }
case ATTR_DialingNumber: { usXError = GetAvpValueVariableAch( &avp, &pControl->pchDialingNumber, &pControl->usDialingNumberLength );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*Dialing#" ) ); } WPLOG( LL_M, LM_CMsg, ( "*Dialing# %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_SubAddress: { usXError = GetAvpValueVariableAch( &avp, &pControl->pchSubAddress, &pControl->usSubAddressLength );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*SubAddr" ) ); } WPLOG( LL_M, LM_CMsg, ( "*SubAddr %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_TxConnectSpeed: { usXError = GetAvpValueUl( &avp, &pControl->pulTxConnectSpeed );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*TxSpeed=%d", *(pControl->pulTxConnectSpeed) ) ); } WPLOG( LL_M, LM_CMsg, ( "*TxSpeed=%d", (usXError == GERR_None) ? *(pControl->pulTxConnectSpeed) : -1 ));
break; }
case ATTR_PhysicalChannelId: { usXError = GetAvpValueUl( &avp, &pControl->pulPhysicalChannelId );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*PhysChannelId=$%08x", *(pControl->pulPhysicalChannelId) ) ); } WPLOG( LL_M, LM_CMsg, ( "*PhysChannelId=%08x", (usXError == GERR_None) ? *(pControl->pulPhysicalChannelId) : (ULONG)'BAD' ));
break; }
case ATTR_Challenge: { usXError = GetAvpValueVariableAch( &avp, &pControl->pchChallenge, &pControl->usChallengeLength );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*Challenge" ) ); } WPLOG( LL_M, LM_CMsg, ( "*Challenge %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_ChallengeResponse: { usXError = GetAvpValueFixedAch( &avp, 16, &pControl->pchResponse );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*ChallengeResp" ) ); } WPLOG( LL_M, LM_CMsg, ( "*ChallengeResp %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_ProxyAuthType: { usXError = GetAvpValueUs( &avp, &pControl->pusProxyAuthType );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*ProxyAuthType=%d", (ULONG )(*(pControl->pusProxyAuthType)) ) ); }
WPLOG( LL_M, LM_CMsg, ( "*ProxyAuthType=%d", (usXError == GERR_None) ? (ULONG )(*(pControl->pusProxyAuthType)) : -1 )); break; }
case ATTR_ProxyAuthResponse: { usXError = GetAvpValueVariableAch( &avp, &pControl->pchProxyAuthResponse, &pControl->usProxyAuthResponseLength );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*ProxyAuthResponse" ) ); } WPLOG( LL_M, LM_CMsg, ( "*ProxyAuthResponse %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_CallErrors: { usXError = GetAvpValueFixedAul( &avp, 6, &pControl->pulCallErrors );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*CallErrors" ) ); } WPLOG( LL_M, LM_CMsg, ( "*CallErrors %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_Accm: { usXError = GetAvpValueFixedAul( &avp, 2, &pControl->pulAccm );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*Accm" ) ); } WPLOG( LL_M, LM_CMsg, ( "*Accm %s", (usXError == GERR_None) ? "*" : "bad" ));
break; }
case ATTR_SequencingRequired: { usXError = GetAvpValueFlag( &avp, &pControl->fSequencingRequired );
DBG_if (usXError == GERR_None) { TRACE( TL_A, TM_CMsg, ( "*SeqReqd" ) ); } WPLOG( LL_M, LM_CMsg, ( "*SeqReqd %s", (usXError == GERR_None) ? "*" : "bad" )); break; }
default: { // The AVP is not one we handle. If optional, just
// ignore it, but if mandatory, fail.
//
TRACE( TL_A, TM_CMsg, ( "*AVP %d ignored", (ULONG )*avp.pusAttribute ) ); WPLOG( LL_A, LM_CMsg, ( "*AVP %d ignored", (ULONG )*avp.pusAttribute ) ); if (*avp.pusBits & ABM_M) { if (*avp.pusAttribute <= ATTR_MAX) { // This is a bug in the peer, but ignoring it
// is the best action.
//
TRACE( TL_A, TM_CMsg, ( "Known AVP %d marked mandatory ignored", (LONG )(*avp.pusAttribute) ) ); WPLOG( LL_A, LM_CMsg, ( "Known AVP %d marked mandatory ignored", (LONG )(*avp.pusAttribute) ) ); } else { usXError = GERR_BadValue; } } break; } }
if (usXError != GERR_None) { break; } }
ASSERT( pCur <= pEndOfBuffer ); break; }
default: { TRACE( TL_A, TM_CMsg, ( "Unknown CMT %d", (ULONG )*(pControl->pusMsgType) ) ); WPLOG( LL_A, LM_CMsg, ( "Unknown CMT %d", (ULONG )*(pControl->pusMsgType) ) ); usXError = GERR_BadValue; break; } }
DBG_if (usXError != GERR_None) TRACE( TL_A, TM_CMsg, ( "XError=%d", (UINT )usXError ) );
pControl->usXError = usXError; }
USHORT ExplodeL2tpHeader( IN CHAR* pL2tpHeader, IN ULONG ulBufferLength, IN OUT L2TPHEADERINFO* pInfo )
// Fills caller's '*pInfo' with the addresses of the various fields in the
// L2TP header at 'pL2tpHeader'. Fields not present are returned as NULL.
// The byte order of the fields in 'pL2tpHeader' is flipped to
// host-byte-order in place. 'UlBufferLength' is the length in bytes from
// 'pL2tpHeader' to the end of the buffer.
//
// Returns GERR_None if 'pL2tpHeader' is a coherent L2TP header, or a
// GERR_* failure code.
//
{ USHORT *pusCur; USHORT usOffset; USHORT usBits; PUSHORT pusEndBuffer = (PUSHORT)(pL2tpHeader + ulBufferLength) - 1;
pusCur = (USHORT*)pL2tpHeader;
// The first 2 bytes contain bits that indicate the presence/absence of
// the other header fields.
//
*pusCur = ntohs( *pusCur ); pInfo->pusBits = pusCur; usBits = *pusCur; ++pusCur;
// The T bit indicates a control packet, as opposed to a payload packet.
//
if (usBits & HBM_T) { // Verify the field-present bits guaranteed to be set/clear in a
// control header are set correctly.
//
if ((usBits & HBM_Bits) != HBM_Control) { TRACE( TL_A, TM_CMsg, ( "Header: Bad bits=$%04x?", (ULONG )usBits ) ); WPLOG( LL_A, LM_CMsg, ( "Header: Bad bits=$%04x?", (ULONG )usBits ) ); return GERR_BadValue; } }
// Verify the version indicates L2TP. Cisco's L2F can theoretically
// co-exist on the same media address, though we don't support that.
//
if ((usBits & HBM_Ver) != VER_L2tp) { TRACE( TL_A, TM_Recv, ( "Header: Non-L2TP Ver=%d?", (usBits & HBM_Ver )) ); WPLOG( LL_A, LM_Recv, ( "Header: Non-L2TP Ver=%d?", (usBits & HBM_Ver )) ); return GERR_BadValue; }
// The L bit indicates a Length field is present.
//
if (usBits & HBM_L) { *pusCur = ntohs( *pusCur ); pInfo->pusLength = pusCur; ++pusCur; } else { pInfo->pusLength = NULL; }
// The Tunnel-ID field is always present.
//
*pusCur = ntohs( *pusCur ); pInfo->pusTunnelId = pusCur; ++pusCur;
// The Call-ID field is always present.
//
if(pusCur > pusEndBuffer) { return GERR_BadValue; } *pusCur = ntohs( *pusCur ); pInfo->pusCallId = pusCur; ++pusCur;
// The F bit indicates the Ns and Nr fields are present.
//
if (usBits & HBM_F) { if(pusCur > pusEndBuffer) { return GERR_BadValue; } *pusCur = ntohs( *pusCur ); pInfo->pusNs = pusCur; ++pusCur; if(pusCur > pusEndBuffer) { return GERR_BadValue; } *pusCur = ntohs( *pusCur ); pInfo->pusNr = pusCur; ++pusCur; } else { pInfo->pusNs = NULL; pInfo->pusNr = NULL; }
// The S bit indicates the Offset field is present. The S bit appears in
// the payload header only, as was verified above.
//
if (usBits & HBM_S) { if(pusCur > pusEndBuffer) { return GERR_BadValue; } *pusCur = ntohs( *pusCur ); usOffset = *pusCur; ++pusCur; } else { usOffset = 0; }
// End and length of header.
//
pInfo->pData = ((CHAR* )pusCur) + usOffset; pInfo->ulHeaderLength = (ULONG )(pInfo->pData - pL2tpHeader);
// "Official" data length.
//
if (pInfo->pusLength) { // Verify any specified length is at least as long as the set header
// bits imply and no longer than the received buffer.
//
if (*(pInfo->pusLength) < pInfo->ulHeaderLength || *(pInfo->pusLength) > ulBufferLength) { TRACE( TL_A, TM_Recv, ( "Header: Bad Length?" ) ); WPLOG( LL_A, LM_Recv, ( "Header: Bad Length? Length = %d, HeaderLength = %d", *(pInfo->pusLength), pInfo->ulHeaderLength) ); return GERR_BadLength; }
// Use the L2TP length as the "official" length, i.e. any strange
// bytes received beyond what the L2TP header says it sent will be
// ignored.
//
pInfo->ulDataLength = *(pInfo->pusLength) - pInfo->ulHeaderLength;
DBG_if( *(pInfo->pusLength) != ulBufferLength ) TRACE( TL_A, TM_Recv, ( "EOB padding ignored" ) ); } else { // Verify any implied length is at least as long as the set header
// bits imply.
//
if (ulBufferLength < pInfo->ulHeaderLength) { TRACE( TL_A, TM_Recv, ( "Header: Bad Length?" ) ); WPLOG( LL_A, LM_Recv, ( "Header: Bad Length? BufferLength = %d, HeaderLength = %d", ulBufferLength, pInfo->ulHeaderLength) ); return GERR_BadLength; }
// No length field so the received buffer length is the "official"
// length.
//
pInfo->ulDataLength = ulBufferLength - pInfo->ulHeaderLength; }
return GERR_None; }
USHORT GetAvpValueFixedAch( IN AVPINFO* pAvp, IN USHORT usArraySize, OUT CHAR** ppch )
// Set callers '*ppch' to point to value field of AVP 'pAvp' containing an
// array of 'usArraySize' bytes. No byte ordering is done.
//
// Returns GERR_None if successful, or a GERR_* error code.
//
{ // Make sure it's the right size.
//
if (pAvp->usValueLength != usArraySize) { return GERR_BadLength; }
*ppch = pAvp->pValue; return GERR_None; }
USHORT GetAvpValueFixedAul( IN AVPINFO* pAvp, IN USHORT usArraySize, OUT UNALIGNED ULONG** paulArray )
// Set callers '*paulArray' to point to value field of AVP 'pAvp'
// containing an array of 'usArraySize' ULONGs, converted to host
// byte-order. A 2-byte reserved field is assumed to preceed the first
// ULONG.
//
// Returns GERR_None if successful, or a GERR_* error code.
//
{ USHORT* pusCur; UNALIGNED ULONG* pulCur; ULONG i;
// Make sure it's the right size.
//
if (pAvp->usValueLength != sizeof(USHORT) + (usArraySize * sizeof(ULONG))) { return GERR_BadLength; }
pusCur = (USHORT* )pAvp->pValue;
// Skip over and ignore the 'Reserved' field.
//
++pusCur;
*paulArray = (UNALIGNED ULONG* )pusCur; for (i = 0, pulCur = *paulArray; i < usArraySize; ++i, ++pulCur) { // Convert to host byte-order.
//
*pulCur = ntohl( *pulCur ); }
return GERR_None; }
USHORT GetAvpValueFlag( IN AVPINFO* pAvp, OUT UNALIGNED BOOLEAN* pf )
// Set callers '*pf' to true since with a flag AVP the existence is the
// data, and performs the routine AVP validations.
//
// Returns GERR_None if successful, or a GERR_* error code.
//
{ // Make sure it's the right size.
//
if (pAvp->usValueLength != 0) { return GERR_BadLength; }
*pf = TRUE;
return GERR_None; }
USHORT GetAvpValueUs( IN AVPINFO* pAvp, OUT UNALIGNED USHORT** ppus )
// Set callers '*ppus' to point to the USHORT value field of AVP 'pAvp'.
// The field is host byte-ordered.
//
// Returns GERR_None if successful, or a GERR_* error code.
//
{ UNALIGNED USHORT* pusCur;
// Make sure it's the right size.
//
if (pAvp->usValueLength != sizeof(USHORT)) { return GERR_BadLength; }
// Convert in place to host byte-order.
//
pusCur = (USHORT* )pAvp->pValue; *pusCur = ntohs( *pusCur ); *ppus = pusCur;
return GERR_None; }
USHORT GetAvpValue2UsAndVariableAch( IN AVPINFO* pAvp, OUT UNALIGNED USHORT** ppus1, OUT UNALIGNED USHORT** ppus2, OUT CHAR** ppch, OUT USHORT* pusArraySize )
// Gets the data from an AVP with 2 USHORTs followed by a variable length
// array. Sets '*ppus1' and '*ppus2' to the two short integers and
// '*ppus' to the variable length array. '*PusArraySize is set to the
// length of the '*ppch' array. 'pAvp'. The field is host byte-ordered.
//
// Returns GERR_None if successful, or a GERR_* error code.
//
{ UNALIGNED USHORT* pusCur;
// Make sure it's the right size.
//
if (pAvp->usValueLength < sizeof(USHORT)) { return GERR_BadLength; }
// Convert in place to host byte-order.
//
pusCur = (USHORT* )pAvp->pValue; *pusCur = ntohs( *pusCur ); *ppus1 = pusCur; ++pusCur; // NOTE: second ushort value and the rest are optional
if (pAvp->usValueLength >= (2 * sizeof(USHORT))) { *pusCur = ntohs( *pusCur ); *ppus2 = pusCur; } else { *ppus2 = NULL; }
++pusCur;
if (pAvp->usValueLength > (2 * sizeof(USHORT))) { *ppch = (CHAR* )pusCur; *pusArraySize = pAvp->usValueLength - (2 * sizeof(USHORT)); } else { *ppch = NULL; *pusArraySize = 0; }
return GERR_None; }
USHORT GetAvpValueUl( IN AVPINFO* pAvp, OUT UNALIGNED ULONG** ppul )
// Set callers '*ppul' to point to the ULONG value field of AVP 'pAvp'.
// The field is host byte-ordered.
//
// Returns GERR_None if successful, or a GERR_* error code.
//
{ UNALIGNED ULONG* pulCur;
// Make sure it's the right size.
//
if (pAvp->usValueLength != sizeof(ULONG)) { return GERR_BadLength; }
// Convert in place to host byte-order.
//
pulCur = (UNALIGNED ULONG* )pAvp->pValue; *pulCur = ntohl( *pulCur ); *ppul = pulCur;
return GERR_None; }
USHORT GetAvpValueVariableAch( IN AVPINFO* pAvp, OUT CHAR** ppch, OUT USHORT* pusArraySize )
// Set callers '*ppch' to point to value field of AVP 'pAvp' containing an
// array of bytes, where '*pusArraySize' is set to the length in bytes.
// No byte ordering is done.
//
// Returns GERR_None if successful, or a GERR_* error code.
//
{ // The win9x clients send null host names. Remove the check.
//if(pAvp->usValueLength == 0 || pAvp->pValue[0] == '\0')
//{
// return GERR_BadLength;
//}
*pusArraySize = pAvp->usValueLength; *ppch = pAvp->pValue;
return GERR_None; }
VOID HelloTimerEvent( IN TIMERQITEM* pItem, IN VOID* pContext, IN TIMERQEVENT event )
// PTIMERQEVENT handler set to expire when a "Hello" interval has expired.
//
{ ADAPTERCB* pAdapter; TUNNELCB* pTunnel; BOOLEAN fReusedTimerQItem;
TRACE( TL_V, TM_Send, ( "HelloTimerEvent(%s)", TimerQPszFromEvent( event ) ) );
// Unpack context information.
//
pTunnel = (TUNNELCB* )pContext; pAdapter = pTunnel->pAdapter;
fReusedTimerQItem = FALSE;
if (event == TE_Expire) { NdisAcquireSpinLock( &pTunnel->lockT ); { if (pTunnel->ulHelloResetsThisInterval == 0 && pTunnel->ulRemainingHelloMs == 0) { if (pTunnel->state != CCS_Idle && pItem == pTunnel->pTqiHello) { // The full timeout period has expired, the tunnel's not
// idle, and the hello timer was not cancelled or
// terminated since the expire timer fired. It's time to
// send a "Hello" message to make sure the media is still
// up.
//
SendControl( pTunnel, NULL, CMT_Hello, 0, 0, NULL, 0 ); } DBG_else { TRACE( TL_A, TM_Send, ( "Hello aborted" ) ); }
pTunnel->pTqiHello = NULL; } else { ULONG ulTimeoutMs;
// Not a full timeout expiration event. Adjust interval
// counters and schedule next interval timeout.
//
if (pTunnel->ulHelloResetsThisInterval > 0) { pTunnel->ulRemainingHelloMs = pAdapter->ulHelloMs; pTunnel->ulHelloResetsThisInterval = 0; }
if (pTunnel->ulRemainingHelloMs >= L2TP_HelloIntervalMs) { ulTimeoutMs = L2TP_HelloIntervalMs; pTunnel->ulRemainingHelloMs -= L2TP_HelloIntervalMs; } else { ulTimeoutMs = pTunnel->ulRemainingHelloMs; pTunnel->ulRemainingHelloMs = 0; }
TimerQInitializeItem( pItem ); TimerQScheduleItem( pTunnel->pTimerQ, pItem, ulTimeoutMs, HelloTimerEvent, pTunnel );
fReusedTimerQItem = TRUE; } } NdisReleaseSpinLock( &pTunnel->lockT ); }
if (!fReusedTimerQItem) { FREE_TIMERQITEM( pAdapter, pItem ); } }
VOID IndicateReceived( IN VCCB* pVc, IN CHAR* pBuffer, IN ULONG ulOffset, IN ULONG ulLength, IN LONGLONG llTimeReceived )
// Indicates to the client above a packet received on VC 'pVc' containing
// 'ulLength' bytes of data from NDIS_BUFFER 'pBuffer' starting 'ulOffset'
// bytes in. Caller must not reference 'pBuffer' after calling this
// routine. 'UllTimeReceived' is the time the packet was received from
// the net, or 0 if call parameters said client doesn't care.
//
// IMPORTANT: Caller should not hold any spinlocks as this routine make
// NDIS indications.
//
{ NDIS_STATUS status; NDIS_PACKET* pPacket; NDIS_BUFFER* pTrimmedBuffer; ADAPTERCB* pAdapter; PACKETHEAD* pHead; LONG* plRef; LONG lRef;
pAdapter = pVc->pAdapter;
pPacket = GetPacketFromPool( &pAdapter->poolPackets, &pHead ); if (!pPacket) { // Packet descriptor pool is maxed.
//
FreeBufferToPool( &pAdapter->poolFrameBuffers, pBuffer, TRUE ); return; }
// Lop off the L2TP header and hook the corresponding NDIS_BUFFER to the
// packet. The "copy" here refers to descriptor information only. The
// packet data is not copied.
//
NdisCopyBuffer( &status, &pTrimmedBuffer, PoolHandleForNdisCopyBufferFromBuffer( pBuffer ), NdisBufferFromBuffer( pBuffer ), ulOffset, ulLength );
if (status != STATUS_SUCCESS) { // Can't get a MDL which likely means the system is toast.
//
TRACE( TL_A, TM_Recv, ( "NdisCopyBuffer=%08x?", status ) ); WPLOG( LL_A, LM_Recv, ( "NdisCopyBuffer=%08x?", status ) ); FreePacketToPool( &pAdapter->poolPackets, pHead, TRUE ); FreeBufferToPool( &pAdapter->poolFrameBuffers, pBuffer, TRUE ); return; } else { extern ULONG g_ulNdisCopyBuffers;
NdisInterlockedIncrement( &g_ulNdisCopyBuffers ); }
NdisChainBufferAtFront( pPacket, pTrimmedBuffer );
// Stash the time the packet was received in the packet.
//
NDIS_SET_PACKET_TIME_RECEIVED( pPacket, llTimeReceived );
// Pre-set the packet to success, since a random value of
// NDIS_STATUS_RESOURCES would prevent our ReturnPackets handler from
// getting called.
//
NDIS_SET_PACKET_STATUS( pPacket, NDIS_STATUS_SUCCESS );
// Stash our context information with the packet for clean-up use in
// LmpReturnPacket, then indicate the packet to NDISWAN.
//
*((PACKETHEAD** )(&pPacket->MiniportReserved[ 0 ])) = pHead; *((CHAR** )(&pPacket->MiniportReserved[ sizeof(VOID*) ])) = pBuffer;
TRACE( TL_N, TM_Recv, ( "NdisMCoIndRecPkt(len=%d)...", ulLength ) ); NdisMCoIndicateReceivePacket( pVc->NdisVcHandle, &pPacket, 1 ); TRACE( TL_N, TM_Recv, ( "NdisMCoIndRecPkt done" ) );
// Tell NDIS our "receive process" is complete. Since we deal with one
// packet at a time and NDISWAN does also, this doesn't accomplish
// anything, but the consensus is it's bad form to omit it.
//
TRACE( TL_N, TM_Recv, ( "NdisMCoRecComp..." ) ); NdisMCoReceiveComplete( pAdapter->MiniportAdapterHandle ); TRACE( TL_N, TM_Recv, ( "NdisMCoRecComp done" ) );
NdisInterlockedIncrement( &g_lPacketsIndicated );
NdisAcquireSpinLock( &pVc->lockV ); { ++pVc->stats.ulRecdDataPackets; pVc->stats.ulDataBytesRecd += ulLength; } NdisReleaseSpinLock( &pVc->lockV ); }
TUNNELCB* TunnelCbFromTunnelId( IN ADAPTERCB* pAdapter, IN USHORT usTunnelId )
// Return the tunnel control block associated with 'ulIpAddress' in
// 'pAdapter's list of TUNNELCBs or NULL if not found.
//
// 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->usTunnelId == usTunnelId) { pTunnel = pThis; break; } }
return pTunnel; }
BOOLEAN LookUpTunnelAndVcCbs( IN ADAPTERCB* pAdapter, IN USHORT* pusTunnelId, IN USHORT* pusCallId, IN L2TPHEADERINFO* pHeader, IN CONTROLMSGINFO* pControl, OUT TUNNELCB** ppTunnel, OUT VCCB** ppVc )
// Fill caller's '*ppTunnel' and '*ppVc' with the control blocks implied
// by the Tunnel-ID and Call-ID found in the header, if any. 'PHeader' is
// the exploded L2TP header. 'PControl' is the exploded control message
// info or NULL if payload.
//
// Returns true if a valid combination is found. This does not
// necessarily mean that both tunnel and VC outputs are non-NULL.
//
// Returns false if the combination is invalid. In this case, the packet
// is zombie acked if necessary. See ZombieAckIfNecessary routine.
//
{ BOOLEAN fFail;
*ppVc = NULL; *ppTunnel = NULL;
// As of draft-05 Tunnel-ID and Call-ID are no longer optional.
//
ASSERT( pusCallId ); ASSERT( pusTunnelId );
if (*pusCallId) { // Non-0 Call-ID must have non-0 Tunnel-ID
if(!*pusTunnelId) { return FALSE; } if (*pusCallId > pAdapter->usMaxVcs) { // Non-0 Call-ID out of range of the table, i.e. it's a VC that is
// being used for graceful termination and is not passed up. Look
// up tunnel and VC blocks by walking lists.
//
// Search the adapter's list of active tunnels for the one
// with peer's specified Tunnel-ID.
//
NdisAcquireSpinLock( &pAdapter->lockTunnels ); { *ppTunnel = TunnelCbFromTunnelId( pAdapter, *pusTunnelId ); if (*ppTunnel) { ReferenceTunnel( *ppTunnel, TRUE ); } } NdisReleaseSpinLock( &pAdapter->lockTunnels );
if (*ppTunnel) { // Search the tunnel's list of active VCs for the one with
// peer's specified Call-ID.
//
NdisAcquireSpinLock( &((*ppTunnel)->lockVcs) ); { *ppVc = VcCbFromCallId( *ppTunnel, *pusCallId ); if (*ppVc) { ReferenceVc( *ppVc ); } } NdisReleaseSpinLock( &((*ppTunnel)->lockVcs) );
if (!*ppVc) { // Non-0 Call-ID out of range of table with no
// associated VC control block.
//
TRACE( TL_A, TM_Recv, ( "CBs bad: Big CID w/!pV" ) ); WPLOG( LL_A, LM_Recv, ( "CBs bad: Big CID w/!pV" ) ); ZombieAckIfNecessary( *ppTunnel, pHeader, pControl ); DereferenceTunnel( *ppTunnel ); *ppTunnel = NULL; return FALSE; } } else { // Non-0 Call-ID out of range of table with no tunnel
// control block associated with the Tunnel-ID.
//
TRACE( TL_A, TM_Recv, ( "CBs bad: Big CID w/!pT" ) ); WPLOG( LL_A, LM_Recv, ( "CBs bad: Big CID w/!pT" ) ); return FALSE; } } else { // Read the VCCB* from the adapter's table.
//
fFail = FALSE; NdisDprAcquireSpinLock( &pAdapter->lockVcs ); { *ppVc = pAdapter->ppVcs[ *pusCallId - 1 ];
if (*ppVc && *ppVc != (VCCB* )-1) { ReferenceVc( *ppVc );
*ppTunnel = (*ppVc)->pTunnel; ASSERT( *ppTunnel ); ReferenceTunnel( *ppTunnel, FALSE );
if(*pusTunnelId != (*ppTunnel)->usTunnelId) { // Non-0 Call-ID is associated with a tunnel different
// than the one indicated by peer in the header.
//
TRACE( TL_A, TM_Recv, ( "CBs bad: TIDs=%d,%d?", (ULONG )*pusTunnelId, (ULONG )(*ppTunnel)->usTunnelId ) );
WPLOG( LL_A, LM_Recv, ( "CBs bad: TIDs=%d,%d?", (ULONG )*pusTunnelId, (ULONG )(*ppTunnel)->usTunnelId ) ); DereferenceTunnel( *ppTunnel ); *ppTunnel = NULL; DereferenceVc( *ppVc ); *ppVc = NULL; fFail = TRUE; } } else { // Non-0 Call-ID without an active VC.
//
TRACE( TL_A, TM_Recv, ( "CBs bad: CID=%d, pV=$%p?", (ULONG )*pusCallId, *ppVc ) );
WPLOG( LL_A, LM_Recv, ( "CBs bad: CID=%d, pV=$%p?", (ULONG )*pusCallId, *ppVc ) ); // Search the adapter's list of active tunnels for the one
// with peer's specified Tunnel-ID.
//
NdisAcquireSpinLock( &pAdapter->lockTunnels ); { *ppTunnel = TunnelCbFromTunnelId( pAdapter, *pusTunnelId ); if (*ppTunnel) { ReferenceTunnel( *ppTunnel, TRUE ); } } NdisReleaseSpinLock( &pAdapter->lockTunnels );
*ppVc = NULL; fFail = TRUE; } } NdisDprReleaseSpinLock( &pAdapter->lockVcs );
if (fFail) { if (*ppTunnel) { ZombieAckIfNecessary( *ppTunnel, pHeader, pControl ); DereferenceTunnel( *ppTunnel ); *ppTunnel = NULL; }
return FALSE; } } } else if (*pusTunnelId) { // 0 Call-ID with non-0 Tunnel-ID. Search the list of active tunnels
// for the one with peer's specified Tunnel-ID.
//
NdisAcquireSpinLock( &pAdapter->lockTunnels ); { *ppTunnel = TunnelCbFromTunnelId( pAdapter, *pusTunnelId ); if (*ppTunnel) { ReferenceTunnel( *ppTunnel, TRUE ); } } NdisReleaseSpinLock( &pAdapter->lockTunnels );
if (!*ppTunnel) { // 0 Call-Id with bogus Tunnel-ID.
//
TRACE( TL_A, TM_Recv, ( "CBs bad: Cid=0, Tid=%d, pT=0?", (ULONG )*pusTunnelId ) ); WPLOG( LL_A, LM_Recv, ( "CBs bad: Cid=0, Tid=%d, pT=0?", (ULONG )*pusTunnelId ) ); return FALSE; }
if (pControl && pControl->usXError == GERR_None && pControl->pusMsgType && *(pControl->pusMsgType) == CMT_CDN && pControl->pusAssignedCallId) { // The CallDisconnectNotify message includes the sender's assigned
// Call-ID as an AVP so that it may be sent before sender receives
// peer's assigned Call-ID. Unfortunately, this requires this
// routine to have AVP knowledge. Search the tunnel's list of
// associated VCs for the one with peer's specified Assigned
// Call-ID.
//
NdisDprAcquireSpinLock( &((*ppTunnel)->lockVcs) ); { *ppVc = VcCbFromCallId( *ppTunnel, *(pControl->pusAssignedCallId) );
if (*ppVc) { ReferenceVc( *ppVc ); } } NdisDprReleaseSpinLock( &((*ppTunnel)->lockVcs) );
if (!*ppVc) { // 0 Call-Id CDN with no associated VC.
//
TRACE( TL_A, TM_Recv, ( "CBs bad: CDN Tid %d, !pVc?", (ULONG )*pusTunnelId ) ); WPLOG( LL_A, LM_Recv, ( "CBs bad: CDN Tid %d, !pVc?", (ULONG )*pusTunnelId ) ); ZombieAckIfNecessary( *ppTunnel, pHeader, pControl ); DereferenceTunnel( *ppTunnel ); *ppTunnel = NULL; return FALSE; } } }
// Note: 0 Call-ID with 0 Tunnel-ID should only occur on peer's SCCRQ to
// start a tunnel, but that means it's not an error here, even though we
// report back neither control block.
ASSERT( !*ppTunnel || (*ppTunnel)->ulTag == MTAG_TUNNELCB ); ASSERT( !*ppVc || (*ppVc)->ulTag == MTAG_VCCB ); TRACE( TL_N, TM_Recv, ( "CBs good: pT=$%p, pV=$%p", *ppTunnel, *ppVc ) );
return TRUE; }
VOID PayloadAcknowledged( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN USHORT usReceivedNr )
// Cancels the timer of all payload-sent contexts in the VCs
// 'listSendsOut' queue with 'Next Sent' less than 'usReceivedNr'.
//
// IMPORTANT: Caller must hold 'pVc->lockV', which may be released and
// re-acquired by this routine. Caller must not hold any other
// locks.
//
{ while (!IsListEmpty( &pVc->listSendsOut )) { PAYLOADSENT* pPs; LIST_ENTRY* pLink; BOOLEAN fUpdateSendWindow; LINKSTATUSINFO info;
pLink = pVc->listSendsOut.Flink; pPs = CONTAINING_RECORD( pLink, PAYLOADSENT, linkSendsOut );
// The list is in 'Ns' order so as soon as a non-acknowledge is hit
// we're done.
//
if (CompareSequence( pPs->usNs, usReceivedNr ) >= 0) { break; }
// This packet has been acknowledged.
//
pPs->status = NDIS_STATUS_SUCCESS;
// Remove the context from the head of the "outstanding send" list.
// The corresponding dereference occurs below.
//
RemoveEntryList( &pPs->linkSendsOut ); InitializeListHead( &pPs->linkSendsOut );
// Doesn't matter if this cancel fails because the expire handler will
// recognize that the context is not linked into the "out" list and do
// nothing.
//
TimerQCancelItem( pTunnel->pTimerQ, pPs->pTqiSendTimeout );
// Adjust the timeouts and, if necessary, the send window as suggested
// in the draft/RFC.
//
AdjustTimeoutsAtAckReceived( pPs->llTimeSent, pTunnel->pAdapter->ulMaxSendTimeoutMs, &pVc->ulSendTimeoutMs, &pVc->ulRoundTripMs, &pVc->lDeviationMs );
fUpdateSendWindow = AdjustSendWindowAtAckReceived( pVc->ulMaxSendWindow, &pVc->ulAcksSinceSendTimeout, &pVc->ulSendWindow );
TRACE( TL_V, TM_Send, ( "C%d: ACK(%d) new rtt=%d dev=%d ato=%d sw=%d", (ULONG )pVc->usCallId, (ULONG )pPs->usNs, pVc->ulRoundTripMs, pVc->ulSendTimeoutMs, pVc->lDeviationMs, pVc->ulSendWindow ) );
// Update the statistics the reflect the acknowledge, it's round trip
// time, and any change in the send window. The field
// 'pVc->UlRoundTripMs' is really an "estimate" of the next round trip
// rather than the actual trip time. However, just after an
// acknowledge has been received, the two are identical so it can be
// used in the statistics here.
//
++pVc->stats.ulSentPacketsAcked; ++pVc->stats.ulRoundTrips; pVc->stats.ulRoundTripMsTotal += pVc->ulRoundTripMs;
if (pVc->ulRoundTripMs > pVc->stats.ulMaxRoundTripMs) { pVc->stats.ulMaxRoundTripMs = pVc->ulRoundTripMs; }
if (pVc->ulRoundTripMs < pVc->stats.ulMinRoundTripMs || pVc->stats.ulRoundTrips == 1) { pVc->stats.ulMinRoundTripMs = pVc->ulRoundTripMs; }
if (fUpdateSendWindow) { ++pVc->stats.ulSendWindowChanges;
if (pVc->ulSendWindow > pVc->stats.ulMaxSendWindow) { pVc->stats.ulMaxSendWindow = pVc->ulSendWindow; } else if (pVc->ulSendWindow < pVc->stats.ulMinSendWindow) { pVc->stats.ulMinSendWindow = pVc->ulSendWindow; }
// Indicate the send window change to NDISWAN. The lock is
// released first since this involves a call outside our driver.
//
TransferLinkStatusInfo( pVc, &info ); NdisReleaseSpinLock( &pVc->lockV ); { IndicateLinkStatus( pVc, &info ); } NdisAcquireSpinLock( &pVc->lockV ); }
// This dereference corresponds to the removal of the context from the
// "outstanding send" list above.
//
DereferencePayloadSent( pPs ); } }
BOOLEAN ReceiveFromOutOfOrder( IN VCCB* pVc )
// "Receives" the first buffer queued on 'pVc's out-of-order list if it is
// the next expected packet.
//
// Returns true if a buffer was "received", false otherwise. If true is
// returned, caller should call SchedulePayloadAck. It's not called here
// so caller can receive multiple packets from the out-of-order queue and
// set the timer once.
//
// IMPORTANT: Caller must hold 'pVc->lockV'. Also, be aware this routine
// may release and re-acquire the lock to make the NDIS receive
// indication.
//
{ ADAPTERCB* pAdapter; LIST_ENTRY* pFirstLink; PAYLOADRECEIVED* pFirstPr; SHORT sDiff;
TRACE( TL_N, TM_Recv, ( "ReceiveFromOutOfOrder Nr=%d", pVc->usNr ) );
if (IsListEmpty( &pVc->listOutOfOrder )) { // No out-of-order buffers queued.
//
TRACE( TL_N, TM_Recv, ( "None queued" ) ); return FALSE; }
pAdapter = pVc->pAdapter; pFirstLink = pVc->listOutOfOrder.Flink; pFirstPr = CONTAINING_RECORD( pFirstLink, PAYLOADRECEIVED, linkOutOfOrder );
// Verify the next queued buffer is in sequence first.
//
sDiff = CompareSequence( pFirstPr->usNs, pVc->usNr ); if (sDiff > 0) { // No, first queued packet is still beyond the next one expected.
//
TRACE( TL_I, TM_Recv, ( "Still out-of-order, Ns=%d", pFirstPr->usNs ) ); return FALSE; }
// De-queue the first out-of-order buffer and if it's exactly the one we
// expected, update 'Next Receive'to be the one following it's 'Next
// Send'. When peer sends an R-bit to set 'Next Receive' ahead, packets
// prior to the new expected packet may be queued before the expected
// packet. These packets are still good and are immediately indicated up,
// but since 'Next Receive' is already updated in that case, it is not
// adjusted here.
//
RemoveEntryList( pFirstLink ); InitializeListHead( pFirstLink );
if (sDiff == 0) { pVc->usNr = pFirstPr->usNs + 1; }
TRACE( TL_I, TM_Recv, ( "%d from queue", (UINT )pFirstPr->usNs ) ); ++pVc->stats.ulDataPacketsDequeued;
NdisReleaseSpinLock( &pVc->lockV ); { // Indicate the buffer to the driver above, and free it's out-of-order
// context.
//
IndicateReceived( pVc, pFirstPr->pBuffer, pFirstPr->ulPayloadOffset, pFirstPr->ulPayloadLength, pFirstPr->llTimeReceived );
FREE_PAYLOADRECEIVED( pAdapter, pFirstPr ); } NdisAcquireSpinLock( &pVc->lockV );
return TRUE; }
VOID ResetHelloTimer( IN TUNNELCB* pTunnel )
// Resets (logically anyway) the 'pTunnel' Hello timer.
//
{ ADAPTERCB* pAdapter;
pAdapter = pTunnel->pAdapter;
if (pAdapter->ulHelloMs) { NdisAcquireSpinLock( &pTunnel->lockT ); { if (pTunnel->state != CCS_Idle) { if (pTunnel->pTqiHello) { TRACE( TL_V, TM_Send, ( "Reset HelloTimer" ) );
// Timer's running so just note that a reset has occurred
// since it was started.
//
++pTunnel->ulHelloResetsThisInterval; } else { TRACE( TL_I, TM_Send, ( "Kickstart HelloTimer" ) );
// Timer is not running. Kickstart it by scheduling an
// "instant expire" event that will reset the interval.
//
pTunnel->pTqiHello = ALLOC_TIMERQITEM( pAdapter ); if (pTunnel->pTqiHello) { pTunnel->ulHelloResetsThisInterval = 1; pTunnel->ulRemainingHelloMs = 0;
TimerQInitializeItem( pTunnel->pTqiHello ); TimerQScheduleItem( pTunnel->pTimerQ, pTunnel->pTqiHello, 0, HelloTimerEvent, pTunnel ); } } } } NdisReleaseSpinLock( &pTunnel->lockT ); } }
VOID ScheduleControlAck( IN TUNNELCB* pTunnel, IN USHORT usMsgTypeToAcknowledge )
// Schedule a 'ControlAckTimerEvent' to occur in 1/4 of the standard send
// timeout. If one's already ticking no action is taken, because any
// packet that goes out will get it done. Doesn't matter who requested
// it. 'UsMsgTypeToAcknowledge' is the CMT_* code of the message to be
// acknowledged and is used for performance tuning.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ TIMERQITEM* pTqi; ADAPTERCB* pAdapter; ULONG ulDelayMs; BOOLEAN fFastAck;
if ((usMsgTypeToAcknowledge == CMT_StopCCN || usMsgTypeToAcknowledge == CMT_ICCN || usMsgTypeToAcknowledge == CMT_OCCN || usMsgTypeToAcknowledge == CMT_CDN) || (pTunnel->ulSendsOut < pTunnel->ulSendWindow)) { TRACE( TL_N, TM_Recv, ( "Fast ACK" ) );
// Certain messages where follow-on messages are unlikely are
// acknowledged without delay, as are all messages when the send
// window is closed.
//
fFastAck = TRUE; } else { fFastAck = FALSE; }
if (pTunnel->pTqiDelayedAck) { if (fFastAck) { TimerQExpireItem( pTunnel->pTimerQ, pTunnel->pTqiDelayedAck ); } } else { pAdapter = pTunnel->pAdapter; pTqi = ALLOC_TIMERQITEM( pAdapter ); if (!pTqi) { return; }
pTunnel->pTqiDelayedAck = pTqi;
if (fFastAck) { ulDelayMs = 0; } else { ulDelayMs = pTunnel->ulSendTimeoutMs >> 2; if (ulDelayMs > pAdapter->ulMaxAckDelayMs) { ulDelayMs = pAdapter->ulMaxAckDelayMs; } }
TRACE( TL_N, TM_Recv, ( "SchedControlAck(%dms)", ulDelayMs ) );
ReferenceTunnel( pTunnel, FALSE ); TimerQInitializeItem( pTqi ); TimerQScheduleItem( pTunnel->pTimerQ, pTqi, ulDelayMs, ControlAckTimerEvent, pTunnel ); } }
VOID SchedulePayloadAck( IN TUNNELCB* pTunnel, IN VCCB* pVc )
// Schedule a 'PayloadAckTimerEvent' to occur in 1/4 of the standard send
// timeout. If one's already ticking no action is taken, because any
// packet that goes out will get it done. Doesn't matter who requested
// it.
//
// IMPORTANT: Caller must hold 'pVc->lockV'.
//
{ ADAPTERCB* pAdapter; TIMERQITEM* pTqi; ULONG ulDelayMs;
if (!pVc->pTqiDelayedAck) { pAdapter = pVc->pAdapter; pTqi = ALLOC_TIMERQITEM( pAdapter ); if (!pTqi) { return; }
pVc->pTqiDelayedAck = pTqi;
ulDelayMs = pVc->ulSendTimeoutMs >> 2; if (ulDelayMs > pAdapter->ulMaxAckDelayMs) { ulDelayMs = pAdapter->ulMaxAckDelayMs; }
TRACE( TL_N, TM_Recv, ( "SchedPayloadAck(%dms)=$%p", ulDelayMs, pTqi ) );
ReferenceVc( pVc ); TimerQInitializeItem( pTqi ); TimerQScheduleItem( pTunnel->pTimerQ, pTqi, ulDelayMs, PayloadAckTimerEvent, pVc ); } }
VCCB* VcCbFromCallId( IN TUNNELCB* pTunnel, IN USHORT usCallId )
// Return the VC control block associated with 'usCallId' in 'pTunnel's
// list of active VCs or NULL if not found.
//
// IMPORTANT: Caller must hold 'pTunnel->lockVcs'.
//
{ VCCB* pVc; LIST_ENTRY* pLink;
pVc = NULL;
for (pLink = pTunnel->listVcs.Flink; pLink != &pTunnel->listVcs; pLink = pLink->Flink) { VCCB* pThis;
pThis = CONTAINING_RECORD( pLink, VCCB, linkVcs ); if (pThis->usCallId == usCallId) { pVc = pThis; break; } }
return pVc; }
VOID ZombieAckIfNecessary( IN TUNNELCB* pTunnel, IN L2TPHEADERINFO* pHeader, IN CONTROLMSGINFO* pControl )
// Determines if a message not matched to any VC warrants a "zombie"
// re-acknowledge, and if so, schedules one. This situation arises when
// our side sends an acknowledge to peer's CDN on a given call and the
// acknowledge is lost. Our side tears down the VC immediately, but peer
// will eventually drop the entire tunnel if no acknowledge of his follow
// on CDN retransmits are received, thus affecting calls beyond the one
// dropped. This routine acknowledges such retransmissions.
//
// Another simpler approach would be to take a reference on the call and
// hold it for a full retransmission interval before dereferencing.
// However, this would block the drop indications up and would therefore,
// from dial-out user's point of view, cause a potentially long delay
// whenever server disconnected a call. This is judged undesirable enough
// to tolerate the zombie ack messiness.
//
// 'PTunnel' is the associated tunnel control block. 'PHeader' is the
// exploded L2TP header. 'PControl' is the exploded control header, or
// NULL if not a control message. Caller should already have determined
// that no VC is associated with the message.
//
{ if (pControl && pControl->usXError == GERR_None && pControl->pusMsgType && *(pControl->pusMsgType) == CMT_CDN && pControl->pusAssignedCallId) { // It's a CDN message and a candidate for re-acknowledgement. See if
// it's sequence number is prior to or equal to the next expected
// packet. If so, schedule a zombie acknowledge.
//
if (CompareSequence( *(pHeader->pusNs), pTunnel->usNr ) <= 0) { TRACE( TL_A, TM_Send, ( "Zombie acking" ) );
NdisAcquireSpinLock( &pTunnel->lockT ); { // Cancel any pending delayed acknowledge timeout.
//
if (pTunnel->pTqiDelayedAck) { TimerQCancelItem( pTunnel->pTimerQ, pTunnel->pTqiDelayedAck ); pTunnel->pTqiDelayedAck = NULL; } } NdisReleaseSpinLock( &pTunnel->lockT );
ScheduleTunnelWork( pTunnel, NULL, SendControlAck, 0, 0, 0, 0, FALSE, FALSE ); } } }
|