|
|
// -*- mode: C++; tab-width: 4; indent-tabs-mode: nil -*- (for GNU Emacs)
//
// Copyright (c) 1985-2000 Microsoft Corporation
//
// This file is part of the Microsoft Research IPv6 Network Protocol Stack.
// You should have received a copy of the Microsoft End-User License Agreement
// for this software along with this release; see the file "license.txt".
// If not, please see http://www.research.microsoft.com/msripv6/license.htm,
// or write to Microsoft Research, One Microsoft Way, Redmond, WA 98052-6399.
//
// Abstract:
//
// Code for TCP Control Block management.
//
#include "oscfg.h"
#include "ndis.h"
#include "ip6imp.h"
#include "ip6def.h"
#include "tdi.h"
#include "tdint.h"
#include "tdistat.h"
#include "queue.h"
#include "transprt.h"
#include "tcp.h"
#include "tcb.h"
#include "tcpconn.h"
#include "tcpsend.h"
#include "tcprcv.h"
#include "info.h"
#include "tcpcfg.h"
#include "tcpdeliv.h"
#include "route.h"
KSPIN_LOCK TCBTableLock;
uint TCPTime; uint TCBWalkCount;
TCB **TCBTable;
TCB *LastTCB;
TCB *PendingFreeList;
SLIST_HEADER FreeTCBList;
KSPIN_LOCK FreeTCBListLock; // Lock to protect TCB free list.
extern KSPIN_LOCK AddrObjTableLock;
extern SeqNum ISNMonotonicPortion; extern int ISNCredits; extern int ISNMaxCredits; extern uint GetDeltaTime();
uint CurrentTCBs = 0; uint FreeTCBs = 0;
uint MaxTCBs = 0xffffffff;
#define MAX_FREE_TCBS 1000
#define NUM_DEADMAN_TICKS MS_TO_TICKS(1000)
uint MaxFreeTCBs = MAX_FREE_TCBS; uint DeadmanTicks;
KTIMER TCBTimer; KDPC TCBTimeoutDpc;
//
// All of the init code can be discarded.
//
#ifdef ALLOC_PRAGMA
int InitTCB(void);
#pragma alloc_text(INIT, InitTCB)
#endif // ALLOC_PRAGMA
//* ReadNextTCB - Read the next TCB in the table.
//
// Called to read the next TCB in the table. The needed information
// is derived from the incoming context, which is assumed to be valid.
// We'll copy the information, and then update the context value with
// the next TCB to be read.
//
uint // Returns: TRUE if more data is available to be read, FALSE is not.
ReadNextTCB( void *Context, // Pointer to a TCPConnContext.
void *Buffer) // Pointer to a TCPConnTableEntry structure.
{ TCPConnContext *TCContext = (TCPConnContext *)Context; TCP6ConnTableEntry *TCEntry = (TCP6ConnTableEntry *)Buffer; KIRQL OldIrql; TCB *CurrentTCB; uint i;
CurrentTCB = TCContext->tcc_tcb; CHECK_STRUCT(CurrentTCB, tcb);
KeAcquireSpinLock(&CurrentTCB->tcb_lock, &OldIrql); if (CLOSING(CurrentTCB)) TCEntry->tct_state = TCP_CONN_CLOSED; else TCEntry->tct_state = (uint)CurrentTCB->tcb_state + TCB_STATE_DELTA; TCEntry->tct_localaddr = CurrentTCB->tcb_saddr; TCEntry->tct_localscopeid = CurrentTCB->tcb_sscope_id; TCEntry->tct_localport = CurrentTCB->tcb_sport; TCEntry->tct_remoteaddr = CurrentTCB->tcb_daddr; TCEntry->tct_remotescopeid = CurrentTCB->tcb_dscope_id; TCEntry->tct_remoteport = CurrentTCB->tcb_dport; TCEntry->tct_owningpid = (CurrentTCB->tcb_conn) ? CurrentTCB->tcb_conn->tc_owningpid : 0; KeReleaseSpinLock(&CurrentTCB->tcb_lock, OldIrql);
// We've filled it in. Now update the context.
if (CurrentTCB->tcb_next != NULL) { TCContext->tcc_tcb = CurrentTCB->tcb_next; return TRUE; } else { // NextTCB is NULL. Loop through the TCBTable looking for a new one.
i = TCContext->tcc_index + 1; while (i < TcbTableSize) { if (TCBTable[i] != NULL) { TCContext->tcc_tcb = TCBTable[i]; TCContext->tcc_index = i; return TRUE; break; } else i++; }
TCContext->tcc_index = 0; TCContext->tcc_tcb = NULL; return FALSE; } }
//* ValidateTCBContext - Validate the context for reading a TCB table.
//
// Called to start reading the TCB table sequentially. We take in
// a context, and if the values are 0 we return information about the
// first TCB in the table. Otherwise we make sure that the context value
// is valid, and if it is we return TRUE.
// We assume the caller holds the TCB table lock.
//
// Upon return, *Valid is set to true if the context is valid.
//
uint // Returns: TRUE if data in table, FALSE if not.
ValidateTCBContext( void *Context, // Pointer to a TCPConnContext.
uint *Valid) // Where to return infoformation about context being valid.
{ TCPConnContext *TCContext = (TCPConnContext *)Context; uint i; TCB *TargetTCB; TCB *CurrentTCB;
i = TCContext->tcc_index; TargetTCB = TCContext->tcc_tcb;
//
// If the context values are 0 and NULL, we're starting from the beginning.
//
if (i == 0 && TargetTCB == NULL) { *Valid = TRUE; do { if ((CurrentTCB = TCBTable[i]) != NULL) { CHECK_STRUCT(CurrentTCB, tcb); break; } i++; } while (i < TcbTableSize);
if (CurrentTCB != NULL) { TCContext->tcc_index = i; TCContext->tcc_tcb = CurrentTCB; return TRUE; } else return FALSE;
} else { //
// We've been given a context. We just need to make sure that it's
// valid.
//
if (i < TcbTableSize) { CurrentTCB = TCBTable[i]; while (CurrentTCB != NULL) { if (CurrentTCB == TargetTCB) { *Valid = TRUE; return TRUE; break; } else { CurrentTCB = CurrentTCB->tcb_next; } }
}
// If we get here, we didn't find the matching TCB.
*Valid = FALSE; return FALSE; } }
//* FindNextTCB - Find the next TCB in a particular chain.
//
// This routine is used to find the 'next' TCB in a chain. Since we keep
// the chain in ascending order, we look for a TCB which is greater than
// the input TCB. When we find one, we return it.
//
// This routine is mostly used when someone is walking the table and needs
// to free the various locks to perform some action.
//
TCB * // Returns: Pointer to the next TCB, or NULL.
FindNextTCB( uint Index, // Index into TCBTable.
TCB *Current) // Current TCB - we find the one after this one.
{ TCB *Next;
ASSERT(Index < TcbTableSize);
Next = TCBTable[Index];
while (Next != NULL && (Next <= Current)) Next = Next->tcb_next;
return Next; }
//* ResetSendNext - Set the sendnext value of a TCB.
//
// Called to set the send next value of a TCB. We do that, and adjust all
// pointers to the appropriate places. We assume the caller holds the lock
// on the TCB.
//
void // Returns: Nothing.
ResetSendNext( TCB *SeqTCB, // TCB to be updated.
SeqNum NewSeq) // Sequence number to set.
{ TCPSendReq *SendReq; uint AmtForward; Queue *CurQ; PNDIS_BUFFER Buffer; uint Offset;
CHECK_STRUCT(SeqTCB, tcb); ASSERT(SEQ_GTE(NewSeq, SeqTCB->tcb_senduna));
//
// The new seq must be less than send max, or NewSeq, senduna, sendnext,
// and sendmax must all be equal (the latter case happens when we're
// called exiting TIME_WAIT, or possibly when we're retransmitting
// during a flow controlled situation).
//
ASSERT(SEQ_LT(NewSeq, SeqTCB->tcb_sendmax) || (SEQ_EQ(SeqTCB->tcb_senduna, SeqTCB->tcb_sendnext) && SEQ_EQ(SeqTCB->tcb_senduna, SeqTCB->tcb_sendmax) && SEQ_EQ(SeqTCB->tcb_senduna, NewSeq)));
AmtForward = NewSeq - SeqTCB->tcb_senduna;
if ((AmtForward == 1) && (SeqTCB->tcb_flags & FIN_SENT) && !((SeqTCB->tcb_sendnext - SeqTCB->tcb_senduna) > 1) && (SEQ_EQ(SeqTCB->tcb_sendnext,SeqTCB->tcb_sendmax))) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE, "tcpip6: trying to set sendnext for FIN_SENT\n")); SeqTCB->tcb_sendnext = NewSeq; SeqTCB->tcb_flags &= ~FIN_OUTSTANDING; return; } if((SeqTCB->tcb_flags & FIN_SENT) && (SEQ_EQ(SeqTCB->tcb_sendnext,SeqTCB->tcb_sendmax)) && ((SeqTCB->tcb_sendnext - NewSeq) == 1) ){
//
// There is only FIN that is left beyond sendnext.
//
SeqTCB->tcb_sendnext = NewSeq; SeqTCB->tcb_flags &= ~FIN_OUTSTANDING; return; }
SeqTCB->tcb_sendnext = NewSeq;
//
// If we're backing off send next, turn off the FIN_OUTSTANDING flag to
// maintain a consistent state.
//
if (!SEQ_EQ(NewSeq, SeqTCB->tcb_sendmax)) SeqTCB->tcb_flags &= ~FIN_OUTSTANDING;
if (SYNC_STATE(SeqTCB->tcb_state) && SeqTCB->tcb_state != TCB_TIME_WAIT) { //
// In these states we need to update the send queue.
//
if (!EMPTYQ(&SeqTCB->tcb_sendq)) { CurQ = QHEAD(&SeqTCB->tcb_sendq);
SendReq = (TCPSendReq *)CONTAINING_RECORD(CurQ, TCPReq, tr_q);
//
// SendReq points to the first send request on the send queue.
// Move forward AmtForward bytes on the send queue, and set the
// TCB pointers to the resultant SendReq, buffer, offset, size.
//
while (AmtForward) {
CHECK_STRUCT(SendReq, tsr);
if (AmtForward >= SendReq->tsr_unasize) { //
// We're going to move completely past this one. Subtract
// his size from AmtForward and get the next one.
//
AmtForward -= SendReq->tsr_unasize; CurQ = QNEXT(CurQ); ASSERT(CurQ != QEND(&SeqTCB->tcb_sendq)); SendReq = (TCPSendReq *)CONTAINING_RECORD(CurQ, TCPReq, tr_q); } else { //
// We're pointing at the proper send req now. Break out
// of this loop and save the information. Further down
// we'll need to walk down the buffer chain to find
// the proper buffer and offset.
//
break; } }
//
// We're pointing at the proper send req now. We need to go down
// the buffer chain here to find the proper buffer and offset.
//
SeqTCB->tcb_cursend = SendReq; SeqTCB->tcb_sendsize = SendReq->tsr_unasize - AmtForward; Buffer = SendReq->tsr_buffer; Offset = SendReq->tsr_offset;
while (AmtForward) { // Walk the buffer chain.
uint Length;
//
// We'll need the length of this buffer. Use the portable
// macro to get it. We have to adjust the length by the offset
// into it, also.
//
ASSERT((Offset < NdisBufferLength(Buffer)) || ((Offset == 0) && (NdisBufferLength(Buffer) == 0)));
Length = NdisBufferLength(Buffer) - Offset;
if (AmtForward >= Length) { //
// We're moving past this one. Skip over him, and 0 the
// Offset we're keeping.
//
AmtForward -= Length; Offset = 0; Buffer = NDIS_BUFFER_LINKAGE(Buffer); ASSERT(Buffer != NULL); } else break; }
//
// Save the buffer we found, and the offset into that buffer.
//
SeqTCB->tcb_sendbuf = Buffer; SeqTCB->tcb_sendofs = Offset + AmtForward;
} else { ASSERT(SeqTCB->tcb_cursend == NULL); ASSERT(AmtForward == 0); } }
CheckTCBSends(SeqTCB); }
//* TCPAbortAndIndicateDisconnect
//
// Abortively closes a TCB and issues a disconnect indication up to the
// transport user. This function is used to support cancellation of
// TDI send and receive requests.
//
void // Returns: Nothing.
TCPAbortAndIndicateDisconnect( CONNECTION_CONTEXT ConnectionContext // Connection ID to find a TCB for.
) { TCB *AbortTCB; KIRQL Irql0, Irql1; // One per lock nesting level.
TCPConn *Conn;
Conn = GetConnFromConnID(PtrToUlong(ConnectionContext), &Irql0);
if (Conn != NULL) { CHECK_STRUCT(Conn, tc);
AbortTCB = Conn->tc_tcb;
if (AbortTCB != NULL) { //
// If it's CLOSING or CLOSED, skip it.
//
if ((AbortTCB->tcb_state != TCB_CLOSED) && !CLOSING(AbortTCB)) { CHECK_STRUCT(AbortTCB, tcb); KeAcquireSpinLock(&AbortTCB->tcb_lock, &Irql1); KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
if (AbortTCB->tcb_state == TCB_CLOSED || CLOSING(AbortTCB)) { KeReleaseSpinLock(&AbortTCB->tcb_lock, Irql0); return; }
AbortTCB->tcb_refcnt++; AbortTCB->tcb_flags |= NEED_RST; // send a reset if connected
TryToCloseTCB(AbortTCB, TCB_CLOSE_ABORTED, Irql0);
RemoveTCBFromConn(AbortTCB);
IF_TCPDBG(TCP_DEBUG_IRP) { KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_TCPDBG, "TCPAbortAndIndicateDisconnect, indicating discon\n")); }
NotifyOfDisc(AbortTCB, TDI_CONNECTION_ABORTED, NULL);
KeAcquireSpinLock(&AbortTCB->tcb_lock, &Irql0); DerefTCB(AbortTCB, Irql0);
// TCB lock freed by DerefTCB.
return; } else KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0); } else KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0); } }
//* TCBTimeout - Do timeout events on TCBs.
//
// Called every MS_PER_TICKS milliseconds to do timeout processing on TCBs.
// We run throught the TCB table, decrementing timers. If one goes to zero
// we look at its state to decide what to do.
//
void // Returns: Nothing.
TCBTimeout( PKDPC MyDpcObject, // The DPC object describing this routine.
void *Context, // The argument we asked to be called with.
void *Unused1, void *Unused2) { uint i; TCB *CurrentTCB; uint Delayed = FALSE; uint CallRcvComplete; int Delta;
UNREFERENCED_PARAMETER(MyDpcObject); UNREFERENCED_PARAMETER(Context); UNREFERENCED_PARAMETER(Unused1); UNREFERENCED_PARAMETER(Unused2);
//
// Update our free running counter.
//
TCPTime++;
ExInterlockedAddUlong((PULONG)&TCBWalkCount, 1, &TCBTableLock);
//
// Set credits so that some more connections can increment the
// Initial Sequence Number, during the next 100 ms.
//
InterlockedExchange((PLONG)&ISNCredits, ISNMaxCredits);
Delta = GetDeltaTime();
//
// The increment made is (256)*(Time in milliseconds). This is really close
// to 25000 increment made originally every 100 ms.
//
if (Delta > 0) { Delta *= 0x100; InterlockedExchangeAdd((PLONG)&ISNMonotonicPortion, Delta); }
//
// Loop through each bucket in the table, going down the chain of
// TCBs on the bucket.
//
for (i = 0; i < TcbTableSize; i++) { TCB *TempTCB; uint maxRexmitCnt;
CurrentTCB = TCBTable[i];
while (CurrentTCB != NULL) { CHECK_STRUCT(CurrentTCB, tcb); KeAcquireSpinLockAtDpcLevel(&CurrentTCB->tcb_lock);
//
// If it's CLOSING or CLOSED, skip it.
//
if (CurrentTCB->tcb_state == TCB_CLOSED || CLOSING(CurrentTCB)) { TempTCB = CurrentTCB->tcb_next; KeReleaseSpinLockFromDpcLevel(&CurrentTCB->tcb_lock); CurrentTCB = TempTCB; continue; }
CheckTCBSends(CurrentTCB); CheckTCBRcv(CurrentTCB);
//
// First check the rexmit timer.
//
if (TCB_TIMER_RUNNING(CurrentTCB->tcb_rexmittimer)) { //
// The timer is running.
//
if (--(CurrentTCB->tcb_rexmittimer) == 0) { //
// And it's fired. Figure out what to do now.
//
if ((CurrentTCB->tcb_state == TCB_SYN_SENT) || (CurrentTCB->tcb_state == TCB_SYN_RCVD)) { maxRexmitCnt = MaxConnectRexmitCount; } else { maxRexmitCnt = MaxDataRexmitCount; }
//
// If we've run out of retransmits or we're in FIN_WAIT2,
// time out.
//
CurrentTCB->tcb_rexmitcnt++; if (CurrentTCB->tcb_rexmitcnt > maxRexmitCnt) {
ASSERT(CurrentTCB->tcb_state > TCB_LISTEN);
//
// This connection has timed out. Abort it. First
// reference him, then mark as closed, notify the
// user, and finally dereference and close him.
//
TimeoutTCB: CurrentTCB->tcb_refcnt++; TryToCloseTCB(CurrentTCB, TCB_CLOSE_TIMEOUT, DISPATCH_LEVEL);
RemoveTCBFromConn(CurrentTCB); NotifyOfDisc(CurrentTCB, TDI_TIMED_OUT, NULL);
KeAcquireSpinLockAtDpcLevel(&CurrentTCB->tcb_lock); DerefTCB(CurrentTCB, DISPATCH_LEVEL);
CurrentTCB = FindNextTCB(i, CurrentTCB); continue; }
//
// Stop round trip time measurement.
//
CurrentTCB->tcb_rtt = 0;
//
// Figure out what our new retransmit timeout should be.
// We double it each time we get a retransmit, and reset it
// back when we get an ack for new data.
//
CurrentTCB->tcb_rexmit = MIN(CurrentTCB->tcb_rexmit << 1, MAX_REXMIT_TO);
//
// Reset the sequence number, and reset the congestion
// window.
//
ResetSendNext(CurrentTCB, CurrentTCB->tcb_senduna);
if (!(CurrentTCB->tcb_flags & FLOW_CNTLD)) { //
// Don't let the slow start threshold go below 2
// segments.
//
CurrentTCB->tcb_ssthresh = MAX(MIN(CurrentTCB->tcb_cwin, CurrentTCB->tcb_sendwin) / 2, (uint) CurrentTCB->tcb_mss * 2); CurrentTCB->tcb_cwin = CurrentTCB->tcb_mss; } else { //
// We're probing, and the probe timer has fired. We
// need to set the FORCE_OUTPUT bit here.
//
CurrentTCB->tcb_flags |= FORCE_OUTPUT; }
//
// See if we need to probe for a PMTU black hole.
//
if (PMTUBHDetect && CurrentTCB->tcb_rexmitcnt == ((maxRexmitCnt+1)/2)) { //
// We may need to probe for a black hole. If we're
// doing MTU discovery on this connection and we
// are retransmitting more than a minimum segment
// size, or we are probing for a PMTU BH already,
// bump the probe count. If the probe count gets
// too big we'll assume it's not a PMTU black hole,
// and we'll try to switch the router.
//
if ((CurrentTCB->tcb_flags & PMTU_BH_PROBE) || (CurrentTCB->tcb_sendmax - CurrentTCB->tcb_senduna > 8)) { //
// May need to probe. If we haven't exceeded our
// probe count, do so, otherwise restore those
// values.
//
if (CurrentTCB->tcb_bhprobecnt++ < 2) { //
// We're going to probe. Turn on the flag,
// drop the MSS, and turn off the don't
// fragment bit.
//
if (!(CurrentTCB->tcb_flags & PMTU_BH_PROBE)) { CurrentTCB->tcb_flags |= PMTU_BH_PROBE; CurrentTCB->tcb_slowcount++; CurrentTCB->tcb_fastchk |= TCP_FLAG_SLOW; //
// Drop the MSS to the minimum.
//
CurrentTCB->tcb_mss = MIN(DEFAULT_MSS, CurrentTCB->tcb_remmss);
ASSERT(CurrentTCB->tcb_mss > 0);
CurrentTCB->tcb_cwin = CurrentTCB->tcb_mss; }
//
// Drop the rexmit count so we come here again.
//
CurrentTCB->tcb_rexmitcnt--; } else { //
// Too many probes. Stop probing, and allow
// fallover to the next gateway.
//
// Currently this code won't do BH probing on
// the 2nd gateway. The MSS will stay at the
// minimum size. This might be a little
// suboptimal, but it's easy to implement for
// the Sept. 95 service pack and will keep
// connections alive if possible.
//
// In the future we should investigate doing
// dead g/w detect on a per-connection basis,
// and then doing PMTU probing for each
// connection.
//
if (CurrentTCB->tcb_flags & PMTU_BH_PROBE) { CurrentTCB->tcb_flags &= ~PMTU_BH_PROBE; if (--(CurrentTCB->tcb_slowcount) == 0) CurrentTCB->tcb_fastchk &= ~TCP_FLAG_SLOW; } CurrentTCB->tcb_bhprobecnt = 0; } } }
//
// Now handle the various cases.
//
switch (CurrentTCB->tcb_state) {
case TCB_SYN_SENT: case TCB_SYN_RCVD: //
// In SYN-SENT or SYN-RCVD we'll need to retransmit
// the SYN.
//
SendSYN(CurrentTCB, DISPATCH_LEVEL); CurrentTCB = FindNextTCB(i, CurrentTCB); continue;
case TCB_FIN_WAIT1: case TCB_CLOSING: case TCB_LAST_ACK: //
// The call to ResetSendNext (above) will have
// turned off the FIN_OUTSTANDING flag.
//
CurrentTCB->tcb_flags |= FIN_NEEDED;
case TCB_CLOSE_WAIT: case TCB_ESTAB: //
// In this state we have data to retransmit, unless
// the window is zero (in which case we need to
// probe), or we're just sending a FIN.
//
CheckTCBSends(CurrentTCB);
//
// Since we're retransmitting, our first-hop router
// may be down. Tell IP we're suspicious if this
// is the first retransmit.
//
if (CurrentTCB->tcb_rexmitcnt == 1 && CurrentTCB->tcb_rce != NULL) { ForwardReachabilityInDoubt(CurrentTCB->tcb_rce); }
Delayed = TRUE; DelayAction(CurrentTCB, NEED_OUTPUT); break;
case TCB_TIME_WAIT: //
// If it's fired in TIME-WAIT, we're all done and
// can clean up. We'll call TryToCloseTCB even
// though he's already sort of closed. TryToCloseTCB
// will figure this out and do the right thing.
//
TryToCloseTCB(CurrentTCB, TCB_CLOSE_SUCCESS, DISPATCH_LEVEL); CurrentTCB = FindNextTCB(i, CurrentTCB); continue;
default: break; } } }
//
// Now check the SWS deadlock timer..
//
if (TCB_TIMER_RUNNING(CurrentTCB->tcb_swstimer)) { //
// The timer is running.
//
if (--(CurrentTCB->tcb_swstimer) == 0) { //
// And it's fired. Force output now.
//
CurrentTCB->tcb_flags |= FORCE_OUTPUT; Delayed = TRUE; DelayAction(CurrentTCB, NEED_OUTPUT); } }
//
// Check the push data timer.
//
if (TCB_TIMER_RUNNING(CurrentTCB->tcb_pushtimer)) { //
// The timer is running. Decrement it.
//
if (--(CurrentTCB->tcb_pushtimer) == 0) { //
// It's fired.
//
PushData(CurrentTCB); Delayed = TRUE; } }
//
// Check the delayed ack timer.
//
if (TCB_TIMER_RUNNING(CurrentTCB->tcb_delacktimer)) { //
// The timer is running.
//
if (--(CurrentTCB->tcb_delacktimer) == 0) { //
// And it's fired. Set up to send an ACK.
//
Delayed = TRUE; DelayAction(CurrentTCB, NEED_ACK); } }
//
// Finally check the keepalive timer.
//
if (CurrentTCB->tcb_state == TCB_ESTAB) { if ((CurrentTCB->tcb_flags & KEEPALIVE) && (CurrentTCB->tcb_conn != NULL)) { uint Delta;
Delta = TCPTime - CurrentTCB->tcb_alive; if (Delta > CurrentTCB->tcb_conn->tc_tcbkatime) { Delta -= CurrentTCB->tcb_conn->tc_tcbkatime; if (Delta > (CurrentTCB->tcb_kacount * CurrentTCB->tcb_conn->tc_tcbkainterval)) { if (CurrentTCB->tcb_kacount < MaxDataRexmitCount) { SendKA(CurrentTCB, DISPATCH_LEVEL); CurrentTCB = FindNextTCB(i, CurrentTCB); continue; } else goto TimeoutTCB; } } else CurrentTCB->tcb_kacount = 0; } }
//
// If this is an active open connection in SYN-SENT or SYN-RCVD,
// or we have a FIN pending, check the connect timer.
//
if (CurrentTCB->tcb_flags & (ACTIVE_OPEN | FIN_NEEDED | FIN_SENT)) { TCPConnReq *ConnReq = CurrentTCB->tcb_connreq;
ASSERT(ConnReq != NULL); if (TCB_TIMER_RUNNING(ConnReq->tcr_timeout)) { // Timer is running.
if (--(ConnReq->tcr_timeout) == 0) { // The connection timer has timed out.
TryToCloseTCB(CurrentTCB, TCB_CLOSE_TIMEOUT, DISPATCH_LEVEL); CurrentTCB = FindNextTCB(i, CurrentTCB); continue; } } }
//
// Timer isn't running, or didn't fire.
//
TempTCB = CurrentTCB->tcb_next; KeReleaseSpinLockFromDpcLevel(&CurrentTCB->tcb_lock); CurrentTCB = TempTCB; } }
//
// See if we need to call receive complete as part of deadman processing.
// We do this now because we want to restart the timer before calling
// receive complete, in case that takes a while. If we make this check
// while the timer is running we'd have to lock, so we'll check and save
// the result now before we start the timer.
//
if (DeadmanTicks == TCPTime) { CallRcvComplete = TRUE; DeadmanTicks += NUM_DEADMAN_TICKS; } else CallRcvComplete = FALSE;
//
// Now check the pending free list. If it's not null, walk down the
// list and decrement the walk count. If the count goes below 2, pull it
// from the list. If the count goes to 0, free the TCB. If the count is
// at 1 it'll be freed by whoever called RemoveTCB.
//
KeAcquireSpinLockAtDpcLevel(&TCBTableLock); if (PendingFreeList != NULL) { TCB *PrevTCB;
PrevTCB = CONTAINING_RECORD(&PendingFreeList, TCB, tcb_delayq.q_next);
do { CurrentTCB = (TCB *)PrevTCB->tcb_delayq.q_next;
CHECK_STRUCT(CurrentTCB, tcb);
CurrentTCB->tcb_walkcount--; if (CurrentTCB->tcb_walkcount <= 1) { *(TCB **)&PrevTCB->tcb_delayq.q_next = (TCB *)CurrentTCB->tcb_delayq.q_next;
if (CurrentTCB->tcb_walkcount == 0) { FreeTCB(CurrentTCB); } } else { PrevTCB = CurrentTCB; } } while (PrevTCB->tcb_delayq.q_next != NULL); }
TCBWalkCount--; KeReleaseSpinLockFromDpcLevel(&TCBTableLock);
if (Delayed) ProcessTCBDelayQ();
if (CallRcvComplete) TCPRcvComplete(); }
//* TCBWalk - Walk the TCBs in the table, and call a function for each of them.
//
// Called when we need to repetively do something to each TCB in the table.
// We call the specified function with a pointer to the TCB and the input
// context for each TCB in the table. If the function returns FALSE, we
// delete the TCB.
//
void // Returns: Nothing.
TCBWalk( uint (*CallRtn)(struct TCB *, void *, void *, void *), // Routine to call.
void *Context1, // Context to pass to CallRtn.
void *Context2, // Second context to pass to call routine.
void *Context3) // Third context to pass to call routine.
{ uint i; TCB *CurTCB; KIRQL Irql0, Irql1;
//
// Loop through each bucket in the table, going down the chain of
// TCBs on the bucket. For each one call CallRtn.
//
KeAcquireSpinLock(&TCBTableLock, &Irql0);
for (i = 0; i < TcbTableSize; i++) {
CurTCB = TCBTable[i];
//
// Walk down the chain on this bucket.
//
while (CurTCB != NULL) { if (!(*CallRtn)(CurTCB, Context1, Context2, Context3)) { //
// Call failed on this one.
// Notify the client and close the TCB.
//
KeAcquireSpinLock(&CurTCB->tcb_lock, &Irql1); if (!CLOSING(CurTCB)) { CurTCB->tcb_refcnt++; KeReleaseSpinLock(&TCBTableLock, Irql1); TryToCloseTCB(CurTCB, TCB_CLOSE_ABORTED, Irql0);
RemoveTCBFromConn(CurTCB); if (CurTCB->tcb_state != TCB_TIME_WAIT) NotifyOfDisc(CurTCB, TDI_CONNECTION_ABORTED, NULL);
KeAcquireSpinLock(&CurTCB->tcb_lock, &Irql0); DerefTCB(CurTCB, Irql0); KeAcquireSpinLock(&TCBTableLock, &Irql0); } else KeReleaseSpinLock(&CurTCB->tcb_lock, Irql1);
CurTCB = FindNextTCB(i, CurTCB); } else { CurTCB = CurTCB->tcb_next; } } }
KeReleaseSpinLock(&TCBTableLock, Irql0); }
//* FindTCB - Find a TCB in the tcb table.
//
// Called when we need to find a TCB in the TCB table. We take a quick
// look at the last TCB we found, and if it matches we return it. Otherwise
// we hash into the TCB table and look for it. We assume the TCB table lock
// is held when we are called.
//
TCB * // Returns: Pointer to TCB found, or NULL if none.
FindTCB( IPv6Addr *Src, // Source IP address of TCB to be found.
IPv6Addr *Dest, // Destination IP address of TCB to be found.
uint SrcScopeId, // Source address scope identifier.
uint DestScopeId, // Destination address scope identifier.
ushort SrcPort, // Source port of TCB to be found.
ushort DestPort) // Destination port of TCB to be found.
{ TCB *FoundTCB;
if (LastTCB != NULL) { CHECK_STRUCT(LastTCB, tcb); if (IP6_ADDR_EQUAL(&LastTCB->tcb_daddr, Dest) && LastTCB->tcb_dscope_id == DestScopeId && LastTCB->tcb_dport == DestPort && IP6_ADDR_EQUAL(&LastTCB->tcb_saddr, Src) && LastTCB->tcb_sscope_id == SrcScopeId && LastTCB->tcb_sport == SrcPort) return LastTCB; }
//
// Didn't find it in our 1 element cache.
//
FoundTCB = TCBTable[TCB_HASH(*Dest, *Src, DestPort, SrcPort)]; while (FoundTCB != NULL) { CHECK_STRUCT(FoundTCB, tcb); if (IP6_ADDR_EQUAL(&FoundTCB->tcb_daddr, Dest) && FoundTCB->tcb_dscope_id == DestScopeId && FoundTCB->tcb_dport == DestPort && IP6_ADDR_EQUAL(&FoundTCB->tcb_saddr, Src) && FoundTCB->tcb_sscope_id == SrcScopeId && FoundTCB->tcb_sport == SrcPort) {
//
// Found it. Update the cache for next time, and return.
//
LastTCB = FoundTCB; return FoundTCB; } else FoundTCB = FoundTCB->tcb_next; }
return FoundTCB; }
//* InsertTCB - Insert a TCB in the tcb table.
//
// This routine inserts a TCB in the TCB table. No locks need to be held
// when this routine is called. We insert TCBs in ascending address order.
// Before inserting we make sure that the TCB isn't already in the table.
//
uint // Returns: TRUE if we inserted, false if we didn't.
InsertTCB( TCB *NewTCB) // TCB to be inserted.
{ uint TCBIndex; KIRQL OldIrql; TCB *PrevTCB, *CurrentTCB; TCB *WhereToInsert;
ASSERT(NewTCB != NULL); CHECK_STRUCT(NewTCB, tcb); TCBIndex = TCB_HASH(NewTCB->tcb_daddr, NewTCB->tcb_saddr, NewTCB->tcb_dport, NewTCB->tcb_sport);
KeAcquireSpinLock(&TCBTableLock, &OldIrql); KeAcquireSpinLockAtDpcLevel(&NewTCB->tcb_lock);
//
// Find the proper place in the table to insert him. While
// we're walking we'll check to see if a dupe already exists.
// When we find the right place to insert, we'll remember it, and
// keep walking looking for a duplicate.
//
PrevTCB = CONTAINING_RECORD(&TCBTable[TCBIndex], TCB, tcb_next); WhereToInsert = NULL;
while (PrevTCB->tcb_next != NULL) { CurrentTCB = PrevTCB->tcb_next;
if (IP6_ADDR_EQUAL(&CurrentTCB->tcb_daddr, &NewTCB->tcb_daddr) && IP6_ADDR_EQUAL(&CurrentTCB->tcb_saddr, &NewTCB->tcb_saddr) && (CurrentTCB->tcb_dscope_id == NewTCB->tcb_dscope_id) && (CurrentTCB->tcb_sscope_id == NewTCB->tcb_sscope_id) && (CurrentTCB->tcb_sport == NewTCB->tcb_sport) && (CurrentTCB->tcb_dport == NewTCB->tcb_dport)) {
KeReleaseSpinLockFromDpcLevel(&NewTCB->tcb_lock); KeReleaseSpinLock(&TCBTableLock, OldIrql); return FALSE;
} else {
if (WhereToInsert == NULL && CurrentTCB > NewTCB) { WhereToInsert = PrevTCB; }
CHECK_STRUCT(PrevTCB->tcb_next, tcb); PrevTCB = PrevTCB->tcb_next; } }
if (WhereToInsert == NULL) { WhereToInsert = PrevTCB; }
NewTCB->tcb_next = WhereToInsert->tcb_next; WhereToInsert->tcb_next = NewTCB; NewTCB->tcb_flags |= IN_TCB_TABLE; TStats.ts_numconns++;
KeReleaseSpinLockFromDpcLevel(&NewTCB->tcb_lock); KeReleaseSpinLock(&TCBTableLock, OldIrql); return TRUE; }
//* RemoveTCB - Remove a TCB from the tcb table.
//
// Called when we need to remove a TCB from the TCB table. We assume the
// TCB table lock and the TCB lock are held when we are called. If the
// TCB isn't in the table we won't try to remove him.
//
uint // Returns: TRUE if it's OK to free it, FALSE otherwise.
RemoveTCB( TCB *RemovedTCB) // TCB to be removed.
{ uint TCBIndex; TCB *PrevTCB; #if DBG
uint Found = FALSE; #endif
CHECK_STRUCT(RemovedTCB, tcb);
if (RemovedTCB->tcb_flags & IN_TCB_TABLE) { TCBIndex = TCB_HASH(RemovedTCB->tcb_daddr, RemovedTCB->tcb_saddr, RemovedTCB->tcb_dport, RemovedTCB->tcb_sport);
PrevTCB = CONTAINING_RECORD(&TCBTable[TCBIndex], TCB, tcb_next);
do { if (PrevTCB->tcb_next == RemovedTCB) { // Found him.
PrevTCB->tcb_next = RemovedTCB->tcb_next; RemovedTCB->tcb_flags &= ~IN_TCB_TABLE; TStats.ts_numconns--; #if DBG
Found = TRUE; #endif
break; } PrevTCB = PrevTCB->tcb_next; #if DBG
if (PrevTCB != NULL) CHECK_STRUCT(PrevTCB, tcb); #endif
} while (PrevTCB != NULL);
ASSERT(Found); }
if (LastTCB == RemovedTCB) LastTCB = NULL;
if (TCBWalkCount == 0) { return TRUE; } else { RemovedTCB->tcb_walkcount = TCBWalkCount + 1; *(TCB **)&RemovedTCB->tcb_delayq.q_next = PendingFreeList; PendingFreeList = RemovedTCB; return FALSE; } }
//* ScavengeTCB - Scavenge a TCB that's in the TIME_WAIT state.
//
// Called when we're running low on TCBs, and need to scavenge one from
// TIME_WAIT state. We'll walk through the TCB table, looking for the oldest
// TCB in TIME_WAIT. We'll remove and return a pointer to that TCB. If we
// don't find any TCBs in TIME_WAIT, we'll return NULL.
//
TCB * // Returns: Pointer to a reusable TCB, or NULL.
ScavengeTCB( void) { KIRQL Irql0, Irql1, IrqlSave = 0; uint Now = SystemUpTime(); uint Delta = 0; uint i; TCB *FoundTCB = NULL, *PrevFound = NULL; TCB *CurrentTCB, *PrevTCB;
KeAcquireSpinLock(&TCBTableLock, &Irql0);
if (TCBWalkCount != 0) { KeReleaseSpinLock(&TCBTableLock, Irql0); return NULL; }
for (i = 0; i < TcbTableSize; i++) {
PrevTCB = CONTAINING_RECORD(&TCBTable[i], TCB, tcb_next); CurrentTCB = PrevTCB->tcb_next;
while (CurrentTCB != NULL) { CHECK_STRUCT(CurrentTCB, tcb);
KeAcquireSpinLock(&CurrentTCB->tcb_lock, &Irql1); if (CurrentTCB->tcb_state == TCB_TIME_WAIT && (CurrentTCB->tcb_refcnt == 0) && !CLOSING(CurrentTCB)){ if (FoundTCB == NULL || ((Now - CurrentTCB->tcb_alive) > Delta)) { //
// Found a new 'older' TCB. If we already have one, free
// the lock on him and get the lock on the new one.
//
if (FoundTCB != NULL) KeReleaseSpinLock(&FoundTCB->tcb_lock, Irql1); else IrqlSave = Irql1;
PrevFound = PrevTCB; FoundTCB = CurrentTCB; Delta = Now - FoundTCB->tcb_alive; } else KeReleaseSpinLock(&CurrentTCB->tcb_lock, Irql1); } else KeReleaseSpinLock(&CurrentTCB->tcb_lock, Irql1);
//
// Look at the next one.
//
PrevTCB = CurrentTCB; CurrentTCB = PrevTCB->tcb_next; } }
//
// If we have one, pull him from the list.
//
if (FoundTCB != NULL) { PrevFound->tcb_next = FoundTCB->tcb_next; FoundTCB->tcb_flags &= ~IN_TCB_TABLE;
//
// Release our references on the NTE and RCE. We won't
// be sending anymore using the old incarnation of this TCB.
//
if (FoundTCB->tcb_nte != NULL) ReleaseNTE(FoundTCB->tcb_nte); if (FoundTCB->tcb_rce != NULL) ReleaseRCE(FoundTCB->tcb_rce);
TStats.ts_numconns--; if (LastTCB == FoundTCB) { LastTCB = NULL; } KeReleaseSpinLock(&FoundTCB->tcb_lock, IrqlSave); }
KeReleaseSpinLock(&TCBTableLock, Irql0); return FoundTCB; }
//* AllocTCB - Allocate a TCB.
//
// Called whenever we need to allocate a TCB. We try to pull one off the
// free list, or allocate one if we need one. We then initialize it, etc.
//
TCB * // Returns: Pointer to the new TCB, or NULL if we couldn't get one.
AllocTCB( void) { TCB *NewTCB;
//
// First, see if we have one on the free list.
//
PSLIST_ENTRY BufferLink;
BufferLink = ExInterlockedPopEntrySList(&FreeTCBList, &FreeTCBListLock);
if (BufferLink != NULL) { NewTCB = CONTAINING_RECORD(BufferLink, TCB, tcb_next); CHECK_STRUCT(NewTCB, tcb); ExInterlockedAddUlong((PULONG)&FreeTCBs, (ULONG)-1, &FreeTCBListLock); } else { //
// We have none on the free list. If the total number of TCBs
// outstanding is more than we like to keep on the free list, try
// to scavenge a TCB from time wait.
//
if (CurrentTCBs < MaxFreeTCBs || ((NewTCB = ScavengeTCB()) == NULL)) { if (CurrentTCBs < MaxTCBs) { NewTCB = ExAllocatePool(NonPagedPool, sizeof(TCB)); if (NewTCB == NULL) { return NewTCB; } else { ExInterlockedAddUlong((PULONG)&CurrentTCBs, 1, &FreeTCBListLock); } } else return NULL; } }
ASSERT(NewTCB != NULL);
RtlZeroMemory(NewTCB, sizeof(TCB)); #if DBG
NewTCB->tcb_sig = tcb_signature; #endif
INITQ(&NewTCB->tcb_sendq); NewTCB->tcb_cursend = NULL; NewTCB->tcb_alive = TCPTime; NewTCB->tcb_hops = -1;
//
// Initially we're not on the fast path because we're not established. Set
// the slowcount to one and set up the fastchk fields so we don't take the
// fast path.
//
NewTCB->tcb_slowcount = 1; NewTCB->tcb_fastchk = TCP_FLAG_ACK | TCP_FLAG_SLOW; KeInitializeSpinLock(&NewTCB->tcb_lock);
return NewTCB; }
//* FreeTCB - Free a TCB.
//
// Called whenever we need to free a TCB.
//
// Note: This routine may be called with the TCBTableLock held.
//
void // Returns: Nothing.
FreeTCB( TCB *FreedTCB) // TCB to be freed.
{ PSLIST_ENTRY BufferLink; KIRQL OldIrql;
CHECK_STRUCT(FreedTCB, tcb);
#if defined(_WIN64)
if (CurrentTCBs > 2 * MaxFreeTCBs) {
#else
//
// Acquire FreeTCBListLock before accessing Depth field.
//
KeAcquireSpinLock(&FreeTCBListLock, &OldIrql); if ((CurrentTCBs > 2 * MaxFreeTCBs) || (FreeTCBList.Depth > 65000)) { KeReleaseSpinLock(&FreeTCBListLock, OldIrql);
#endif
ExInterlockedAddUlong((PULONG)&CurrentTCBs, (ulong) - 1, &FreeTCBListLock); ExFreePool(FreedTCB); return; } #if !defined(_WIN64)
KeReleaseSpinLock(&FreeTCBListLock, OldIrql); #endif
BufferLink = CONTAINING_RECORD(&(FreedTCB->tcb_next), SLIST_ENTRY, Next); ExInterlockedPushEntrySList(&FreeTCBList, BufferLink, &FreeTCBListLock); ExInterlockedAddUlong((PULONG)&FreeTCBs, 1, &FreeTCBListLock); }
#pragma BEGIN_INIT
//* InitTCB - Initialize our TCB code.
//
// Called during init time to initialize our TCB code. We initialize
// the TCB table, etc, then return.
//
int // Returns: TRUE if we did initialize, false if we didn't.
InitTCB( void) { LARGE_INTEGER InitialWakeUp; uint i;
TCBTable = ExAllocatePool(NonPagedPool, TcbTableSize * sizeof(TCB*)); if (TCBTable == NULL) { return FALSE; }
for (i = 0; i < TcbTableSize; i++) TCBTable[i] = NULL;
LastTCB = NULL;
ExInitializeSListHead(&FreeTCBList);
KeInitializeSpinLock(&TCBTableLock); KeInitializeSpinLock(&FreeTCBListLock);
TCPTime = 0; TCBWalkCount = 0; DeadmanTicks = NUM_DEADMAN_TICKS;
//
// Set up our timer to call TCBTimeout once every MS_PER_TICK milliseconds.
//
// REVIEW: Switch this to be driven off the IPv6Timeout routine instead
// REVIEW: of having two independent timers?
//
KeInitializeDpc(&TCBTimeoutDpc, TCBTimeout, NULL); KeInitializeTimer(&TCBTimer); InitialWakeUp.QuadPart = -(LONGLONG) MS_PER_TICK * 10000; KeSetTimerEx(&TCBTimer, InitialWakeUp, MS_PER_TICK, &TCBTimeoutDpc);
return TRUE; }
#pragma END_INIT
//* UnloadTCB
//
// Called during shutdown to uninitialize
// in preparation for unloading the stack.
//
// There are no open sockets (or else we wouldn't be unloading).
// Because UnloadTCPSend has already been called,
// we are no longer receiving packets from the IPv6 layer.
//
void UnloadTCB(void) { PSLIST_ENTRY BufferLink; TCB *CurrentTCB; uint i; KIRQL OldIrql;
//
// First stop TCBTimeout from being called.
//
KeCancelTimer(&TCBTimer);
//
// Wait until all the DPC routines have finished.
//
KeFlushQueuedDpcs();
//
// Traverse the buckets looking for TCBs.
// REVIEW - Can we have TCBs in states other than time-wait?
//
for (i = 0; i < TcbTableSize; i++) {
while ((CurrentTCB = TCBTable[i]) != NULL) {
KeAcquireSpinLock(&CurrentTCB->tcb_lock, &OldIrql);
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_STATE, "UnloadTCB(%p): state %x flags %x refs %x " "reason %x pend %x walk %x\n", CurrentTCB, CurrentTCB->tcb_state, CurrentTCB->tcb_flags, CurrentTCB->tcb_refcnt, CurrentTCB->tcb_closereason, CurrentTCB->tcb_pending, CurrentTCB->tcb_walkcount));
CurrentTCB->tcb_flags |= NEED_RST; TryToCloseTCB(CurrentTCB, TCB_CLOSE_ABORTED, OldIrql); } }
//
// Now pull TCBs off the free list and really free them.
//
while ((BufferLink = ExInterlockedPopEntrySList(&FreeTCBList, &FreeTCBListLock)) != NULL) { CurrentTCB = CONTAINING_RECORD(BufferLink, TCB, tcb_next); CHECK_STRUCT(CurrentTCB, tcb);
ExFreePool(CurrentTCB); }
ExFreePool(TCBTable); TCBTable = NULL; }
//* CleanupTCBWithIF
//
// Helper function for TCBWalk, to remove
// TCBs and RCEs that reference the specified interface.
//
// Lock: Called in TCBWalk with TCB table lock held.
// Returns FALSE if CheckTCB should be deleted, TRUE otherwise.
//
uint CleanupTCBWithIF( TCB *CheckTCB, void *Context1, void *Context2, void *Context3) { Interface *IF = (Interface *) Context1;
UNREFERENCED_PARAMETER(Context2); UNREFERENCED_PARAMETER(Context3);
CHECK_STRUCT(CheckTCB, tcb);
//
// Take the lock of this TCB before accessing its NTE and RCE.
//
KeAcquireSpinLockAtDpcLevel(&CheckTCB->tcb_lock);
if ((CheckTCB->tcb_nte != NULL) && (CheckTCB->tcb_nte->IF == IF)) {
//
// Any NTE on this IF is guaranteed to be invalid by the time
// this routine gets called. So we need to quit using it.
//
ReleaseNTE(CheckTCB->tcb_nte);
//
// See if this address lives on as a different NTE.
//
CheckTCB->tcb_nte = FindNetworkWithAddress(&CheckTCB->tcb_saddr, CheckTCB->tcb_sscope_id); if (CheckTCB->tcb_nte == NULL) { //
// Game over man, game over.
//
KeReleaseSpinLockFromDpcLevel(&CheckTCB->tcb_lock); return FALSE; // Delete this TCB.
} }
if ((CheckTCB->tcb_rce != NULL) && (CheckTCB->tcb_rce->NTE->IF == IF)) { //
// Free up this RCE. TCP will attempt to get a new one
// the next time it wants to send something.
//
ReleaseRCE(CheckTCB->tcb_rce); CheckTCB->tcb_rce = NULL; }
KeReleaseSpinLockFromDpcLevel(&CheckTCB->tcb_lock); return TRUE; // Do not delete this TCB.
}
//* TCPRemoveIF
//
// Remove TCP's references to the specified interface.
//
void TCPRemoveIF(Interface *IF) { //
// Currently, only TCBs hold onto references.
// The TCBTable might have already been freed if we're being
// unloaded at this point.
//
if (TCBTable != NULL) { TCBWalk(CleanupTCBWithIF, IF, NULL, NULL); } }
|