Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2171 lines
67 KiB

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