// 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 ); } } }