|
|
// Copyright (c) 1997, Microsoft Corporation, all rights reserved
//
// fsm.c
// RAS L2TP WAN mini-port/call-manager driver
// L2TP finite state machine routines
//
// 01/07/97 Steve Cobb
#include "l2tpp.h"
#include "fsm.tmh"
//-----------------------------------------------------------------------------
// Local prototypes (alphabetically)
//-----------------------------------------------------------------------------
VOID FsmInCallIdle( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmInCallWaitConnect( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmInCallEstablished( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmInCallWaitReply( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmOutCallBearerAnswer( IN TUNNELCB* pTunnel, IN VCCB* pVc );
VOID FsmOutCallEstablished( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmOutCallIdle( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmOutCallWaitReply( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmOutCallWaitConnect( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl );
VOID FsmTunnelEstablished( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl );
VOID FsmTunnelIdle( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl );
VOID FsmTunnelWaitCtlConnect( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl );
VOID FsmTunnelWaitCtlReply( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl );
VOID GetCcAvps( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl, OUT USHORT* pusResult, OUT USHORT* pusError );
ULONG StatusFromResultAndError( IN USHORT usResult, IN USHORT usError );
//-----------------------------------------------------------------------------
// FSM interface routines
//-----------------------------------------------------------------------------
BOOLEAN FsmReceive( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CHAR* pBuffer, IN CONTROLMSGINFO* pControl )
// Dispatches a received control message to the appropriate FSM handler.
// 'PTunnel' and 'pVc' are the tunnel and VC control blocks. 'PControl'
// is the exploded description of the received control message. 'PBuffer'
// is the receieve buffer.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
// Returns true if the message was processed, false if
// SetupVcAsynchronously was called.
//
{ TRACE( TL_V, TM_Cm, ( "FsmReceive" ) );
if (pControl->fTunnelMsg) { switch (pTunnel->state) { case CCS_Idle: { FsmTunnelIdle( pTunnel, pControl ); break; }
case CCS_WaitCtlReply: { FsmTunnelWaitCtlReply( pTunnel, pControl ); break; }
case CCS_WaitCtlConnect: { FsmTunnelWaitCtlConnect( pTunnel, pControl ); break; }
case CCS_Established: { FsmTunnelEstablished( pTunnel, pControl ); break; } } } else { if (!pVc) { if (*(pControl->pusMsgType) == CMT_ICRQ || *(pControl->pusMsgType) == CMT_OCRQ) { ULONG ulIpAddress; // Check if the MUST AVPs are present
if(!pControl->pusAssignedCallId || !pControl->pulCallSerialNumber) { return TRUE; }
// Peer wants to start a new call. Set up a VC and dispatch
// the received call request to the client above. This is an
// asynchronous operation that will eventually call
// ReceiveControlExpected to finish processing the message.
//
ulIpAddress = pTunnel->address.ulIpAddress; NdisReleaseSpinLock( &pTunnel->lockT ); { SetupVcAsynchronously( pTunnel, ulIpAddress, pBuffer, pControl ); } NdisAcquireSpinLock( &pTunnel->lockT ); return FALSE; } else { // Don't know what VC the call control message if for and it's
// not a "create new call" request, so there's nothing useful
// to do. Ignore it. Don't want to bring down the tunnel
// because it may just be out of order. One case is where
// post-ICRQ packets are received before ICRQ is processed, to
// create the VC block.
//
TRACE( TL_A, TM_Fsm, ( "CMT %d w/o VC?", *(pControl->pusMsgType) ) ); WPLOG( LL_A, LM_Fsm, ( "CMT %d w/o VC?", *(pControl->pusMsgType) ) ); return TRUE; } }
NdisAcquireSpinLock( &pVc->lockV ); { if (ReadFlags( &pVc->ulFlags ) & VCBF_IncomingFsm) { // L2TP Incoming Call FSM for both LAC/LNS.
//
switch (pVc->state) { case CS_Idle: { FsmInCallIdle( pTunnel, pVc, pControl ); break; }
case CS_WaitReply: { FsmInCallWaitReply( pTunnel, pVc, pControl ); break; }
case CS_WaitConnect: { FsmInCallWaitConnect( pTunnel, pVc, pControl ); break; }
case CS_Established: { FsmInCallEstablished( pTunnel, pVc, pControl ); break; } } } else { // L2TP Outgoing Call FSM for both LAC/LNS.
//
switch (pVc->state) { case CS_Idle: { FsmOutCallIdle( pTunnel, pVc, pControl ); break; }
case CS_WaitReply: { FsmOutCallWaitReply( pTunnel, pVc, pControl ); break; }
case CS_WaitConnect: { FsmOutCallWaitConnect( pTunnel, pVc, pControl ); break; }
case CS_WaitCsAnswer: { // Because no WAN modes are supported and locks are
// held during the "null" WAN bearer answer, we should
// never be in this state on a received message.
//
ASSERT( FALSE ); break; }
case CS_Established: { FsmOutCallEstablished( pTunnel, pVc, pControl ); break; } } } } NdisReleaseSpinLock( &pVc->lockV ); }
return TRUE; }
VOID FsmOpenTunnel( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs )
// A PTUNNELWORK routine to handle a Control Connection (tunnel) Open
// event.
//
// This routine is called only at PASSIVE IRQL.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter;
TRACE( TL_N, TM_Fsm, ( "FsmOpenTunnel" ) );
// Unpack context information then free the work item.
//
pAdapter = pVc->pAdapter; FREE_TUNNELWORK( pAdapter, pWork );
status = NDIS_STATUS_SUCCESS; if (!(ReadFlags( &pTunnel->ulFlags ) & TCBF_TdixReferenced)) { // Set up TDI for L2TP send/receive.
//
status = TdixOpen( &pAdapter->tdix ); if (status == NDIS_STATUS_SUCCESS) { // Set this flag so TdixClose is called as the tunnel control
// block is destroyed.
//
SetFlags( &pTunnel->ulFlags, TCBF_TdixReferenced ); } else { TRACE( TL_A, TM_Fsm, ( "TdixOpen=$%08x", status ) ); WPLOG( LL_A, LM_Fsm, ( "TdixOpen=$%08x", status ) ); } }
NdisAcquireSpinLock( &pTunnel->lockT ); { if (status == NDIS_STATUS_SUCCESS) { if (ReadFlags( &pTunnel->ulFlags ) & TCBF_Closing) { // New tunnel requests cannot be linked onto closing tunnels
// as they would not be properly cleaned up.
//
TRACE( TL_A, TM_Fsm, ( "FOT aborted" ) ); WPLOG( LL_A, LM_Fsm, ( "Can't open on an aborted tunnel" ) ); status = NDIS_STATUS_TAPI_DISCONNECTMODE_UNKNOWN; } }
if (status == NDIS_STATUS_SUCCESS) { if (ReadFlags( &pTunnel->ulFlags ) & TCBF_CcInTransition) { // The tunnel control channel is in the process of changing
// states from Idle to Established or vice-versa. Queue our
// request to be resolved when the result is known. See
// TunnelTransitionComplete.
//
ASSERT( pVc->linkRequestingVcs.Flink == &pVc->linkRequestingVcs ); InsertTailList( &pTunnel->listRequestingVcs, &pVc->linkRequestingVcs ); } else { // The tunnel control channel is in the Idle or Established
// states and no transition is underway.
//
if (pTunnel->state == CCS_Established) { // The tunnel control channel is already up, so skip ahead
// to making a call to establish the data channel.
//
WPLOG( LL_M, LM_Fsm, ( "TUNNEL %p established, CALL %p", pTunnel, pVc) ); FsmOpenCall( pTunnel, pVc ); } else { // The tunnel control channel is down, so try to bring it
// up.
//
WPLOG( LL_M, LM_Fsm, ( "TUNNEL %p idle, CALL %p", pTunnel, pVc) ); FsmOpenIdleTunnel( pTunnel, pVc ); } } } else { // Fail the call.
//
NdisAcquireSpinLock( &pVc->lockV ); { pVc->status = status; CallTransitionComplete( pTunnel, pVc, CS_Idle ); } NdisReleaseSpinLock( &pVc->lockV );
CompleteVcs( pTunnel ); } } NdisReleaseSpinLock( &pTunnel->lockT ); }
VOID FsmOpenIdleTunnel( IN TUNNELCB* pTunnel, IN VCCB* pVc )
// Initiate the tunnel connection on 'pTunnel' requested by 'pVc', i.e.
// send the initial SCCRQ which kicks off the control connection (tunnel)
// FSM.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ TRACE( TL_N, TM_Cm, ( "FsmOpenIdleTunnel" ) ); ASSERT( pTunnel->state == CCS_Idle );
SetFlags( &pTunnel->ulFlags, TCBF_CcInTransition ); ASSERT( pVc->linkRequestingVcs.Flink == &pVc->linkRequestingVcs ); InsertTailList( &pTunnel->listRequestingVcs, &pVc->linkRequestingVcs );
pTunnel->state = CCS_WaitCtlReply; SendControl( pTunnel, NULL, CMT_SCCRQ, 0, 0, NULL, 0 ); }
VOID FsmOpenCall( IN TUNNELCB* pTunnel, IN VCCB* pVc )
// Execute an "open" event for a call on 'pTunnel'/'pVc' playing the role
// of the LAC/LNS indicated by the VCBF_IncomingFsm flag. The owning
// tunnel must be established first.
//
{ ULONG ulFlags; USHORT usMsgType;
ulFlags = ReadFlags( &pVc->ulFlags );
TRACE( TL_N, TM_Cm, ( "FsmCallOpen" ) ); ASSERT( (ulFlags & VCBF_ClientOpenPending) || (ulFlags & VCBF_PeerOpenPending) ); ASSERT( pVc->state == CS_Idle || pVc->state == CS_WaitTunnel );
ActivateCallIdSlot( pVc );
if (pVc->pAdapter->usPayloadReceiveWindow) { SetFlags( &pVc->ulFlags, VCBF_Sequencing ); }
usMsgType = (USHORT )((ulFlags & VCBF_IncomingFsm) ? CMT_ICRQ : CMT_OCRQ );
pVc->state = CS_WaitReply; SendControl( pTunnel, pVc, usMsgType, 0, 0, NULL, 0 ); }
VOID FsmCloseTunnel( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs )
// A PTUNNELWORK routine to close down 'pTunnel' gracefully. Arg0 and
// Arg1 are the result and error codes to send in the StopCCN message.
//
// This routine is called only at PASSIVE IRQL.
//
{ USHORT usResult; USHORT usError;
// Unpack context information, then free the work item.
//
usResult = (USHORT )(punpArgs[ 0 ]); usError = (USHORT )(punpArgs[ 1 ]); FREE_TUNNELWORK( pTunnel->pAdapter, pWork );
ASSERT( usResult );
NdisAcquireSpinLock( &pTunnel->lockT ); { if (pTunnel->state == CCS_Idle || pTunnel->state == CCS_WaitCtlReply) { TRACE( TL_I, TM_Cm, ( "FsmCloseTunnel(f=$%08x,r=%d,e=%d) now", ReadFlags( &pTunnel->ulFlags ), (UINT )usResult, (UINT )usError ) );
// The tunnel's already idle so no closing exchange is necessary.
// We also include the other state where we've had no response
// from peer, but have sent our SCCRQ. This is a tad rude to the
// remote peer as we're deciding that it's more important to
// respond quickly to our cancelling user than it is to wait for a
// peer who may not be responding. However, this is the trade-off
// we've chosen.
//
CloseTunnel2( pTunnel ); } else { TRACE( TL_I, TM_Cm, ( "FsmCloseTunnel(f=$%08x,r=%d,e=%d) grace", ReadFlags( &pTunnel->ulFlags ), (UINT )usResult, (UINT )usError ) );
// Set flags and reference the tunnel for "graceful close". The
// reference is removed when the tunnel reaches idle state.
//
SetFlags( &pTunnel->ulFlags, (TCBF_Closing | TCBF_FsmCloseRef | TCBF_CcInTransition) ); ReferenceTunnel( pTunnel, FALSE );
// Initiate the closing exchange, holding the VC until the closing
// message is acknowledged.
//
pTunnel->state = CCS_Idle; SendControl( pTunnel, NULL, CMT_StopCCN, (ULONG )usResult, (ULONG )usError, NULL, CSF_TunnelIdleOnAck ); } } NdisReleaseSpinLock( &pTunnel->lockT ); }
VOID FsmCloseCall( IN TUNNELWORK* pWork, IN TUNNELCB* pTunnel, IN VCCB* pVc, IN ULONG_PTR* punpArgs )
// A PTUNNELWORK routine to close down the call on 'pVc' gracefully. Arg0
// and Arg1 are the result and error codes to send in the CDN message.
//
// This routine is called only at PASSIVE IRQL.
//
{ BOOLEAN fCompleteVcs; USHORT usResult; USHORT usError;
// Unpack context information, then free the work item.
//
usResult = (USHORT )(punpArgs[ 0 ]); usError = (USHORT )(punpArgs[ 1 ]); FREE_TUNNELWORK( pTunnel->pAdapter, pWork );
ASSERT( usResult );
fCompleteVcs = FALSE; NdisAcquireSpinLock( &pTunnel->lockT ); { NdisAcquireSpinLock( &pVc->lockV ); { if (pVc->state == CS_Idle || pVc->state == CS_WaitTunnel || (ReadFlags( &pVc->ulFlags ) & VCBF_PeerClosePending)) { TRACE( TL_I, TM_Cm, ( "FsmCloseCall(f=$%08x,r=%d,e=%d) now", ReadFlags( &pVc->ulFlags ), (UINT )usResult, (UINT )usError ) );
if (usResult == CRESULT_GeneralWithError) { usResult = TRESULT_GeneralWithError; } else { usResult = TRESULT_Shutdown; usError = GERR_None; }
// Slam the call closed.
//
fCompleteVcs = CloseCall2( pTunnel, pVc, usResult, usError ); } else { TRACE( TL_I, TM_Cm, ( "FsmCloseCall(f=$%08x,r=%d,e=%d) grace", ReadFlags( &pVc->ulFlags ), (UINT )usResult, (UINT )usError ) );
// Initiate the closing exchange.
//
pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_NORMAL; pVc->state = CS_Idle; SendControl( pTunnel, pVc, CMT_CDN, (ULONG )usResult, (ULONG )usError, NULL, CSF_CallIdleOnAck ); }
} NdisReleaseSpinLock( &pVc->lockV );
if (fCompleteVcs) { CompleteVcs( pTunnel ); } } NdisReleaseSpinLock( &pTunnel->lockT ); }
VOID TunnelTransitionComplete( IN TUNNELCB* pTunnel, IN L2TPCCSTATE state )
// Sets 'pTunnel's state to it's new CCS_Idle or CCS_Established 'state'
// and kickstarts any MakeCall's that pended on the result. If
// established, adds the host route directing IP traffic to the L2TP peer
// to the LAN card rather than the WAN (tunnel) adapter.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ NDIS_STATUS status; LIST_ENTRY list; LIST_ENTRY* pLink; ULONG ulFlags; VCCB* pVc;
pTunnel->state = state; ClearFlags( &pTunnel->ulFlags, TCBF_CcInTransition ); ulFlags = ReadFlags( &pTunnel->ulFlags );
if (state == CCS_Established) { TRACE( TL_A, TM_Fsm, ( "TUNNEL %d UP", (ULONG )pTunnel->usTunnelId ) ); WPLOG( LL_M, LM_Fsm, ( "TUNNEL %p UP, Tid %d, Peer's Tid %d", pTunnel, pTunnel->usTunnelId, pTunnel->usAssignedTunnelId ) );
// The tunnel any requesting VCs wanted established was established.
// Skip ahead to establishing the outgoing calls.
//
while (!IsListEmpty( &pTunnel->listRequestingVcs )) { pLink = RemoveHeadList( &pTunnel->listRequestingVcs ); InitializeListHead( pLink ); pVc = CONTAINING_RECORD( pLink, VCCB, linkRequestingVcs ); FsmOpenCall( pTunnel, pVc ); }
// Add the host route so traffic sent to the L2TP peer goes out the
// LAN card instead of looping on the WAN (tunnel) interface, when
// activated.
//
TRACE( TL_N, TM_Recv, ( "Schedule AddHostRoute" ) ); ASSERT( !(ulFlags & TCBF_HostRouteAdded) ); ScheduleTunnelWork( pTunnel, NULL, AddHostRoute, 0, 0, 0, 0, FALSE, FALSE ); } else { ASSERT( state == CCS_Idle ); SetFlags( &pTunnel->ulFlags, TCBF_Closing );
TRACE( TL_A, TM_Fsm, ( "%s TUNNEL %d DOWN", ((ulFlags & TCBF_PeerInitiated) ? "IN" : "OUT"), (ULONG )pTunnel->usTunnelId ) );
WPLOG( LL_M, LM_Fsm, ( "%s TUNNEL %p DOWN, Tid %d, Peer's Tid %d", ((ulFlags & TCBF_PeerInitiated) ? "IN" : "OUT"), pTunnel, pTunnel->usTunnelId, pTunnel->usAssignedTunnelId ) ); // Any VCs associated with the tunnel are abruptly terminated. This
// is done by making it look like any pending operation has failed, or
// if none is pending, that a bogus peer initiated close has
// completed.
//
NdisAcquireSpinLock( &pTunnel->lockVcs ); { for (pLink = pTunnel->listVcs.Flink; pLink != &pTunnel->listVcs; pLink = pLink->Flink) {
pVc = CONTAINING_RECORD( pLink, VCCB, linkVcs );
NdisAcquireSpinLock( &pVc->lockV ); { if (pVc->status == NDIS_STATUS_SUCCESS) { if (ulFlags & TCBF_PeerNotResponding) { // Line went down because peer stopped responding
// (or never responded).
//
pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_NOANSWER; } else { // Line went down for unknown reason.
//
pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_UNKNOWN; } }
CallTransitionComplete( pTunnel, pVc, CS_Idle ); } NdisReleaseSpinLock( &pVc->lockV ); } } NdisReleaseSpinLock( &pTunnel->lockVcs );
ASSERT( IsListEmpty( &pTunnel->listRequestingVcs ) );
// Flush the outstanding send list.
//
while (!IsListEmpty( &pTunnel->listSendsOut )) { CONTROLSENT* pCs;
pLink = RemoveHeadList( &pTunnel->listSendsOut ); InitializeListHead( pLink ); pCs = CONTAINING_RECORD( pLink, CONTROLSENT, linkSendsOut );
TRACE( TL_I, TM_Recv, ( "Flush pCs=$%p", pCs ) );
// Terminate the timer. Doesn't matter if the terminate fails as
// the expire handler recognizes the context is not on the "out"
// list and does nothing.
//
ASSERT( pCs->pTqiSendTimeout ); TimerQTerminateItem( pTunnel->pTimerQ, pCs->pTqiSendTimeout );
// Remove the context reference corresponding to linkage in the
// "out" list. Terminate the
//
DereferenceControlSent( pCs ); }
// Flush the out of order list.
//
while (!IsListEmpty( &pTunnel->listOutOfOrder )) { CONTROLRECEIVED* pCr; ADAPTERCB* pAdapter;
pLink = RemoveHeadList( &pTunnel->listOutOfOrder ); InitializeListHead( pLink ); pCr = CONTAINING_RECORD( pLink, CONTROLRECEIVED, linkOutOfOrder );
TRACE( TL_I, TM_Recv, ( "Flush pCr=$%p", pCr ) );
pAdapter = pTunnel->pAdapter; FreeBufferToPool( &pAdapter->poolFrameBuffers, pCr->pBuffer, TRUE );
if (pCr->pVc) { DereferenceVc( pCr->pVc ); }
FREE_CONTROLRECEIVED( pAdapter, pCr ); }
// Cancel the "hello" timer if it's running.
//
if (pTunnel->pTqiHello) { TimerQCancelItem( pTunnel->pTimerQ, pTunnel->pTqiHello ); pTunnel->pTqiHello = NULL; }
if (ulFlags & TCBF_PeerInitRef) { // Remove the "peer initiation" tunnel reference.
//
ClearFlags( &pTunnel->ulFlags, TCBF_PeerInitRef ); DereferenceTunnel( pTunnel ); }
if (ulFlags & TCBF_FsmCloseRef) { // Remove the "graceful close" tunnel reference.
//
ClearFlags( &pTunnel->ulFlags, TCBF_FsmCloseRef ); DereferenceTunnel( pTunnel ); } } }
VOID CallTransitionComplete( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN L2TPCALLSTATE state )
// Sets 'pVc's state to it's new CS_Idle or CS_Established state and sets
// up for reporting the result to the client.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT' and 'pVc->lockV'.
//
{ ULONG ulFlags;
pVc->state = state;
ulFlags = ReadFlags( &pVc->ulFlags ); if (!(ulFlags & VCBM_Pending)) { if (ulFlags & VCBF_CallClosableByPeer) { // Nothing else was pending and the call is closable so either
// peer initiated a close or some fatal error occurred which will
// be cleaned up as if peer initiated a close.
//
ASSERT( pVc->status != NDIS_STATUS_SUCCESS ); SetFlags( &pVc->ulFlags, VCBF_PeerClosePending ); ClearFlags( &pVc->ulFlags, VCBF_CallClosableByPeer ); } else { // Nothing was pending and the call's not closable, so there's no
// action required for this transition.
//
TRACE( TL_I, TM_Fsm, ( "Call not closable" ) ); return; } } else if (ulFlags & VCBF_ClientOpenPending) { if (pVc->status != NDIS_STATUS_SUCCESS) { // A pending client open just failed and will bring down the call.
// From this point on we will fail new attempts to close the call
// from both client and peer.
//
ClearFlags( &pVc->ulFlags, (VCBF_CallClosableByClient | VCBF_CallClosableByPeer )); } } else if (ulFlags & VCBF_PeerOpenPending) { if (pVc->status != NDIS_STATUS_SUCCESS) { // A pending peer open just failed and will bring down the call.
// From this point on we will fail new attempts to close the call
// from the peer. Client closes must be accepted because of the
// way CoNDIS loops dispatched close calls back to the CM's close
// handler.
//
ClearFlags( &pVc->ulFlags, VCBF_CallClosableByPeer ); } }
// Update some call statistics.
//
{ LARGE_INTEGER lrgTime;
NdisGetCurrentSystemTime( &lrgTime ); if (pVc->state == CS_Idle) { if (pVc->stats.llCallUp) { pVc->stats.ulSeconds = (ULONG ) (((ULONGLONG )lrgTime.QuadPart - pVc->stats.llCallUp) / 10000000); } } else { ASSERT( pVc->state == CS_Established ); pVc->stats.llCallUp = (ULONGLONG )lrgTime.QuadPart;
pVc->stats.ulMinSendWindow = pVc->stats.ulMaxSendWindow = pVc->ulSendWindow; } }
TRACE( TL_A, TM_Fsm, ( "CALL %d ON TUNNEL %d %s", (ULONG )pVc->usCallId, (ULONG )pTunnel->usTunnelId, ((state == CS_Established) ? "UP" : "DOWN") ) ); WPLOG( LL_M, LM_Fsm, ( "CALL %p, Cid %d, ON TUNNEL %p, Tid %d %s", pVc, (ULONG )pVc->usCallId, pTunnel, (ULONG )pTunnel->usTunnelId, ((state == CS_Established) ? "UP" : "DOWN") ) );
// Move the VC onto the tunnel's completing list. The VC may or may not
// be on the tunnel request list, but if it is, remove it.
//
RemoveEntryList( &pVc->linkRequestingVcs ); InitializeListHead( &pVc->linkRequestingVcs ); // Check if this VC is already on the list
if(!(ReadFlags(&pVc->ulFlags) & VCBF_CompPending)) { ASSERT( pVc->linkCompletingVcs.Flink == &pVc->linkCompletingVcs ); ASSERT( pVc->linkCompletingVcs.Blink == &pVc->linkCompletingVcs ); SetFlags( &pVc->ulFlags, VCBF_CompPending ); InsertTailList( &pTunnel->listCompletingVcs, &pVc->linkCompletingVcs ); } }
//-----------------------------------------------------------------------------
// FSM utility routines (alphabetically)
//-----------------------------------------------------------------------------
VOID FsmInCallEstablished( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Incoming call creation FSM Established state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ if (*(pControl->pusMsgType) == CMT_CDN) { // Call is down.
//
pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_NORMAL; CallTransitionComplete( pTunnel, pVc, CS_Idle ); } }
VOID FsmInCallIdle( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Incoming call creation FSM Idle state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter;
pAdapter = pVc->pAdapter;
if (*(pControl->pusMsgType) == CMT_ICRQ) { if (!(ReadFlags( &pVc->ulFlags ) & VCBF_PeerOpenPending)) { // If no open is pending, the call and/or owning tunnel has been
// slammed, we are in the clean up phase, and no response should
// be made.
//
TRACE( TL_A, TM_Fsm, ( "IC aborted" ) ); WPLOG( LL_A, LM_Fsm, ( "ICRQ received & we're in the cleanup phase" ) ); return; }
if (*pControl->pusAssignedCallId) { pVc->usAssignedCallId = *(pControl->pusAssignedCallId); }
if (pVc->usResult) { // Call is down, but must hold the VC until the closing message is
// acknowledged.
//
pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_NORMAL; pVc->state = CS_Idle; SendControl( pTunnel, pVc, CMT_CDN, (ULONG )pVc->usResult, (ULONG )pVc->usError, NULL, CSF_CallIdleOnAck ); } else { if (pAdapter->usPayloadReceiveWindow) { SetFlags( &pVc->ulFlags, VCBF_Sequencing ); }
// Stash call serial number.
//
if (pControl->pulCallSerialNumber) { pVc->pLcParams->ulCallSerialNumber = *(pControl->pulCallSerialNumber); } else { pVc->pLcParams->ulCallSerialNumber = 0; }
// Stash acceptable bearer types.
//
pVc->pTcInfo->ulMediaMode = 0; if (pControl->pulBearerType) { if (*(pControl->pulBearerType) & BBM_Analog) { pVc->pTcInfo->ulMediaMode |= LINEMEDIAMODE_DATAMODEM; }
if (*(pControl->pulBearerType) & BBM_Digital) { pVc->pTcInfo->ulMediaMode |= LINEMEDIAMODE_DIGITALDATA; } }
// Stash physical channel ID.
//
if (pControl->pulPhysicalChannelId) { pVc->pLcParams->ulPhysicalChannelId = *(pControl->pulPhysicalChannelId); } else { pVc->pLcParams->ulPhysicalChannelId = 0xFFFFFFFF; }
// Note: The phone numbers of the caller and callee as well as the
// Subaddress are available at this point. Currently, the
// CallerID field of the TAPI structures is used for the IP
// address of the other end of the tunnel, which is used above for
// the IPSEC filters. The WAN caller information may also be
// useful but there is no obvious way to return both the WAN and
// tunnel endpoints in the current TAPI structures.
// Send response.
//
pVc->state = CS_WaitConnect; SendControl( pTunnel, pVc, CMT_ICRP, 0, 0, NULL, 0 ); } } }
VOID FsmInCallWaitConnect( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Incoming call creation FSM WaitConnect state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ if (*(pControl->pusMsgType) == CMT_ICCN) { if (pControl->pulTxConnectSpeed) { pVc->ulConnectBps = *(pControl->pulTxConnectSpeed); } else { // Not supposed to happen, but go on with a least common
// denominator if it does.
//
pVc->ulConnectBps = 9600; }
if (pControl->pulFramingType && !(*(pControl->pulFramingType) & FBM_Sync)) { // Uh oh, the call is not using synchronous framing, which is the
// only one NDISWAN supports. Peer should have noticed we don't
// support asynchronous during tunnel setup. Close the call.
//
TRACE( TL_A, TM_Fsm, ( "Sync framing?" ) ); WPLOG( LL_A, LM_Fsm, ( "Sync framing?" ) );
if (!(pVc->pAdapter->ulFlags & ACBF_IgnoreFramingMismatch)) { ScheduleTunnelWork( pTunnel, pVc, FsmCloseCall, (ULONG_PTR )CRESULT_GeneralWithError, (ULONG_PTR )GERR_None, 0, 0, FALSE, FALSE ); return; } }
if (!pControl->pusRWindowSize) { // Peer did not send a receive window AVP so we're not doing Ns/Nr
// flow control on the session. If we requested sequencing peer
// is really supposed to send his window, but if he doesn't assume
// that means he wants no sequencing. The draft/RFC is a little
// ambiguous on this point.
//
DBG_if (ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing) TRACE( TL_A, TM_Fsm, ( "No rw when we sent one?" ) );
ClearFlags( &pVc->ulFlags, VCBF_Sequencing ); } else { ULONG ulNew;
if (*(pControl->pusRWindowSize) == 0) { // When peer sends a receive window of 0 it means he needs
// sequencing to do out of order handling but doesn't want to
// do flow control. (Why would anyone choose this?) We fake
// "no flow control" by setting a huge send window that should
// never be filled.
//
pVc->ulMaxSendWindow = 10000; } else { pVc->ulMaxSendWindow = *(pControl->pusRWindowSize); }
// Set the initial send window to 1/2 the maximum, to "slow start"
// in case the networks congested. If it's not the window will
// quickly adapt to the maximum.
//
ulNew = pVc->ulMaxSendWindow >> 1; pVc->ulSendWindow = max( ulNew, 1 ); }
// Initialize the round trip time to the packet processing delay, if
// any, per the draft/RFC. The PPD is in 1/10ths of seconds.
//
if (pControl->pusPacketProcDelay) { pVc->ulRoundTripMs = ((ULONG )*(pControl->pusPacketProcDelay)) * 100; } else if (pVc->ulRoundTripMs == 0) { pVc->ulRoundTripMs = pVc->pAdapter->ulInitialSendTimeoutMs; }
// Call is up.
//
CallTransitionComplete( pTunnel, pVc, CS_Established ); } else if (*(pControl->pusMsgType) == CMT_CDN) { // Call is down.
//
pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_NORMAL; CallTransitionComplete( pTunnel, pVc, CS_Idle ); } }
VOID FsmInCallWaitReply( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Incoming call creation FSM WaitReply state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter;
pAdapter = pVc->pAdapter;
if (*(pControl->pusMsgType) == CMT_ICRP) { pVc->pMakeCall->Flags |= CALL_PARAMETERS_CHANGED;
if (pControl->pusAssignedCallId && *(pControl->pusAssignedCallId) > 0) { pVc->usAssignedCallId = *(pControl->pusAssignedCallId); } else { ASSERT( !"No assigned CID?" ); WPLOG( LL_A, LM_Fsm, ( "*** pVc = %p, no assigned CID", pVc ) ); ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_GeneralWithError, (ULONG_PTR )GERR_BadCallId, 0, 0, FALSE, FALSE ); return; }
// Use the queried media speed to set the connect speed
//
pVc->ulConnectBps = pTunnel->ulMediaSpeed;
if (pControl->pusRWindowSize) { ULONG ulNew;
SetFlags( &pVc->ulFlags, VCBF_Sequencing );
if (*(pControl->pusRWindowSize) == 0) { // When peer sends a receive window of 0 it means he needs
// sequencing to do out of order handling but doesn't want to
// do flow control. (Why would anyone choose this?) We fake
// "no flow control" by setting a huge send window that should
// never be filled.
//
pVc->ulMaxSendWindow = 10000; } else { pVc->ulMaxSendWindow = (ULONG )*(pControl->pusRWindowSize); }
// Set the initial send window to 1/2 the maximum, to "slow start"
// in case the networks congested. If it's not the window will
// quickly adapt to the maximum.
//
ulNew = pVc->ulMaxSendWindow >> 1; pVc->ulSendWindow = max( ulNew, 1 ); }
// Initialize the round trip time to the packet processing delay, if
// any, per the draft/RFC. The PPD is in 1/10ths of seconds. If it's
// not here, it might show up in the InCallConn.
//
if (pControl->pusPacketProcDelay) { pVc->ulRoundTripMs = ((ULONG )*(pControl->pusPacketProcDelay)) * 100; }
// Send InCallConn and the call is up.
//
SendControl( pTunnel, pVc, CMT_ICCN, 0, 0, NULL, 0 ); CallTransitionComplete( pTunnel, pVc, CS_Established );
} else if (*(pControl->pusMsgType) == CMT_CDN) { USHORT usResult; USHORT usError;
if (pControl->pusResult) { usResult = *(pControl->pusResult); if(pControl->pusError) { usError = *(pControl->pusError); } else { usError = GERR_None; } } else { usResult = CRESULT_GeneralWithError; usError = GERR_BadValue; }
// Map the result/error to a TAPI disconnect code.
//
pVc->status = StatusFromResultAndError( usResult, usError );
// Call is down.
//
CallTransitionComplete( pTunnel, pVc, CS_Idle ); } }
VOID FsmOutCallBearerAnswer( IN TUNNELCB* pTunnel, IN VCCB* pVc )
// The bearer WAN media has answered the call initiated by an outgoing
// call request from peer. 'PVc' is the VC control block associated with
// the outgoing call.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter;
ASSERT( pVc->state == CS_WaitCsAnswer );
pAdapter = pVc->pAdapter;
// Send OutCallConn, and the call is up.
//
SendControl( pTunnel, pVc, CMT_OCCN, 0, 0, NULL, 0 ); CallTransitionComplete( pTunnel, pVc, CS_Established ); }
VOID FsmOutCallEstablished( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Outgoing call creation FSM Established state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ if (*(pControl->pusMsgType) == CMT_CDN) { // Call is down.
//
pVc->status = NDIS_STATUS_TAPI_DISCONNECTMODE_NORMAL; CallTransitionComplete( pTunnel, pVc, CS_Idle ); } }
VOID FsmOutCallIdle( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Outgoing call creation FSM Idle state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter;
pAdapter = pVc->pAdapter;
if (*(pControl->pusMsgType) == CMT_OCRQ) { if (!(ReadFlags( &pVc->ulFlags ) & VCBF_PeerOpenPending)) { // If no open is pending, the call and/or owning tunnel has been
// slammed, we are in the clean up phase, and no response should
// be made.
//
TRACE( TL_A, TM_Fsm, ( "OC aborted" ) ); WPLOG( LL_A, LM_Fsm, ( "OCRQ received & we're in the cleanup phase" ) ); return; }
if (pControl->pusAssignedCallId) { pVc->usAssignedCallId = *(pControl->pusAssignedCallId); }
if (pVc->usResult) { // Call is down.
//
pVc->status = StatusFromResultAndError( pVc->usResult, pVc->usError );
pVc->state = CS_Idle; SendControl( pTunnel, pVc, CMT_CDN, (ULONG )pVc->usResult, (ULONG )pVc->usError, NULL, CSF_CallIdleOnAck ); } else { // Stash the call serial number.
//
if (pControl->pulCallSerialNumber) { pVc->pLcParams->ulCallSerialNumber = *(pControl->pulCallSerialNumber); } else { pVc->pLcParams->ulCallSerialNumber = 0; }
// The minimum and maximum rates acceptable to peer must be
// dropped on the floor here and the TAPI structures for incoming
// calls do not have a way to report such information.
//
// Calculate the connect bps to report to NDISWAN and to peer.
// Since we have no WAN link and no real way to figure the link
// speed, it's just a guesstimate of the LAN speed or the maximum
// acceptable to peer, whichever is smaller.
//
if (pControl->pulMaximumBps) { pVc->ulConnectBps = (ULONG )*(pControl->pulMaximumBps); } if (pVc->ulConnectBps > pTunnel->ulMediaSpeed) { pVc->ulConnectBps = pTunnel->ulMediaSpeed; }
// Stash the requested bearer types.
//
pVc->pTcInfo->ulMediaMode = 0; if (pControl->pulBearerType) { if (*(pControl->pulBearerType) & BBM_Analog) { pVc->pTcInfo->ulMediaMode |= LINEMEDIAMODE_DATAMODEM; }
if (*(pControl->pulBearerType) & BBM_Digital) { pVc->pTcInfo->ulMediaMode |= LINEMEDIAMODE_DIGITALDATA; } }
// Stash the maximum send window.
//
if (pControl->pusRWindowSize) { SetFlags( &pVc->ulFlags, VCBF_Sequencing );
if (*(pControl->pusRWindowSize) == 0) { // When peer sends a receive window of 0 it means he needs
// sequencing to do out of order handling but doesn't want
// to do flow control. (Why would anyone choose this?) We
// fake "no flow control" by setting a huge send window
// that should never be filled.
//
pVc->ulMaxSendWindow = 10000; } else { pVc->ulMaxSendWindow = (ULONG )*(pControl->pusRWindowSize); } }
// Initialize the round trip time to the packet processing delay,
// if any, per the draft/RFC. The PPD is in 1/10ths of seconds.
//
if (pControl->pusPacketProcDelay) { pVc->ulRoundTripMs = ((ULONG )*(pControl->pusPacketProcDelay)) * 100; } else { pVc->ulRoundTripMs = pAdapter->ulInitialSendTimeoutMs; }
// Note: The phone numbers of the caller and callee as well as the
// Subaddress are available at this point. Currently, the
// CallerID field of the TAPI structures is used for the IP
// address of the other end of the tunnel, which is used above for
// the IPSEC filters. The WAN caller information may also be
// useful but there is no obvious way to return both the WAN and
// tunnel endpoints in the current TAPI structures.
// Store the IP address of the peer.
pVc->state = CS_WaitCsAnswer; SendControl( pTunnel, pVc, CMT_OCRP, 0, 0, NULL, 0 );
// For now, with only "null" WAN call handoff supported, the
// bearer answer event is also generated here.
//
FsmOutCallBearerAnswer( pTunnel, pVc ); } } }
VOID FsmOutCallWaitReply( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Outgoing call creation FSM WaitReply state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ if (*(pControl->pusMsgType) == CMT_OCRP) { pVc->pMakeCall->Flags |= CALL_PARAMETERS_CHANGED;
// Stash the assigned Call-ID.
//
if (pControl->pusAssignedCallId && *(pControl->pusAssignedCallId) > 0) { pVc->usAssignedCallId = *(pControl->pusAssignedCallId); } else { // Peer ignored a MUST we can't cover up, by not sending a Call-ID
// for call control and payload traffic headed his way.
//
ASSERT( !"No assigned CID?" ); ScheduleTunnelWork( pTunnel, NULL, FsmCloseTunnel, (ULONG_PTR )TRESULT_GeneralWithError, (ULONG_PTR )GERR_None, 0, 0, FALSE, FALSE ); return; }
// Stash the physical channel ID.
//
if (pControl->pulPhysicalChannelId) { pVc->pLcParams->ulPhysicalChannelId = *(pControl->pulPhysicalChannelId); } else { pVc->pLcParams->ulPhysicalChannelId = 0xFFFFFFFF; }
pVc->state = CS_WaitConnect; } else if (*(pControl->pusMsgType) == CMT_CDN) { USHORT usResult; USHORT usError;
if (pControl->pusResult) { usResult = *(pControl->pusResult); if(pControl->pusError) { usError = *(pControl->pusError); } else { usError = GERR_None; } } else { usResult = CRESULT_GeneralWithError; usError = GERR_BadValue; }
// Map the result/error to a TAPI disconnect code.
//
pVc->status = StatusFromResultAndError( usResult, usError );
// Call is down.
//
CallTransitionComplete( pTunnel, pVc, CS_Idle ); } }
VOID FsmOutCallWaitConnect( IN TUNNELCB* pTunnel, IN VCCB* pVc, IN CONTROLMSGINFO* pControl )
// Outgoing call creation FSM WaitConnect state processing for VC 'pVc'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pVc->lockV' and 'pTunnel->lockT'.
//
{ if (*(pControl->pusMsgType) == CMT_OCCN) { // Stash the connect BPS.
//
if (pControl->pulTxConnectSpeed) { pVc->ulConnectBps = *(pControl->pulTxConnectSpeed); } else { // Not supposed to happen, but try to go on with a least common
// denominator if it does.
//
pVc->ulConnectBps = 9600; }
DBG_if (pControl->pulFramingType && !(*(pControl->pulFramingType) & FBM_Sync)) { // Should not happen since we said in our request we only want
// synchronous framing. If it does, go on in the hope that this
// AVP is what peer got wrong and not the framing itself.
//
ASSERT( "No sync framing?" ); }
// Stash the maximum send window.
//
if (!pControl->pusRWindowSize) { // Peer did not send a receive window AVP so we're not doing Ns/Nr
// flow control on the session. If we requested sequencing peer
// is really supposed to send his window, but if he doesn't assume
// that means he wants no sequencing. The draft/RFC is a little
// ambiguous on this point.
//
DBG_if (ReadFlags( &pVc->ulFlags ) & VCBF_Sequencing) TRACE( TL_A, TM_Fsm, ( "No rw when we sent one?" ) );
ClearFlags( &pVc->ulFlags, VCBF_Sequencing ); } else { ULONG ulNew;
if (*(pControl->pusRWindowSize) == 0) { // When peer sends a receive window of 0 it means he needs
// sequencing to do out of order handling but doesn't want to
// do flow control. (Why would anyone choose this?) We fake
// "no flow control" by setting a huge send window that should
// never be filled.
//
pVc->ulMaxSendWindow = 10000; } else { pVc->ulMaxSendWindow = *(pControl->pusRWindowSize); }
// Set the initial send window to 1/2 the maximum, to "slow start"
// in case the networks congested. If it's not the window will
// quickly adapt to the maximum.
//
ulNew = pVc->ulMaxSendWindow << 1; pVc->ulSendWindow = max( ulNew, 1 ); }
// Initialize the round trip time to the packet processing delay, if
// any, per the draft/RFC. The PPD is in 1/10ths of seconds.
//
if (pControl->pusPacketProcDelay) { pVc->ulRoundTripMs = ((ULONG )*(pControl->pusPacketProcDelay)) * 100; } else { pVc->ulRoundTripMs = pVc->pAdapter->ulInitialSendTimeoutMs; }
// Call is up.
//
CallTransitionComplete( pTunnel, pVc, CS_Established ); } else if (*(pControl->pusMsgType) == CMT_CDN) { USHORT usResult; USHORT usError;
if (pControl->pusResult) { usResult = *(pControl->pusResult); if(pControl->pusError) { usError = *(pControl->pusError); } else { usError = GERR_None; } } else { usResult = CRESULT_GeneralWithError; usError = GERR_BadValue; }
// Map the result/error to a TAPI disconnect code.
//
pVc->status = StatusFromResultAndError( usResult, usError );
// Call is down.
//
CallTransitionComplete( pTunnel, pVc, CS_Idle ); } }
VOID FsmTunnelEstablished( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl )
// Tunnel creation FSM Established state processing for tunnel 'pTunnel'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter;
pAdapter = pTunnel->pAdapter;
if (*(pControl->pusMsgType) == CMT_StopCCN) { // Peer taking tunnel down.
//
TunnelTransitionComplete( pTunnel, CCS_Idle ); } }
VOID FsmTunnelIdle( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl )
// Tunnel creation FSM Idle state processing for tunnel 'pTunnel'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; USHORT usResult; USHORT usError;
pAdapter = pTunnel->pAdapter;
if (*(pControl->pusMsgType) == CMT_SCCRQ) { SetFlags( &pTunnel->ulFlags, (TCBF_PeerInitiated | TCBF_PeerInitRef) );
if (ReferenceSap( pAdapter )) { // A SAP is active. Because SAPs can be deregistered without
// closing active incoming tunnels, we need a reference on the
// open TDI context for the tunnel. We call TdixReference rather
// than TdixOpen, because with TDI guaranteed to be open the
// effect is the same and TdixReference can be called at DISPATCH
// IRQL while TdixOpen cannot. The reference on the SAP is then
// removed since we don't want the tunnel to prevent the SAP from
// being deregistered.
//
TdixReference( &pAdapter->tdix ); SetFlags( &pTunnel->ulFlags, TCBF_TdixReferenced ); DereferenceSap( pAdapter ); } else { // No SAP is active. The only reason peer's request got this far
// is that an outgoing call or just-deregistered-SAP had TDI open.
// Discard it as if TDI had not been open.
//
TRACE( TL_I, TM_Fsm, ( "No active SAP" ) ); TunnelTransitionComplete( pTunnel, CCS_Idle ); return; }
GetCcAvps( pTunnel, pControl, &usResult, &usError ); if (usResult) { // The tunnel is down, but must hold it and any VCs until the
// closing exchange is acknowledged.
//
SendControl( pTunnel, NULL, CMT_StopCCN, (ULONG )usResult, (ULONG )usError, NULL, CSF_TunnelIdleOnAck ); } else { // Tunnel creation successfully underway. Flip the flag that
// tells MakeCall to queue requesting VCs on the result.
//
SetFlags( &pTunnel->ulFlags, TCBF_CcInTransition );
if (pControl->pchChallenge) { CHAR* pszPassword;
// Challenge received. Calculate the response value, based on
// the password from the registry.
//
pAdapter = pTunnel->pAdapter; if (pAdapter->pszPassword) { pszPassword = pAdapter->pszPassword; } else { pszPassword = ""; }
CalculateResponse( pControl->pchChallenge, (ULONG )pControl->usChallengeLength, pszPassword, CMT_SCCRP, pTunnel->achResponseToSend ); }
pTunnel->state = CCS_WaitCtlConnect; SendControl( pTunnel, NULL, CMT_SCCRP, (pControl->pchChallenge != NULL), 0, NULL, 0 ); } } }
VOID FsmTunnelWaitCtlConnect( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl )
// Tunnel creation FSM WaitCtlConnect state processing for tunnel
// 'pTunnel'. 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ ADAPTERCB* pAdapter;
pAdapter = pTunnel->pAdapter;
if (*(pControl->pusMsgType) == CMT_SCCCN) { USHORT usResult;
usResult = 0; if (pAdapter->pszPassword) { // We sent a challenge.
//
if (pControl->pchResponse) { CHAR achResponseExpected[ 16 ]; ULONG i;
// Challenge response received. Calculate the expected
// response and compare to that received.
//
CalculateResponse( pTunnel->achChallengeToSend, sizeof(pTunnel->achChallengeToSend), pAdapter->pszPassword, CMT_SCCCN, achResponseExpected );
for (i = 0; i < 16; ++i) { if (achResponseExpected[ i ] != pControl->pchResponse[ i ]) { break; } }
if (i < 16) { TRACE( TL_N, TM_Fsm, ( "Wrong challenge response" ) ); usResult = TRESULT_NotAuthorized; } } else { // We sent a challenge and got no challenge response.
//
//
TRACE( TL_N, TM_Fsm, ( "No challenge response" ) ); usResult = TRESULT_FsmError; } }
if (usResult) { // Tunnel going down.
//
pTunnel->state = CCS_Idle; SendControl( pTunnel, NULL, CMT_StopCCN, (ULONG )usResult, 0, NULL, CSF_TunnelIdleOnAck ); } else { // Tunnel is up.
//
TunnelTransitionComplete( pTunnel, CCS_Established ); } } else if (*(pControl->pusMsgType) == CMT_StopCCN) { // Peer taking tunnel down.
//
TunnelTransitionComplete( pTunnel, CCS_Idle ); } }
VOID FsmTunnelWaitCtlReply( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl )
// Tunnel creation FSM WaitCtlReply state processing for tunnel 'pTunnel'.
// 'PControl' is the exploded control message information.
//
// IMPORTANT: Caller must hold 'pTunnel->lockT'.
//
{ NDIS_STATUS status; ADAPTERCB* pAdapter; USHORT usResult; USHORT usError;
pAdapter = pTunnel->pAdapter;
if (*(pControl->pusMsgType) == CMT_SCCRP) { GetCcAvps( pTunnel, pControl, &usResult, &usError );
if (pAdapter->pszPassword) { // We sent a challenge.
//
if (pControl->pchResponse) { CHAR achResponseExpected[ 16 ]; ULONG i;
// Challenge response received. Calculate the expected
// response and compare to that received.
//
CalculateResponse( pTunnel->achChallengeToSend, sizeof(pTunnel->achChallengeToSend), pAdapter->pszPassword, CMT_SCCRP, achResponseExpected );
for (i = 0; i < 16; ++i) { if (achResponseExpected[ i ] != pControl->pchResponse[ i ]) { break; } }
if (i < 16) { TRACE( TL_N, TM_Fsm, ( "Wrong challenge response" ) ); usResult = TRESULT_General; } } else { // We sent a challenge and got no challenge response. Treat
// this as if a bad response was received.
//
TRACE( TL_N, TM_Fsm, ( "No challenge response" ) ); usResult = TRESULT_General; } }
if (usResult) { // Tunnel creation failed, so shut down.
//
pTunnel->state = CCS_Idle; SendControl( pTunnel, NULL, CMT_StopCCN, (ULONG )usResult, (ULONG )usError, NULL, CSF_TunnelIdleOnAck ); } else { if (pControl->pchChallenge) { CHAR* pszPassword;
// Challenge received. Calculate the response value, based on
// the password from the registry.
//
pAdapter = pTunnel->pAdapter; if (pAdapter->pszPassword) pszPassword = pAdapter->pszPassword; else pszPassword = "";
CalculateResponse( pControl->pchChallenge, (ULONG )pControl->usChallengeLength, pszPassword, CMT_SCCCN, pTunnel->achResponseToSend ); }
// Tunnel is up.
//
SendControl( pTunnel, NULL, CMT_SCCCN, (pControl->pchChallenge != NULL), 0, NULL, CSF_QueryMediaSpeed); TunnelTransitionComplete( pTunnel, CCS_Established ); } } else if (*(pControl->pusMsgType) == CMT_StopCCN) { // Peer taking tunnel down.
//
TunnelTransitionComplete( pTunnel, CCS_Idle ); } }
VOID GetCcAvps( IN TUNNELCB* pTunnel, IN CONTROLMSGINFO* pControl, OUT USHORT* pusResult, OUT USHORT* pusError )
// Retrieve and interpret control connection AVPs received in the SCCRQ or
// SCCRP message in 'pControl', returning the result and error codes for
// the response in '*pusResult' and '*pusError'. 'PTunnel' is the tunnel
// control block.
//
{ ULONG ulNew;
*pusResult = 0; *pusError = GERR_None;
if (!pControl->pusProtocolVersion || *(pControl->pusProtocolVersion) != L2TP_ProtocolVersion) { // Peer wants to do a version of L2TP that doesn't match the only
// one we understand.
//
TRACE( TL_A, TM_Recv, ( "Bad protocol version?" ) ); WPLOG( LL_A, LM_Recv, ( "Bad protocol version?" ) ); *pusResult = TRESULT_BadProtocolVersion; return; }
// Make sure the MUST fields are really there and have valid values, then
// store them in our control blocks.
//
if (!pControl->pusAssignedTunnelId || *(pControl->pusAssignedTunnelId) == 0 || !pControl->pulFramingCaps) { TRACE( TL_A, TM_Recv, ( "Missing MUSTs?" ) ); WPLOG( LL_A, LM_Recv, ( "Missing MUSTs?" ) ); *pusResult = TRESULT_GeneralWithError; *pusError = GERR_BadValue; return; }
pTunnel->usAssignedTunnelId = *(pControl->pusAssignedTunnelId); pTunnel->ulFramingCaps = *(pControl->pulFramingCaps);
if (pControl->pulBearerCaps) { pTunnel->ulBearerCaps = *(pControl->pulBearerCaps); } else { pTunnel->ulBearerCaps = 0; }
if (pControl->pusRWindowSize && *(pControl->pusRWindowSize)) { // Peer provided his receive window, which becomes our send window.
//
pTunnel->ulMaxSendWindow = (ULONG )*(pControl->pusRWindowSize); } else { // Peer provided no receive window, so use the default of 4 per the
// draft/RFC.
//
pTunnel->ulMaxSendWindow = L2TP_DefaultReceiveWindow; }
// Set the initial send window to 1/2 the maximum, to "slow start" in case
// the network is congested. If it's not the window will quickly adapt to
// the maximum.
//
ulNew = pTunnel->ulMaxSendWindow >> 1; pTunnel->ulSendWindow = max( ulNew, 1 ); }
ULONG StatusFromResultAndError( IN USHORT usResult, IN USHORT usError )
// Map non-success L2TP result/error codes to a best-fit TAPI
// NDIS_STATUS_TAPI_DISCONNECT_* code.
//
{ ULONG ulResult;
switch (usResult) { case CRESULT_GeneralWithError: { switch (usError) { case GERR_TryAnother: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_BUSY; break; }
case GERR_BadValue: case GERR_BadLength: case GERR_NoControlConnection: case GERR_NoResources: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_UNAVAIL; break; }
default: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_REJECT; break; } } break; }
case CRESULT_Busy: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_BUSY; break; }
case CRESULT_NoCarrier: case CRESULT_NoDialTone: case CRESULT_Timeout: case CRESULT_NoFacilitiesTemporary: case CRESULT_NoFacilitiesPermanent: case CRESULT_Administrative: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_UNAVAIL; break; }
case CRESULT_NoFraming: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_INCOMPATIBLE; break; }
case CRESULT_InvalidDestination: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_BADADDRESS; break; }
case CRESULT_LostCarrier: default: { ulResult = NDIS_STATUS_TAPI_DISCONNECTMODE_CONGESTION; break; } }
return ulResult; }
|