3006 lines
97 KiB
C
3006 lines
97 KiB
C
|
// -*- 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 connection management.
|
||
|
//
|
||
|
// This file contains the code handling TCP connection related requests,
|
||
|
// such as connecting and disconnecting.
|
||
|
//
|
||
|
|
||
|
|
||
|
#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 "addr.h"
|
||
|
#include "tcp.h"
|
||
|
#include "tcb.h"
|
||
|
#include "tcpconn.h"
|
||
|
#include "tcpsend.h"
|
||
|
#include "tcprcv.h"
|
||
|
#include "tcpdeliv.h"
|
||
|
#include "info.h"
|
||
|
#include "tcpcfg.h"
|
||
|
#include "route.h"
|
||
|
#include "security.h"
|
||
|
#include "tcpmd5.h"
|
||
|
#include "md5.h"
|
||
|
#include "crypto\rc4.h"
|
||
|
#include "ntddksec.h"
|
||
|
|
||
|
SLIST_HEADER ConnReqFree; // Connection request free list.
|
||
|
|
||
|
//
|
||
|
// ISN globals.
|
||
|
//
|
||
|
#define ISN_KEY_SIZE 256 // 2048 bits.
|
||
|
#define ISN_DEF_RAND_STORE_SIZE 256
|
||
|
#define ISN_MIN_RAND_STORE_SIZE 1
|
||
|
#define ISN_MAX_RAND_STORE_SIZE 16384
|
||
|
|
||
|
|
||
|
typedef struct _ISN_RAND_STORE {
|
||
|
MD5_CONTEXT Md5Context;
|
||
|
ulong iBuf;
|
||
|
ushort* pBuf;
|
||
|
} ISN_RAND_STORE, *PISN_RAND_STORE;
|
||
|
|
||
|
RC4_KEYSTRUCT ISNRC4Key;
|
||
|
PISN_RAND_STORE ISNStore;
|
||
|
uint ISNStoreSize = ISN_DEF_RAND_STORE_SIZE;
|
||
|
uint ISNStoreMask;
|
||
|
SeqNum ISNMonotonicPortion = 0;
|
||
|
int ISNCredits;
|
||
|
int ISNLastIsnUpdateTime;
|
||
|
int ISNMaxCredits;
|
||
|
|
||
|
extern PDRIVER_OBJECT TCPDriverObject;
|
||
|
|
||
|
KSPIN_LOCK ConnReqFreeLock; // Lock to protect conn req free list.
|
||
|
uint NumConnReq; // Current number of ConnReqs.
|
||
|
uint MaxConnReq = 0xffffffff; // Maximum allowed number of ConnReqs.
|
||
|
uint ConnPerBlock = MAX_CONN_PER_BLOCK;
|
||
|
uint NextConnBlock = 0; // Cached index of next unfilled block.
|
||
|
uint MaxAllocatedConnBlocks = 0; // Current number of blocks in the
|
||
|
// ConnTable.
|
||
|
TCPConnBlock **ConnTable = NULL; // The current connection table.
|
||
|
|
||
|
KSPIN_LOCK ConnTableLock;
|
||
|
extern KSPIN_LOCK AddrObjTableLock;
|
||
|
extern KSPIN_LOCK TCBTableLock;
|
||
|
|
||
|
TCPAddrCheckElement *AddrCheckTable = NULL; // The current check table.
|
||
|
|
||
|
extern void RemoveConnFromAO(AddrObj *AO, TCPConn *Conn);
|
||
|
|
||
|
|
||
|
//
|
||
|
// All of the init code can be discarded.
|
||
|
//
|
||
|
#ifdef ALLOC_PRAGMA
|
||
|
|
||
|
int InitTCPConn(void);
|
||
|
int InitISNGenerator(void);
|
||
|
void UnloadISNGenerator(void);
|
||
|
int GetRandBits();
|
||
|
uint GetDeltaTime();
|
||
|
|
||
|
#pragma alloc_text(INIT, InitTCPConn)
|
||
|
#pragma alloc_text(INIT, InitISNGenerator)
|
||
|
#pragma alloc_text(PAGE, UnloadISNGenerator)
|
||
|
|
||
|
#endif // ALLOC_PRAGMA
|
||
|
|
||
|
void CompleteConnReq(TCB *CmpltTCB, TDI_STATUS Status);
|
||
|
|
||
|
|
||
|
//* UnloadISNGenerator - Unload the support for the ISN generator.
|
||
|
//
|
||
|
// Called when we are unloading the driver.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
UnloadISNGenerator(void)
|
||
|
{
|
||
|
CCHAR i;
|
||
|
|
||
|
ASSERT(ISNStore);
|
||
|
|
||
|
for (i = 0; i < KeNumberProcessors; i++) {
|
||
|
if (ISNStore[i].pBuf != NULL) {
|
||
|
ExFreePool(ISNStore[i].pBuf);
|
||
|
ISNStore[i].pBuf = NULL;
|
||
|
}
|
||
|
}
|
||
|
ExFreePool(ISNStore);
|
||
|
ISNStore = NULL;
|
||
|
}
|
||
|
|
||
|
//* InitISNGenerator - Initialize the support for the ISN generator.
|
||
|
//
|
||
|
// Called when the driver is loaded. Get 2048 bits of randomness and
|
||
|
// use them to create an RC4 key.
|
||
|
//
|
||
|
int //Returns: TRUE if successful.
|
||
|
InitISNGenerator(void)
|
||
|
{
|
||
|
UNICODE_STRING DeviceName;
|
||
|
NTSTATUS NtStatus;
|
||
|
PFILE_OBJECT pFileObject;
|
||
|
PDEVICE_OBJECT pDeviceObject;
|
||
|
unsigned char pBuf[ISN_KEY_SIZE];
|
||
|
PIRP pIrp;
|
||
|
IO_STATUS_BLOCK ioStatusBlock;
|
||
|
KEVENT kEvent;
|
||
|
ULONG cBits = 0;
|
||
|
ULONG i;
|
||
|
ULONG cProcs = KeNumberProcessors;
|
||
|
ULONG ISNRandomValue;
|
||
|
|
||
|
//
|
||
|
// Start with the credits that would last for 1 tick.
|
||
|
//
|
||
|
ISNMaxCredits = ISNCredits = MAX_ISN_INCREMENTABLE_CONNECTIONS_PER_100MS;
|
||
|
ISNLastIsnUpdateTime = (int)X100NSTOMS(KeQueryInterruptTime());
|
||
|
|
||
|
|
||
|
//
|
||
|
// Request a block of random bits from the KSecDD driver.
|
||
|
// To do so, retrieve its device object pointer, build an I/O control
|
||
|
// request to be submitted to the driver, and submit the request.
|
||
|
// If any failure occurs, we fall back on the somewhat less-random
|
||
|
// approach of requesting the bits from the randlibk library.
|
||
|
//
|
||
|
|
||
|
do {
|
||
|
|
||
|
RtlInitUnicodeString(&DeviceName,
|
||
|
DD_KSEC_DEVICE_NAME_U);
|
||
|
|
||
|
KeInitializeEvent(&kEvent, SynchronizationEvent, FALSE);
|
||
|
|
||
|
//
|
||
|
// Get the file and device objects for KDSECDD,
|
||
|
// acquire a reference to the device-object,
|
||
|
// release the unneeded reference to the file-object,
|
||
|
// and build the I/O control request to issued to KSecDD.
|
||
|
//
|
||
|
|
||
|
NtStatus = IoGetDeviceObjectPointer(&DeviceName,
|
||
|
FILE_ALL_ACCESS,
|
||
|
&pFileObject,
|
||
|
&pDeviceObject);
|
||
|
|
||
|
if (!NT_SUCCESS(NtStatus)) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
||
|
"Tcpip: IoGetDeviceObjectPointer(KSecDD)=%08x\n",
|
||
|
NtStatus));
|
||
|
break;
|
||
|
}
|
||
|
ObReferenceObject(pDeviceObject);
|
||
|
ObDereferenceObject(pFileObject);
|
||
|
|
||
|
pIrp = IoBuildDeviceIoControlRequest(IOCTL_KSEC_RNG,
|
||
|
pDeviceObject,
|
||
|
NULL, // No input buffer.
|
||
|
0,
|
||
|
pBuf, // Output buffer stores rng.
|
||
|
ISN_KEY_SIZE,
|
||
|
FALSE,
|
||
|
&kEvent,
|
||
|
&ioStatusBlock);
|
||
|
|
||
|
if (pIrp == NULL) {
|
||
|
ObDereferenceObject(pDeviceObject);
|
||
|
NtStatus = STATUS_UNSUCCESSFUL;
|
||
|
break;
|
||
|
}
|
||
|
//
|
||
|
// Issue the I/O control request, wait for it to complete
|
||
|
// if necessary, and release the reference to KSecDD's device-object.
|
||
|
//
|
||
|
NtStatus = IoCallDriver(pDeviceObject, pIrp);
|
||
|
|
||
|
if (NtStatus == STATUS_PENDING) {
|
||
|
KeWaitForSingleObject(&kEvent,
|
||
|
Executive,
|
||
|
KernelMode,
|
||
|
FALSE, // Not alertable.
|
||
|
NULL); // No timeout.
|
||
|
|
||
|
NtStatus = ioStatusBlock.Status;
|
||
|
}
|
||
|
ObDereferenceObject(pDeviceObject);
|
||
|
|
||
|
if (!NT_SUCCESS(NtStatus)) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
||
|
"Tcpip: IoCallDriver IOCTL_KSEC_RNG failed %#x\n",
|
||
|
NtStatus));
|
||
|
break;
|
||
|
}
|
||
|
} while (FALSE);
|
||
|
|
||
|
if (!NT_SUCCESS(NtStatus)) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Generate the key control structure.
|
||
|
//
|
||
|
rc4_key(&ISNRC4Key, ISN_KEY_SIZE, pBuf);
|
||
|
|
||
|
//
|
||
|
// Initalialize the current sequence number to a random value.
|
||
|
//
|
||
|
rc4(&ISNRC4Key, sizeof(SeqNum), (uchar*)&ISNMonotonicPortion);
|
||
|
|
||
|
//
|
||
|
// Obtain a random value to be used along with the invariants to compute the
|
||
|
// MD5 hash .
|
||
|
//
|
||
|
rc4(&ISNRC4Key, sizeof(ISNRandomValue), (uchar*)&ISNRandomValue);
|
||
|
|
||
|
//
|
||
|
// Round down the store size to power of 2. Verify in range.
|
||
|
//
|
||
|
|
||
|
while (ISNStoreSize = ISNStoreSize >> 1) {
|
||
|
cBits++;
|
||
|
}
|
||
|
|
||
|
ISNStoreSize = 1 << cBits;
|
||
|
|
||
|
if (ISNStoreSize < ISN_MIN_RAND_STORE_SIZE ||
|
||
|
ISNStoreSize > ISN_MAX_RAND_STORE_SIZE) {
|
||
|
ISNStoreSize = ISN_DEF_RAND_STORE_SIZE;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The mask is store size - 1.
|
||
|
//
|
||
|
ISNStoreMask = ISNStoreSize - 1;
|
||
|
|
||
|
//
|
||
|
// Initialize the random ISN store. One array/index per processor.
|
||
|
//
|
||
|
|
||
|
ISNStore = ExAllocatePool(NonPagedPool, cProcs * sizeof(ISN_RAND_STORE));
|
||
|
|
||
|
if (ISNStore == NULL) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
||
|
"Tcpip: failed to allocate ISN rand store\n"));
|
||
|
return FALSE;
|
||
|
}
|
||
|
RtlZeroMemory(ISNStore, sizeof(ISN_RAND_STORE) * cProcs);
|
||
|
|
||
|
for (i = 0; i < cProcs; i++) {
|
||
|
ISNStore[i].pBuf = ExAllocatePool(NonPagedPool, sizeof(ushort) * ISNStoreSize);
|
||
|
|
||
|
if (ISNStore[i].pBuf == NULL) {
|
||
|
goto error1;
|
||
|
}
|
||
|
rc4(&ISNRC4Key,
|
||
|
sizeof(ushort) * ISNStoreSize,
|
||
|
(uchar*)ISNStore[i].pBuf);
|
||
|
|
||
|
//
|
||
|
// Initialize structures required to call the MD5 transform.
|
||
|
//
|
||
|
|
||
|
MD5InitializeData(&ISNStore[i].Md5Context, ISNRandomValue);
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
|
||
|
error1:
|
||
|
|
||
|
UnloadISNGenerator();
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* GetRandomISN - Gets a random Initial Sequence Number.
|
||
|
//
|
||
|
// Called when an Initial Sequence Number (ISN) is needed. Calls crypto
|
||
|
// functions for random number generation.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
GetRandomISN(
|
||
|
SeqNum *Seq, // Returned sequence number
|
||
|
uchar *TcbInvariants) // Connection invariants
|
||
|
{
|
||
|
ulong randbits;
|
||
|
ulong iProc;
|
||
|
KIRQL irql;
|
||
|
PMD5_CONTEXT Md5Context;
|
||
|
|
||
|
//
|
||
|
// Raise IRQL to DISPATCH so that we don't get swapped out while accessing
|
||
|
// the processor specific array. Check to see if already at DISPATCH
|
||
|
// before doing the work.
|
||
|
//
|
||
|
|
||
|
ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
|
||
|
|
||
|
iProc = KeGetCurrentProcessorNumber();
|
||
|
|
||
|
//
|
||
|
// Add the random number only if the number of connections that can
|
||
|
// increment the sequence number within this time period is non zero.
|
||
|
// [Note: This could make the ISNCredits less than 0, but it is not a
|
||
|
// problem].
|
||
|
//
|
||
|
if ((ISNCredits > 0) && (InterlockedDecrement(&ISNCredits) > 0)) {
|
||
|
randbits = GetRandBits();
|
||
|
|
||
|
//
|
||
|
// We want to add between 16K and 32K of random, so adjust. There are
|
||
|
// 15 bits of randomness, just ensure that the high order bit is set
|
||
|
// and we have >= 16K and <= (32K-1)::14bits of randomness.
|
||
|
//
|
||
|
randbits &= 0x7FFF;
|
||
|
randbits |= 0x4000;
|
||
|
|
||
|
} else {
|
||
|
int Delta = GetDeltaTime();
|
||
|
|
||
|
if (Delta > 0) {
|
||
|
randbits = GetRandBits();
|
||
|
|
||
|
//
|
||
|
// We can add anywhere from 256 to 512 per ms.
|
||
|
//
|
||
|
randbits &= 0x1FF;
|
||
|
randbits |= 0x100;
|
||
|
|
||
|
randbits *= Delta;
|
||
|
} else {
|
||
|
randbits = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Update global CurISN. InterlockedExchangeAdd returns initial value
|
||
|
// (not the added value).
|
||
|
//
|
||
|
*Seq = InterlockedExchangeAdd(&ISNMonotonicPortion, randbits);
|
||
|
|
||
|
//
|
||
|
// Move the invariants from the connection.
|
||
|
//
|
||
|
Md5Context = &ISNStore[iProc].Md5Context;
|
||
|
MD5InitializeScratch(Md5Context);
|
||
|
RtlCopyMemory(Md5Context->Data, TcbInvariants, TCP_MD5_DATA_LENGTH);
|
||
|
TransformMD5(Md5Context->Scratch, Md5Context->Data);
|
||
|
|
||
|
//
|
||
|
// Add the Invariant hash to the sequence number.
|
||
|
//
|
||
|
*Seq += (ULONG)(Md5Context->Scratch[0]);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* GetRandBits
|
||
|
//
|
||
|
// Returns 16 random bits from the random number array generated using RC4.
|
||
|
// When the store is exhausted, it will be replenished.
|
||
|
//
|
||
|
int // Returns: 16 bits of random data.
|
||
|
GetRandBits()
|
||
|
{
|
||
|
ulong iStore;
|
||
|
int randbits;
|
||
|
ulong iProc = KeGetCurrentProcessorNumber();
|
||
|
|
||
|
//
|
||
|
// Get index into the random store. Mask performs mod operation.
|
||
|
//
|
||
|
iStore = ++ISNStore[iProc].iBuf & ISNStoreMask;
|
||
|
|
||
|
ASSERT(iStore < ISNStoreSize);
|
||
|
|
||
|
randbits = ISNStore[iProc].pBuf[iStore];
|
||
|
|
||
|
if (iStore == 0) {
|
||
|
rc4(&ISNRC4Key,
|
||
|
sizeof(ushort) * ISNStoreSize,
|
||
|
(uchar*) ISNStore[iProc].pBuf);
|
||
|
}
|
||
|
|
||
|
return randbits;
|
||
|
}
|
||
|
|
||
|
//* GetRandBits
|
||
|
//
|
||
|
// Tracks the time-based updates of ISN. It will return the time elapsed since
|
||
|
// the last time this function was called. This would be used by the caller to
|
||
|
// increment the ISN by an appropriate amount. Note that the maximum value
|
||
|
// is function returns is 200 MS.
|
||
|
//
|
||
|
uint // Returns: Delta time in milli-seconds.
|
||
|
GetDeltaTime()
|
||
|
{
|
||
|
//
|
||
|
// If the time has changed since the ISN was updated last time, it
|
||
|
// can be incremented now.
|
||
|
//
|
||
|
int PreviousUpdateTime, Delta;
|
||
|
int CurrentUpdateTime = (int)X100NSTOMS(KeQueryInterruptTime());
|
||
|
|
||
|
PreviousUpdateTime = InterlockedExchange(&ISNLastIsnUpdateTime,
|
||
|
CurrentUpdateTime);
|
||
|
|
||
|
Delta = CurrentUpdateTime - PreviousUpdateTime;
|
||
|
|
||
|
if (Delta > 0) {
|
||
|
return MIN(Delta, 200);
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// Routines for handling conn refcount going to 0.
|
||
|
//
|
||
|
|
||
|
//* DummyDone - Called when nothing to do.
|
||
|
//
|
||
|
// Called with TCPConnBlock.cb_lock held.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
DummyDone(TCPConn *Conn, // Connection going to 0.
|
||
|
KIRQL PreLockIrql) // IRQL prior to TCPConnBlock.cb_lock acquisition.
|
||
|
{
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, PreLockIrql);
|
||
|
}
|
||
|
|
||
|
//* DummyCmplt - Dummy close completion routine.
|
||
|
void
|
||
|
DummyCmplt(PVOID Dummy1, uint Dummy2, uint Dummy3)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
//* CloseDone - Called when we need to complete a close.
|
||
|
//
|
||
|
// Called with TCPConnBlock.cb_lock held.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
CloseDone(TCPConn *Conn, // Connection going to 0.
|
||
|
KIRQL Irql0) // IRQL prior to TCPConnBlock.cb_lock acquisition.
|
||
|
{
|
||
|
RequestCompleteRoutine Rtn; // Completion routine.
|
||
|
PVOID Context; // User context for completion routine.
|
||
|
AddrObj *AO;
|
||
|
KIRQL Irql1, Irql2;
|
||
|
|
||
|
ASSERT(Conn->tc_flags & CONN_CLOSING);
|
||
|
|
||
|
Rtn = Conn->tc_rtn;
|
||
|
Context = Conn->tc_rtncontext;
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
|
||
|
KeAcquireSpinLock(&AddrObjTableLock, &Irql0);
|
||
|
KeAcquireSpinLock(&Conn->tc_ConnBlock->cb_lock, &Irql1);
|
||
|
|
||
|
if ((AO = Conn->tc_ao) != NULL) {
|
||
|
|
||
|
CHECK_STRUCT(AO, ao);
|
||
|
|
||
|
// It's associated.
|
||
|
KeAcquireSpinLock(&AO->ao_lock, &Irql2);
|
||
|
RemoveConnFromAO(AO, Conn);
|
||
|
// We've pulled him from the AO, we can free the lock now.
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql2);
|
||
|
}
|
||
|
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
KeReleaseSpinLock(&AddrObjTableLock, Irql0);
|
||
|
|
||
|
ExFreePool(Conn);
|
||
|
|
||
|
(*Rtn)(Context, TDI_SUCCESS, 0);
|
||
|
}
|
||
|
|
||
|
//* DisassocDone - Called when we need to complete a disassociate.
|
||
|
//
|
||
|
// Called with TCPConnBlock.cb_lock held.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
DisassocDone(TCPConn *Conn, // Connection going to 0.
|
||
|
KIRQL Irql0) // IRQL prior to TCPConnBlock.cb_lock acquisition.
|
||
|
{
|
||
|
RequestCompleteRoutine Rtn; // Completion routine.
|
||
|
PVOID Context; // User context for completion routine.
|
||
|
AddrObj *AO;
|
||
|
uint NeedClose = FALSE;
|
||
|
KIRQL Irql1, Irql2;
|
||
|
|
||
|
ASSERT(Conn->tc_flags & CONN_DISACC);
|
||
|
ASSERT(!(Conn->tc_flags & CONN_CLOSING));
|
||
|
ASSERT(Conn->tc_refcnt == 0);
|
||
|
|
||
|
Rtn = Conn->tc_rtn;
|
||
|
Context = Conn->tc_rtncontext;
|
||
|
Conn->tc_refcnt = 1;
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
|
||
|
KeAcquireSpinLock(&AddrObjTableLock, &Irql0);
|
||
|
KeAcquireSpinLock(&Conn->tc_ConnBlock->cb_lock, &Irql1);
|
||
|
if (!(Conn->tc_flags & CONN_CLOSING)) {
|
||
|
|
||
|
AO = Conn->tc_ao;
|
||
|
if (AO != NULL) {
|
||
|
KeAcquireSpinLock(&AO->ao_lock, &Irql2);
|
||
|
RemoveConnFromAO(AO, Conn);
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql2);
|
||
|
}
|
||
|
|
||
|
ASSERT(Conn->tc_refcnt == 1);
|
||
|
Conn->tc_flags &= ~CONN_DISACC;
|
||
|
} else
|
||
|
NeedClose = TRUE;
|
||
|
|
||
|
Conn->tc_refcnt = 0;
|
||
|
KeReleaseSpinLock(&AddrObjTableLock, Irql1);
|
||
|
|
||
|
if (NeedClose) {
|
||
|
CloseDone(Conn, Irql0);
|
||
|
} else {
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
(*Rtn)(Context, TDI_SUCCESS, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//* FreeConnReq - Free a connection request structure.
|
||
|
//
|
||
|
// Called to free a connection request structure.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
FreeConnReq(
|
||
|
TCPConnReq *FreedReq) // Connection request structure to be freed.
|
||
|
{
|
||
|
PSLIST_ENTRY BufferLink;
|
||
|
|
||
|
CHECK_STRUCT(FreedReq, tcr);
|
||
|
|
||
|
BufferLink = CONTAINING_RECORD(&(FreedReq->tcr_req.tr_q.q_next),
|
||
|
SLIST_ENTRY, Next);
|
||
|
|
||
|
ExInterlockedPushEntrySList(&ConnReqFree, BufferLink, &ConnReqFreeLock);
|
||
|
}
|
||
|
|
||
|
|
||
|
//* GetConnReq - Get a connection request structure.
|
||
|
//
|
||
|
// Called to get a connection request structure.
|
||
|
//
|
||
|
TCPConnReq * // Returns: Pointer to ConnReq structure, or NULL if none.
|
||
|
GetConnReq(void) // Nothing.
|
||
|
{
|
||
|
TCPConnReq *Temp;
|
||
|
PSLIST_ENTRY BufferLink;
|
||
|
Queue *QueuePtr;
|
||
|
TCPReq *ReqPtr;
|
||
|
|
||
|
BufferLink = ExInterlockedPopEntrySList(&ConnReqFree, &ConnReqFreeLock);
|
||
|
|
||
|
if (BufferLink != NULL) {
|
||
|
QueuePtr = CONTAINING_RECORD(BufferLink, Queue, q_next);
|
||
|
ReqPtr = CONTAINING_RECORD(QueuePtr, TCPReq, tr_q);
|
||
|
Temp = CONTAINING_RECORD(ReqPtr, TCPConnReq, tcr_req);
|
||
|
CHECK_STRUCT(Temp, tcr);
|
||
|
} else {
|
||
|
if (NumConnReq < MaxConnReq)
|
||
|
Temp = ExAllocatePool(NonPagedPool, sizeof(TCPConnReq));
|
||
|
else
|
||
|
Temp = NULL;
|
||
|
|
||
|
if (Temp != NULL) {
|
||
|
ExInterlockedAddUlong(&NumConnReq, 1, &ConnReqFreeLock);
|
||
|
#if DBG
|
||
|
Temp->tcr_req.tr_sig = tr_signature;
|
||
|
Temp->tcr_sig = tcr_signature;
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Temp;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* GetConnFromConnID - Get a Connection from a connection ID.
|
||
|
//
|
||
|
// Called to obtain a Connection pointer from a ConnID. We don't actually
|
||
|
// check the connection pointer here, but we do bounds check the input ConnID
|
||
|
// and make sure the instance fields match.
|
||
|
// If successful, returns with TCPConnBlock.cb_lock held.
|
||
|
//
|
||
|
TCPConn * // Returns: Pointer to the TCPConn, or NULL.
|
||
|
GetConnFromConnID(
|
||
|
uint ConnID, // Connection ID to find a pointer for.
|
||
|
KIRQL* Irql) // Receives IRQL prior to TCPConnBlock.cb_lock acquisition.
|
||
|
{
|
||
|
uint ConnIndex = CONN_INDEX(ConnID);
|
||
|
uint ConnBlockId = CONN_BLOCKID(ConnID);
|
||
|
TCPConn *MatchingConn = NULL;
|
||
|
TCPConnBlock *ConnBlock;
|
||
|
|
||
|
if (ConnIndex < MAX_CONN_PER_BLOCK && ConnBlockId < MaxAllocatedConnBlocks) {
|
||
|
|
||
|
ConnBlock = ConnTable[ConnBlockId];
|
||
|
if (ConnBlock) {
|
||
|
MatchingConn = ConnBlock->cb_conn[ConnIndex];
|
||
|
}
|
||
|
if (MatchingConn != NULL) {
|
||
|
KeAcquireSpinLock(&ConnBlock->cb_lock, Irql);
|
||
|
CHECK_STRUCT(MatchingConn, tc);
|
||
|
if (MatchingConn->tc_inst != CONN_INST(ConnID)) {
|
||
|
MatchingConn = NULL;
|
||
|
KeReleaseSpinLock(&ConnBlock->cb_lock, *Irql);
|
||
|
}
|
||
|
}
|
||
|
} else
|
||
|
MatchingConn = NULL;
|
||
|
|
||
|
return MatchingConn;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* GetConnID - Get a ConnTable slot.
|
||
|
//
|
||
|
// Called during OpenConnection to find a free slot in the ConnTable and
|
||
|
// set it up with a connection.
|
||
|
// If successful, returns with TCPConnBlock.cb_lock held.
|
||
|
//
|
||
|
uint // Returns: A ConnId to use.
|
||
|
GetConnID(
|
||
|
TCPConn *NewConn, // Connection to enter into slot.
|
||
|
KIRQL *Irql0) // Receives IRQL prior to TCPConnBlock.cb_lock
|
||
|
// acquisition.
|
||
|
{
|
||
|
uint CurrConnID = NewConn->tc_connid;
|
||
|
uint i, j, BlockID, ConnIndex;
|
||
|
|
||
|
//
|
||
|
// If NewConn contains a valid ConnID and that location is unoccupied,
|
||
|
// reuse it.
|
||
|
//
|
||
|
if (CurrConnID != INVALID_CONN_ID &&
|
||
|
!NewConn->tc_ConnBlock->cb_conn[CONN_INDEX(CurrConnID)]) {
|
||
|
KeAcquireSpinLock(&NewConn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
//
|
||
|
// Reconfirm under lock that the location is unoccupied and, if so,
|
||
|
// claim it.
|
||
|
//
|
||
|
if (!NewConn->tc_ConnBlock->cb_conn[CONN_INDEX(CurrConnID)]) {
|
||
|
NewConn->tc_ConnBlock->cb_conn[CONN_INDEX(CurrConnID)] = NewConn;
|
||
|
NewConn->tc_ConnBlock->cb_freecons--;
|
||
|
NewConn->tc_inst = NewConn->tc_ConnBlock->cb_conninst++;
|
||
|
NewConn->tc_connid = MAKE_CONN_ID(CONN_INDEX(CurrConnID),
|
||
|
NewConn->tc_ConnBlock->cb_blockid,
|
||
|
NewConn->tc_inst);
|
||
|
return NewConn->tc_connid;
|
||
|
}
|
||
|
KeReleaseSpinLock(&NewConn->tc_ConnBlock->cb_lock, *Irql0);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// NewConn's last spot is taken; search from the block from which
|
||
|
// a ConnID was claimed most recently.
|
||
|
//
|
||
|
if (MaxAllocatedConnBlocks) {
|
||
|
//
|
||
|
// Capture the global counters without acquiring the lock.
|
||
|
//
|
||
|
uint TempMaxAllocatedConnBlocks = MaxAllocatedConnBlocks;
|
||
|
uint TempNextConnBlock = NextConnBlock;
|
||
|
|
||
|
for (i = 0; i < TempMaxAllocatedConnBlocks; i++) {
|
||
|
BlockID = (TempNextConnBlock + i) % TempMaxAllocatedConnBlocks;
|
||
|
|
||
|
if (!ConnTable[BlockID]->cb_freecons) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Reconfirm under lock that the TCPConnBlock has free slots.
|
||
|
//
|
||
|
KeAcquireSpinLock(&ConnTable[BlockID]->cb_lock, Irql0);
|
||
|
if (!ConnTable[BlockID]->cb_freecons) {
|
||
|
KeReleaseSpinLock(&ConnTable[BlockID]->cb_lock, *Irql0);
|
||
|
continue;
|
||
|
}
|
||
|
for (j = 0; j < MAX_CONN_PER_BLOCK; j++) {
|
||
|
ConnIndex = (ConnTable[BlockID]->cb_nextfree + j) %
|
||
|
MAX_CONN_PER_BLOCK;
|
||
|
if (ConnTable[BlockID]->cb_conn[ConnIndex]) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Found the free slot; fill it in.
|
||
|
//
|
||
|
ConnTable[BlockID]->cb_conn[ConnIndex] = NewConn;
|
||
|
ConnTable[BlockID]->cb_nextfree = ConnIndex + 1;
|
||
|
ConnTable[BlockID]->cb_freecons--;
|
||
|
if (!ConnTable[BlockID]->cb_freecons) {
|
||
|
InterlockedCompareExchange(&NextConnBlock,
|
||
|
TempNextConnBlock,
|
||
|
TempNextConnBlock + 1);
|
||
|
}
|
||
|
NewConn->tc_ConnBlock = ConnTable[BlockID];
|
||
|
NewConn->tc_inst = ConnTable[BlockID]->cb_conninst++;
|
||
|
NewConn->tc_connid = MAKE_CONN_ID(ConnIndex, BlockID,
|
||
|
NewConn->tc_inst);
|
||
|
return NewConn->tc_connid;
|
||
|
}
|
||
|
KeReleaseSpinLock(&ConnTable[BlockID]->cb_lock, *Irql0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The entire table is occupied; if we have room to grow,
|
||
|
// allocate a new block.
|
||
|
//
|
||
|
|
||
|
KeAcquireSpinLock(&ConnTableLock, Irql0);
|
||
|
if (MaxAllocatedConnBlocks < MaxConnBlocks) {
|
||
|
TCPConnBlock* ConnBlock;
|
||
|
BlockID = MaxAllocatedConnBlocks;
|
||
|
ConnBlock = ExAllocatePool(NonPagedPool, sizeof(TCPConnBlock));
|
||
|
if (ConnBlock) {
|
||
|
RtlZeroMemory(ConnBlock, sizeof(TCPConnBlock));
|
||
|
KeInitializeSpinLock(&ConnBlock->cb_lock);
|
||
|
|
||
|
KeAcquireSpinLockAtDpcLevel(&ConnBlock->cb_lock);
|
||
|
|
||
|
ConnBlock->cb_blockid = BlockID;
|
||
|
ConnBlock->cb_freecons = MAX_CONN_PER_BLOCK - 1;
|
||
|
ConnBlock->cb_nextfree = 1;
|
||
|
ConnBlock->cb_conninst = 2;
|
||
|
ConnBlock->cb_conn[0] = NewConn;
|
||
|
|
||
|
NewConn->tc_ConnBlock = ConnBlock;
|
||
|
NewConn->tc_inst = 1;
|
||
|
NewConn->tc_connid = MAKE_CONN_ID(0, BlockID, NewConn->tc_inst);
|
||
|
|
||
|
ConnTable[BlockID] = ConnBlock;
|
||
|
InterlockedIncrement(&MaxAllocatedConnBlocks);
|
||
|
|
||
|
KeReleaseSpinLockFromDpcLevel(&ConnTableLock);
|
||
|
|
||
|
return NewConn->tc_connid;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KeReleaseSpinLock(&ConnTableLock, *Irql0);
|
||
|
return INVALID_CONN_ID;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* FreeConnID - Free a ConnTable slot.
|
||
|
//
|
||
|
// Called when we're done with a ConnID. We assume the caller holds the lock
|
||
|
// on the TCPConnBlock when we are called.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
FreeConnID(
|
||
|
TCPConn *Conn) // Conn to be freed.
|
||
|
{
|
||
|
uint ConnIndex = CONN_INDEX(Conn->tc_connid); // Index into conn table.
|
||
|
uint BlockID = CONN_BLOCKID(Conn->tc_connid);
|
||
|
TCPConnBlock* ConnBlock = Conn->tc_ConnBlock;
|
||
|
|
||
|
ASSERT(ConnIndex < MAX_CONN_PER_BLOCK);
|
||
|
ASSERT(BlockID < MaxAllocatedConnBlocks);
|
||
|
ASSERT(ConnBlock->cb_conn[ConnIndex] != NULL);
|
||
|
|
||
|
if (ConnBlock->cb_conn[ConnIndex]) {
|
||
|
ConnBlock->cb_conn[ConnIndex] = NULL;
|
||
|
ConnBlock->cb_freecons++;
|
||
|
ConnBlock->cb_nextfree = ConnIndex;
|
||
|
ASSERT(ConnBlock->cb_freecons <= MAX_CONN_PER_BLOCK);
|
||
|
} else {
|
||
|
ASSERT(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//* MapIPError - Map an IP error to a TDI error.
|
||
|
//
|
||
|
// Called to map an input IP error code to a TDI error code. If we can't,
|
||
|
// we return the provided default.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Mapped TDI error.
|
||
|
MapIPError(
|
||
|
IP_STATUS IPError, // Error code to be mapped.
|
||
|
TDI_STATUS Default) // Default error code to return.
|
||
|
{
|
||
|
switch (IPError) {
|
||
|
|
||
|
case IP_DEST_NO_ROUTE:
|
||
|
return TDI_DEST_NET_UNREACH;
|
||
|
case IP_DEST_ADDR_UNREACHABLE:
|
||
|
return TDI_DEST_HOST_UNREACH;
|
||
|
case IP_UNRECOGNIZED_NEXT_HEADER:
|
||
|
return TDI_DEST_PROT_UNREACH;
|
||
|
case IP_DEST_PORT_UNREACHABLE:
|
||
|
return TDI_DEST_PORT_UNREACH;
|
||
|
default:
|
||
|
return Default;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//* FinishRemoveTCBFromConn - Finish removing a TCB from a conn structure.
|
||
|
//
|
||
|
// Called when we have the locks we need and we just want to pull the
|
||
|
// TCB off the connection.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
FinishRemoveTCBFromConn(
|
||
|
TCB *RemovedTCB) // TCB to be removed.
|
||
|
{
|
||
|
TCPConn *Conn;
|
||
|
AddrObj *AO;
|
||
|
KIRQL Irql;
|
||
|
TCPConnBlock *ConnBlock = NULL;
|
||
|
|
||
|
if (((Conn = RemovedTCB->tcb_conn) != NULL) &&
|
||
|
(Conn->tc_tcb == RemovedTCB)) {
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
ConnBlock = Conn->tc_ConnBlock;
|
||
|
|
||
|
KeAcquireSpinLock(&ConnBlock->cb_lock, &Irql);
|
||
|
|
||
|
AO = Conn->tc_ao;
|
||
|
|
||
|
if (AO != NULL) {
|
||
|
KeAcquireSpinLockAtDpcLevel(&AO->ao_lock);
|
||
|
if (AO_VALID(AO)) {
|
||
|
KeAcquireSpinLockAtDpcLevel(&RemovedTCB->tcb_lock);
|
||
|
|
||
|
// Need to double check this is still correct.
|
||
|
|
||
|
if (Conn == RemovedTCB->tcb_conn) {
|
||
|
// Everything still looks good.
|
||
|
REMOVEQ(&Conn->tc_q);
|
||
|
PUSHQ(&AO->ao_idleq, &Conn->tc_q);
|
||
|
} else
|
||
|
Conn = RemovedTCB->tcb_conn;
|
||
|
} else {
|
||
|
KeAcquireSpinLockAtDpcLevel(&RemovedTCB->tcb_lock);
|
||
|
Conn = RemovedTCB->tcb_conn;
|
||
|
}
|
||
|
|
||
|
KeReleaseSpinLockFromDpcLevel(&AO->ao_lock);
|
||
|
} else {
|
||
|
KeAcquireSpinLockAtDpcLevel(&RemovedTCB->tcb_lock);
|
||
|
Conn = RemovedTCB->tcb_conn;
|
||
|
}
|
||
|
|
||
|
if (Conn != NULL) {
|
||
|
if (Conn->tc_tcb == RemovedTCB)
|
||
|
Conn->tc_tcb = NULL;
|
||
|
else
|
||
|
ASSERT(Conn->tc_tcb == NULL);
|
||
|
}
|
||
|
|
||
|
KeReleaseSpinLockFromDpcLevel(&RemovedTCB->tcb_lock);
|
||
|
KeReleaseSpinLock(&ConnBlock->cb_lock, Irql);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//* RemoveTCBFromConn - Remove a TCB from a Conn structure.
|
||
|
//
|
||
|
// Called when we need to disassociate a TCB from a connection structure.
|
||
|
// All we do is get the appropriate locks and call FinishRemoveTCBFromConn.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
RemoveTCBFromConn(
|
||
|
TCB *RemovedTCB) // TCB to be removed.
|
||
|
{
|
||
|
CHECK_STRUCT(RemovedTCB, tcb);
|
||
|
|
||
|
FinishRemoveTCBFromConn(RemovedTCB);
|
||
|
}
|
||
|
|
||
|
|
||
|
//* RemoveConnFromTCB - Remove a conn from a TCB.
|
||
|
//
|
||
|
// Called when we want to break the final association between a connection
|
||
|
// and a TCB.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
RemoveConnFromTCB(
|
||
|
TCB *RemoveTCB) // TCB to be removed.
|
||
|
{
|
||
|
ConnDoneRtn DoneRtn = NULL;
|
||
|
KIRQL Irql;
|
||
|
TCPConn *Conn;
|
||
|
|
||
|
if ((Conn = RemoveTCB->tcb_conn) != NULL) {
|
||
|
KeAcquireSpinLock(&Conn->tc_ConnBlock->cb_lock, &Irql);
|
||
|
KeAcquireSpinLockAtDpcLevel(&RemoveTCB->tcb_lock);
|
||
|
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
if (--(Conn->tc_refcnt) == 0)
|
||
|
DoneRtn = Conn->tc_donertn;
|
||
|
|
||
|
RemoveTCB->tcb_conn = NULL;
|
||
|
KeReleaseSpinLockFromDpcLevel(&RemoveTCB->tcb_lock);
|
||
|
}
|
||
|
|
||
|
if (DoneRtn != NULL)
|
||
|
(*DoneRtn)(Conn, Irql);
|
||
|
else if (Conn) {
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//* CloseTCB - Close a TCB.
|
||
|
//
|
||
|
// Called when we are done with a TCB, and want to free it. We'll remove
|
||
|
// him from any tables that he's in, and destroy any outstanding requests.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
CloseTCB(
|
||
|
TCB *ClosedTCB, // TCB to be closed.
|
||
|
KIRQL OldIrql) // IRQL prior to acquiring TCB lock.
|
||
|
{
|
||
|
uchar OrigState = ClosedTCB->tcb_state;
|
||
|
TDI_STATUS Status;
|
||
|
uint OKToFree;
|
||
|
|
||
|
CHECK_STRUCT(ClosedTCB, tcb);
|
||
|
ASSERT(ClosedTCB->tcb_refcnt == 0);
|
||
|
ASSERT(ClosedTCB->tcb_state != TCB_CLOSED);
|
||
|
ASSERT(ClosedTCB->tcb_pending & DEL_PENDING);
|
||
|
|
||
|
//
|
||
|
// We'll check to make sure that our state isn't CLOSED. This should never
|
||
|
// happen, since nobody should call TryToCloseTCB when the state is
|
||
|
// closed, or take the reference count if we're closing. Nevertheless,
|
||
|
// we'll double check as a safety measure.
|
||
|
//
|
||
|
if (ClosedTCB->tcb_state == TCB_CLOSED) {
|
||
|
KeReleaseSpinLock(&ClosedTCB->tcb_lock, OldIrql);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Update SNMP counters. If we're in SYN-SENT or SYN-RCVD, this is a
|
||
|
// failed connection attempt. If we're in ESTABLISED or CLOSE-WAIT,
|
||
|
// treat this as an 'Established Reset' event.
|
||
|
//
|
||
|
if (ClosedTCB->tcb_state == TCB_SYN_SENT ||
|
||
|
ClosedTCB->tcb_state == TCB_SYN_RCVD)
|
||
|
TStats.ts_attemptfails++;
|
||
|
else
|
||
|
if (ClosedTCB->tcb_state == TCB_ESTAB ||
|
||
|
ClosedTCB->tcb_state == TCB_CLOSE_WAIT) {
|
||
|
TStats.ts_estabresets++;
|
||
|
TStats.ts_currestab--;
|
||
|
ASSERT(*(int *)&TStats.ts_currestab >= 0);
|
||
|
}
|
||
|
|
||
|
ClosedTCB->tcb_state = TCB_CLOSED;
|
||
|
KeReleaseSpinLockFromDpcLevel(&ClosedTCB->tcb_lock);
|
||
|
|
||
|
//
|
||
|
// Remove the TCB from it's associated TCPConn structure, if it has one.
|
||
|
//
|
||
|
FinishRemoveTCBFromConn(ClosedTCB);
|
||
|
|
||
|
KeAcquireSpinLockAtDpcLevel(&TCBTableLock);
|
||
|
KeAcquireSpinLockAtDpcLevel(&ClosedTCB->tcb_lock);
|
||
|
|
||
|
OKToFree = RemoveTCB(ClosedTCB);
|
||
|
|
||
|
//
|
||
|
// He's been pulled from the appropriate places so nobody can find him.
|
||
|
// Free the locks, and proceed to destroy any requests, etc.
|
||
|
//
|
||
|
KeReleaseSpinLockFromDpcLevel(&ClosedTCB->tcb_lock);
|
||
|
KeReleaseSpinLock(&TCBTableLock, OldIrql);
|
||
|
|
||
|
if (SYNC_STATE(OrigState) && !GRACEFUL_CLOSED_STATE(OrigState)) {
|
||
|
if (ClosedTCB->tcb_flags & NEED_RST)
|
||
|
SendRSTFromTCB(ClosedTCB);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// REVIEW: Is this the right place to drop the reference on our RCE?
|
||
|
// REVIEW: The IPv4 code called down to IP to close the RCE here.
|
||
|
//
|
||
|
if (ClosedTCB->tcb_rce != NULL)
|
||
|
ReleaseRCE(ClosedTCB->tcb_rce);
|
||
|
|
||
|
if (ClosedTCB->tcb_closereason & TCB_CLOSE_RST)
|
||
|
Status = TDI_CONNECTION_RESET;
|
||
|
else if (ClosedTCB->tcb_closereason & TCB_CLOSE_ABORTED)
|
||
|
Status = TDI_CONNECTION_ABORTED;
|
||
|
else if (ClosedTCB->tcb_closereason & TCB_CLOSE_TIMEOUT)
|
||
|
Status = MapIPError(ClosedTCB->tcb_error, TDI_TIMED_OUT);
|
||
|
else if (ClosedTCB->tcb_closereason & TCB_CLOSE_REFUSED)
|
||
|
Status = TDI_CONN_REFUSED;
|
||
|
else if (ClosedTCB->tcb_closereason & TCB_CLOSE_UNREACH)
|
||
|
Status = MapIPError(ClosedTCB->tcb_error, TDI_DEST_UNREACHABLE);
|
||
|
else
|
||
|
Status = TDI_SUCCESS;
|
||
|
|
||
|
//
|
||
|
// Now complete any outstanding requests on the TCB.
|
||
|
//
|
||
|
if (ClosedTCB->tcb_connreq != NULL) {
|
||
|
TCPConnReq *ConnReq = ClosedTCB->tcb_connreq;
|
||
|
|
||
|
CHECK_STRUCT(ConnReq, tcr);
|
||
|
|
||
|
(*ConnReq->tcr_req.tr_rtn)(ConnReq->tcr_req.tr_context, Status, 0);
|
||
|
FreeConnReq(ConnReq);
|
||
|
}
|
||
|
|
||
|
if (ClosedTCB->tcb_discwait != NULL) {
|
||
|
TCPConnReq *ConnReq = ClosedTCB->tcb_discwait;
|
||
|
|
||
|
CHECK_STRUCT(ConnReq, tcr);
|
||
|
|
||
|
(*ConnReq->tcr_req.tr_rtn)(ConnReq->tcr_req.tr_context, Status, 0);
|
||
|
FreeConnReq(ConnReq);
|
||
|
}
|
||
|
|
||
|
while (!EMPTYQ(&ClosedTCB->tcb_sendq)) {
|
||
|
TCPReq *Req;
|
||
|
TCPSendReq *SendReq;
|
||
|
long Result;
|
||
|
|
||
|
DEQUEUE(&ClosedTCB->tcb_sendq, Req, TCPReq, tr_q);
|
||
|
|
||
|
CHECK_STRUCT(Req, tr);
|
||
|
SendReq = (TCPSendReq *)Req;
|
||
|
CHECK_STRUCT(SendReq, tsr);
|
||
|
|
||
|
//
|
||
|
// Decrement the initial reference put on the buffer when it was
|
||
|
// allocated. This reference would have been decremented if the
|
||
|
// send had been acknowledged, but then the send would not still
|
||
|
// be on the tcb_sendq.
|
||
|
//
|
||
|
Result = InterlockedDecrement(&(SendReq->tsr_refcnt));
|
||
|
|
||
|
ASSERT(Result >= 0);
|
||
|
|
||
|
if (Result <= 0) {
|
||
|
// If we've sent directly from this send, NULL out the next
|
||
|
// pointer for the last buffer in the chain.
|
||
|
if (SendReq->tsr_lastbuf != NULL) {
|
||
|
NDIS_BUFFER_LINKAGE(SendReq->tsr_lastbuf) = NULL;
|
||
|
SendReq->tsr_lastbuf = NULL;
|
||
|
}
|
||
|
|
||
|
(*Req->tr_rtn)(Req->tr_context, Status, 0);
|
||
|
FreeSendReq(SendReq);
|
||
|
} else {
|
||
|
// The send request will be freed when all outstanding references
|
||
|
// to it have completed.
|
||
|
SendReq->tsr_req.tr_status = Status;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
while (ClosedTCB->tcb_rcvhead != NULL) {
|
||
|
TCPRcvReq *RcvReq;
|
||
|
|
||
|
RcvReq = ClosedTCB->tcb_rcvhead;
|
||
|
CHECK_STRUCT(RcvReq, trr);
|
||
|
ClosedTCB->tcb_rcvhead = RcvReq->trr_next;
|
||
|
(*RcvReq->trr_rtn)(RcvReq->trr_context, Status, 0);
|
||
|
FreeRcvReq(RcvReq);
|
||
|
}
|
||
|
|
||
|
while (ClosedTCB->tcb_exprcv != NULL) {
|
||
|
TCPRcvReq *RcvReq;
|
||
|
|
||
|
RcvReq = ClosedTCB->tcb_exprcv;
|
||
|
CHECK_STRUCT(RcvReq, trr);
|
||
|
ClosedTCB->tcb_exprcv = RcvReq->trr_next;
|
||
|
(*RcvReq->trr_rtn)(RcvReq->trr_context, Status, 0);
|
||
|
FreeRcvReq(RcvReq);
|
||
|
}
|
||
|
|
||
|
if (ClosedTCB->tcb_pendhead != NULL)
|
||
|
FreePacketChain(ClosedTCB->tcb_pendhead);
|
||
|
|
||
|
if (ClosedTCB->tcb_urgpending != NULL)
|
||
|
FreePacketChain(ClosedTCB->tcb_urgpending);
|
||
|
|
||
|
while (ClosedTCB->tcb_raq != NULL) {
|
||
|
TCPRAHdr *Hdr;
|
||
|
|
||
|
Hdr = ClosedTCB->tcb_raq;
|
||
|
CHECK_STRUCT(Hdr, trh);
|
||
|
ClosedTCB->tcb_raq = Hdr->trh_next;
|
||
|
if (Hdr->trh_buffer != NULL)
|
||
|
FreePacketChain(Hdr->trh_buffer);
|
||
|
|
||
|
ExFreePool(Hdr);
|
||
|
}
|
||
|
|
||
|
RemoveConnFromTCB(ClosedTCB);
|
||
|
|
||
|
if (OKToFree) {
|
||
|
FreeTCB(ClosedTCB);
|
||
|
} else {
|
||
|
KeAcquireSpinLock(&TCBTableLock, &OldIrql);
|
||
|
ClosedTCB->tcb_walkcount--;
|
||
|
if (ClosedTCB->tcb_walkcount == 0) {
|
||
|
FreeTCB(ClosedTCB);
|
||
|
}
|
||
|
KeReleaseSpinLock(&TCBTableLock, OldIrql);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TryToCloseTCB - Try to close a TCB.
|
||
|
//
|
||
|
// Called when we need to close a TCB, but don't know if we can.
|
||
|
// If the reference count is 0, we'll call CloseTCB to deal with it.
|
||
|
// Otherwise we'll set the DELETE_PENDING bit and deal with it when the
|
||
|
// ref. count goes to 0. We assume the TCB is locked when we are called.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
TryToCloseTCB (
|
||
|
TCB *ClosedTCB, // TCB to be closed.
|
||
|
uchar Reason, // Reason we're closing.
|
||
|
KIRQL PreLockIrql) // IRQL prior to acquiring the TCB lock.
|
||
|
{
|
||
|
CHECK_STRUCT(ClosedTCB, tcb);
|
||
|
ASSERT(ClosedTCB->tcb_state != TCB_CLOSED);
|
||
|
|
||
|
ClosedTCB->tcb_closereason |= Reason;
|
||
|
|
||
|
if (ClosedTCB->tcb_pending & DEL_PENDING) {
|
||
|
KeReleaseSpinLock(&ClosedTCB->tcb_lock, PreLockIrql);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ClosedTCB->tcb_pending |= DEL_PENDING;
|
||
|
ClosedTCB->tcb_slowcount++;
|
||
|
ClosedTCB->tcb_fastchk |= TCP_FLAG_SLOW;
|
||
|
|
||
|
if (ClosedTCB->tcb_refcnt == 0)
|
||
|
CloseTCB(ClosedTCB, PreLockIrql);
|
||
|
else {
|
||
|
KeReleaseSpinLock(&ClosedTCB->tcb_lock, PreLockIrql);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//* DerefTCB - Dereference a TCB.
|
||
|
//
|
||
|
// Called when we're done with a TCB, and want to let exclusive user
|
||
|
// have a shot. We dec. the refcount, and if it goes to zero and there
|
||
|
// are pending actions, we'll perform one of the pending actions.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
DerefTCB(
|
||
|
TCB *DoneTCB, // TCB to be dereffed.
|
||
|
KIRQL PreLockIrql) // IRQL prior to acquiring the TCB lock.
|
||
|
{
|
||
|
|
||
|
ASSERT(DoneTCB->tcb_refcnt != 0);
|
||
|
if (--DoneTCB->tcb_refcnt == 0) {
|
||
|
if (DoneTCB->tcb_pending == 0) {
|
||
|
KeReleaseSpinLock(&DoneTCB->tcb_lock, PreLockIrql);
|
||
|
return;
|
||
|
} else {
|
||
|
if (DoneTCB->tcb_pending & DEL_PENDING)
|
||
|
CloseTCB(DoneTCB, PreLockIrql);
|
||
|
else
|
||
|
DbgBreakPoint(); // Fatal condition.
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
KeReleaseSpinLock(&DoneTCB->tcb_lock, PreLockIrql);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* CalculateMSSForTCB - Update MSS, etc. after PMTU changes.
|
||
|
//
|
||
|
// Calculate our connection's MSS based on our PMTU, the sizes
|
||
|
// of various headers, and the remote side's advertised MSS.
|
||
|
// It's expected that this routine will be called whenever
|
||
|
// our cached copy of the PMTU has been updated to a new value.
|
||
|
//
|
||
|
void
|
||
|
CalculateMSSForTCB(
|
||
|
TCB *ThisTCB) // The TCB we're running our calculations on.
|
||
|
{
|
||
|
uint PMTU;
|
||
|
IPSecProc *IPSecToDo;
|
||
|
uint TrailerLength = 0;
|
||
|
uint IPSecBytes = 0;
|
||
|
uint Dummy;
|
||
|
|
||
|
ASSERT(ThisTCB->tcb_pmtu != 0); // Should be set before entering.
|
||
|
|
||
|
//
|
||
|
// First check that the PMTU size is reasonable. IP won't
|
||
|
// let it get below minimum, but we have our own maximum since
|
||
|
// currently TCP can only handle an MSS that fits in 16 bits.
|
||
|
// TBD: If we add IPv6 Jumbogram support, we should also add LFN
|
||
|
// TBD: support to TCP and change this to handle a larger MSS.
|
||
|
//
|
||
|
PMTU = ThisTCB->tcb_pmtu;
|
||
|
if (PMTU > 65535) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_RARE,
|
||
|
"TCPSend: PMTU update value too large %u\n", PMTU));
|
||
|
PMTU = 65535;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Determine size of IPSec headers, if any.
|
||
|
//
|
||
|
IPSecToDo = OutboundSPLookup(&ThisTCB->tcb_saddr, &ThisTCB->tcb_daddr,
|
||
|
IP_PROTOCOL_TCP,
|
||
|
net_short(ThisTCB->tcb_sport),
|
||
|
net_short(ThisTCB->tcb_dport),
|
||
|
ThisTCB->tcb_rce->NTE->IF, &Dummy);
|
||
|
if (IPSecToDo != NULL) {
|
||
|
//
|
||
|
// Calculate the space needed for the IPSec headers.
|
||
|
//
|
||
|
IPSecBytes = IPSecBytesToInsert(IPSecToDo, &Dummy, &TrailerLength);
|
||
|
FreeIPSecToDo(IPSecToDo, IPSecToDo->BundleSize);
|
||
|
IPSecBytes += TrailerLength;
|
||
|
}
|
||
|
IF_TCPDBG(TCP_DEBUG_MSS) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_TCPDBG,
|
||
|
"CalculateMSSForTCB: IPSecBytes is %u\n", IPSecBytes));
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Subtract out the header sizes to yield the TCP MSS.
|
||
|
// If there is an ESP trailer on this connection, round down
|
||
|
// the MSS to allow the trailer to end on a 4-byte boundary.
|
||
|
//
|
||
|
PMTU -= sizeof(IPv6Header) + sizeof(TCPHeader) + IPSecBytes;
|
||
|
if (TrailerLength)
|
||
|
PMTU -= (PMTU & 3);
|
||
|
|
||
|
//
|
||
|
// Don't let MSS exceed what our peer advertised, regardless of how
|
||
|
// large the Path MTU is.
|
||
|
//
|
||
|
IF_TCPDBG(TCP_DEBUG_MSS) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_TCPDBG,
|
||
|
"CalculateMSSForTCB: Old MSS is %u ", ThisTCB->tcb_mss));
|
||
|
}
|
||
|
ThisTCB->tcb_mss = (ushort)MIN(PMTU, ThisTCB->tcb_remmss);
|
||
|
IF_TCPDBG(TCP_DEBUG_MSS) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_TCPDBG,
|
||
|
"New MSS is %u\n", ThisTCB->tcb_mss));
|
||
|
}
|
||
|
|
||
|
ASSERT(ThisTCB->tcb_mss != 0);
|
||
|
|
||
|
//
|
||
|
// We don't want our Congestion Window to be smaller than one maximum
|
||
|
// segment, so we may need to increase it when our MSS grows.
|
||
|
//
|
||
|
if (ThisTCB->tcb_cwin < ThisTCB->tcb_mss) {
|
||
|
ThisTCB->tcb_cwin = ThisTCB->tcb_mss;
|
||
|
|
||
|
//
|
||
|
// Make sure the slow start threshold is at
|
||
|
// least 2 segments.
|
||
|
//
|
||
|
if (ThisTCB->tcb_ssthresh < ((uint) ThisTCB->tcb_mss * 2)) {
|
||
|
ThisTCB->tcb_ssthresh = ThisTCB->tcb_mss * 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
//** TdiOpenConnection - Open a connection.
|
||
|
//
|
||
|
// This is the TDI Open Connection entry point. We open a connection,
|
||
|
// and save the caller's connection context. A TCPConn structure is allocated
|
||
|
// here, but a TCB isn't allocated until the Connect or Listen is done.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of attempt to open connection.
|
||
|
TdiOpenConnection(
|
||
|
PTDI_REQUEST Request, // This TDI request.
|
||
|
PVOID Context) // Connection context to be save for connection.
|
||
|
{
|
||
|
TCPConn *NewConn; // The newly opened connection.
|
||
|
KIRQL OldIrql; // Irql prior to acquiring TCPConnBlock lock.
|
||
|
uint ConnID; // New ConnID.
|
||
|
TDI_STATUS Status; // Status of this request.
|
||
|
|
||
|
NewConn = ExAllocatePool(NonPagedPool, sizeof(TCPConn));
|
||
|
|
||
|
if (NewConn != NULL) {
|
||
|
//
|
||
|
// We allocated a connection.
|
||
|
//
|
||
|
RtlZeroMemory(NewConn, sizeof(TCPConn));
|
||
|
#if DBG
|
||
|
NewConn->tc_sig = tc_signature;
|
||
|
#endif
|
||
|
NewConn->tc_tcb = NULL;
|
||
|
NewConn->tc_ao = NULL;
|
||
|
NewConn->tc_context = Context;
|
||
|
NewConn->tc_connid = INVALID_CONN_ID;
|
||
|
|
||
|
ConnID = GetConnID(NewConn, &OldIrql);
|
||
|
if (ConnID != INVALID_CONN_ID) {
|
||
|
//
|
||
|
// We successfully got a ConnID.
|
||
|
//
|
||
|
Request->Handle.ConnectionContext = (CONNECTION_CONTEXT)UIntToPtr(ConnID);
|
||
|
NewConn->tc_refcnt = 0;
|
||
|
NewConn->tc_flags = 0;
|
||
|
NewConn->tc_tcbflags = NAGLING | (BSDUrgent ? BSD_URGENT : 0);
|
||
|
if (DefaultRcvWin != 0) {
|
||
|
NewConn->tc_window = DefaultRcvWin;
|
||
|
NewConn->tc_flags |= CONN_WINSET;
|
||
|
} else
|
||
|
NewConn->tc_window = DEFAULT_RCV_WIN;
|
||
|
|
||
|
NewConn->tc_donertn = DummyDone;
|
||
|
NewConn->tc_owningpid = HandleToUlong(PsGetCurrentProcessId());
|
||
|
Status = TDI_SUCCESS;
|
||
|
KeReleaseSpinLock(&NewConn->tc_ConnBlock->cb_lock, OldIrql);
|
||
|
} else {
|
||
|
ExFreePool(NewConn);
|
||
|
Status = TDI_NO_RESOURCES;
|
||
|
}
|
||
|
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Couldn't get a connection.
|
||
|
//
|
||
|
return TDI_NO_RESOURCES;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* RemoveConnFromAO - Remove a connection from an AddrObj.
|
||
|
//
|
||
|
// A little utility routine to remove a connection from an AddrObj.
|
||
|
// We run down the connections on the AO, and when we find him we splice
|
||
|
// him out. We assume the caller holds the locks on the AddrObj and the
|
||
|
// TCPConnBlock lock.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
RemoveConnFromAO(
|
||
|
AddrObj *AO, // AddrObj to remove from.
|
||
|
TCPConn *Conn) // Conn to remove.
|
||
|
{
|
||
|
CHECK_STRUCT(AO, ao);
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
REMOVEQ(&Conn->tc_q);
|
||
|
Conn->tc_ao = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TdiCloseConnection - Close a connection.
|
||
|
//
|
||
|
// Called when the user is done with a connection, and wants to close it.
|
||
|
// We look the connection up in our table, and if we find it we'll remove
|
||
|
// the connection from the AddrObj it's associate with (if any). If there's
|
||
|
// a TCB associated with the connection we'll close it also.
|
||
|
//
|
||
|
// There are some interesting wrinkles related to closing while a TCB
|
||
|
// is still referencing the connection (i.e. tc_refcnt != 0) or while a
|
||
|
// disassociate address is in progress. See below for more details.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of attempt to close.
|
||
|
TdiCloseConnection(
|
||
|
PTDI_REQUEST Request) // Request identifying connection to be closed.
|
||
|
{
|
||
|
uint ConnID = PtrToUlong(Request->Handle.ConnectionContext);
|
||
|
KIRQL Irql0;
|
||
|
TCPConn *Conn;
|
||
|
TDI_STATUS Status;
|
||
|
|
||
|
//
|
||
|
// We have the locks we need. Try to find a connection.
|
||
|
//
|
||
|
Conn = GetConnFromConnID(ConnID, &Irql0);
|
||
|
|
||
|
if (Conn != NULL) {
|
||
|
KIRQL Irql1;
|
||
|
TCB *ConnTCB;
|
||
|
|
||
|
//
|
||
|
// We found the connection. Free the ConnID and mark the connection
|
||
|
// as closing.
|
||
|
//
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
FreeConnID(Conn);
|
||
|
|
||
|
Conn->tc_flags |= CONN_CLOSING;
|
||
|
|
||
|
//
|
||
|
// See if there's a TCB referencing this connection.
|
||
|
// If there is, we'll need to wait until he's done before closing him.
|
||
|
// We'll hurry the process along if we still have a pointer to him.
|
||
|
//
|
||
|
if (Conn->tc_refcnt != 0) {
|
||
|
RequestCompleteRoutine Rtn;
|
||
|
PVOID Context;
|
||
|
|
||
|
//
|
||
|
// A connection still references him. Save the current rtn stuff
|
||
|
// in case we are in the middle of disassociating him from an
|
||
|
// address, and store the caller's callback routine and our done
|
||
|
// routine.
|
||
|
//
|
||
|
Rtn = Conn->tc_rtn;
|
||
|
Context = Conn->tc_rtncontext;
|
||
|
|
||
|
Conn->tc_rtn = Request->RequestNotifyObject;
|
||
|
Conn->tc_rtncontext = Request->RequestContext;
|
||
|
Conn->tc_donertn = CloseDone;
|
||
|
|
||
|
//
|
||
|
// See if we're in the middle of disassociating him.
|
||
|
//
|
||
|
if (Conn->tc_flags & CONN_DISACC) {
|
||
|
|
||
|
//
|
||
|
// We are disassociating him. We'll free the conn table lock
|
||
|
// now and fail the disassociate request. Note that when
|
||
|
// we free the lock the refcount could go to zero. This is
|
||
|
// OK, because we've already stored the neccessary info. in
|
||
|
// the connection so the caller will get called back if it
|
||
|
// does. From this point out we return PENDING, so a callback
|
||
|
// is OK. We've marked him as closing, so the disassoc done
|
||
|
// routine will bail out if we've interrupted him. If the ref.
|
||
|
// count does go to zero, Conn->tc_tcb would have to be NULL,
|
||
|
// so in that case we'll just fall out of this routine.
|
||
|
//
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
(*Rtn)(Context, (uint) TDI_REQ_ABORTED, 0);
|
||
|
KeAcquireSpinLock(&Conn->tc_ConnBlock->cb_lock, &Irql0);
|
||
|
}
|
||
|
|
||
|
ConnTCB = Conn->tc_tcb;
|
||
|
if (ConnTCB != NULL) {
|
||
|
CHECK_STRUCT(ConnTCB, tcb);
|
||
|
//
|
||
|
// We have a TCB. Take the lock on him and get ready to
|
||
|
// close him.
|
||
|
//
|
||
|
KeAcquireSpinLock(&ConnTCB->tcb_lock, &Irql1);
|
||
|
if (ConnTCB->tcb_state != TCB_CLOSED) {
|
||
|
ConnTCB->tcb_flags |= NEED_RST;
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
if (!CLOSING(ConnTCB))
|
||
|
TryToCloseTCB(ConnTCB, TCB_CLOSE_ABORTED, Irql0);
|
||
|
else
|
||
|
KeReleaseSpinLock(&ConnTCB->tcb_lock, Irql0);
|
||
|
return TDI_PENDING;
|
||
|
} else {
|
||
|
//
|
||
|
// He's already closing. This should be harmless, but
|
||
|
// check this case.
|
||
|
//
|
||
|
KeReleaseSpinLock(&ConnTCB->tcb_lock, Irql1);
|
||
|
}
|
||
|
}
|
||
|
Status = TDI_PENDING;
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// We have a connection that we can close. Finish the close.
|
||
|
//
|
||
|
Conn->tc_rtn = DummyCmplt;
|
||
|
CloseDone(Conn, Irql0);
|
||
|
return TDI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
|
||
|
} else
|
||
|
Status = TDI_INVALID_CONNECTION;
|
||
|
|
||
|
//
|
||
|
// We're done with the connection. Go ahead and free him.
|
||
|
//
|
||
|
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TdiAssociateAddress - Associate an address with a connection.
|
||
|
//
|
||
|
// Called to associate an address with a connection. We do a minimal
|
||
|
// amount of sanity checking, and then put the connection on the AddrObj's
|
||
|
// list.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of attempt to associate.
|
||
|
TdiAssociateAddress(
|
||
|
PTDI_REQUEST Request, // Structure for this request.
|
||
|
HANDLE AddrHandle) // Address handle to associate connection with.
|
||
|
{
|
||
|
KIRQL Irql0, Irql1; // One per lock nesting level.
|
||
|
AddrObj *AO;
|
||
|
uint ConnID = PtrToUlong(Request->Handle.ConnectionContext);
|
||
|
TCPConn *Conn;
|
||
|
TDI_STATUS Status;
|
||
|
|
||
|
AO = (AddrObj *)AddrHandle;
|
||
|
CHECK_STRUCT(AO, ao);
|
||
|
|
||
|
Conn = GetConnFromConnID(ConnID, &Irql0);
|
||
|
KeAcquireSpinLock(&AO->ao_lock, &Irql1);
|
||
|
if (!AO_VALID(AO)) {
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql1);
|
||
|
if (Conn != NULL) {
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
}
|
||
|
return TDI_INVALID_PARAMETER;
|
||
|
}
|
||
|
|
||
|
if (Conn != NULL) {
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
if (Conn->tc_ao != NULL) {
|
||
|
//
|
||
|
// It's already associated. Error out.
|
||
|
//
|
||
|
KdBreakPoint();
|
||
|
Status = TDI_ALREADY_ASSOCIATED;
|
||
|
} else {
|
||
|
Conn->tc_ao = AO;
|
||
|
ASSERT(Conn->tc_tcb == NULL);
|
||
|
PUSHQ(&AO->ao_idleq, &Conn->tc_q);
|
||
|
Status = TDI_SUCCESS;
|
||
|
}
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql1);
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
return Status;
|
||
|
} else
|
||
|
Status = TDI_INVALID_CONNECTION;
|
||
|
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql1);
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TdiDisAssociateAddress - Disassociate a connection from an address.
|
||
|
//
|
||
|
// The TDI entry point to disassociate a connection from an address. The
|
||
|
// connection must actually be associated and not connected to anything.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of request.
|
||
|
TdiDisAssociateAddress(
|
||
|
PTDI_REQUEST Request) // Structure for this request.
|
||
|
{
|
||
|
uint ConnID = PtrToUlong(Request->Handle.ConnectionContext);
|
||
|
KIRQL Irql0, Irql1, Irql2; // One per lock nesting level.
|
||
|
TCPConn *Conn;
|
||
|
AddrObj *AO;
|
||
|
TDI_STATUS Status;
|
||
|
|
||
|
KeAcquireSpinLock(&AddrObjTableLock, &Irql0);
|
||
|
Conn = GetConnFromConnID(ConnID, &Irql1);
|
||
|
|
||
|
if (Conn != NULL) {
|
||
|
//
|
||
|
// The connection actually exists!
|
||
|
//
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
AO = Conn->tc_ao;
|
||
|
if (AO != NULL) {
|
||
|
CHECK_STRUCT(AO, ao);
|
||
|
//
|
||
|
// And it's associated.
|
||
|
//
|
||
|
KeAcquireSpinLock(&AO->ao_lock, &Irql2);
|
||
|
//
|
||
|
// If there's no connection currently active, go ahead and remove
|
||
|
// him from the AddrObj. If a connection is active error the
|
||
|
// request out.
|
||
|
//
|
||
|
if (Conn->tc_tcb == NULL) {
|
||
|
if (Conn->tc_refcnt == 0) {
|
||
|
RemoveConnFromAO(AO, Conn);
|
||
|
Status = TDI_SUCCESS;
|
||
|
} else {
|
||
|
//
|
||
|
// He shouldn't be closing, or we couldn't have found him.
|
||
|
//
|
||
|
ASSERT(!(Conn->tc_flags & CONN_CLOSING));
|
||
|
|
||
|
Conn->tc_rtn = Request->RequestNotifyObject;
|
||
|
Conn->tc_rtncontext = Request->RequestContext;
|
||
|
Conn->tc_donertn = DisassocDone;
|
||
|
Conn->tc_flags |= CONN_DISACC;
|
||
|
Status = TDI_PENDING;
|
||
|
}
|
||
|
|
||
|
} else
|
||
|
Status = TDI_CONNECTION_ACTIVE;
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql2);
|
||
|
} else
|
||
|
Status = TDI_NOT_ASSOCIATED;
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
} else
|
||
|
Status = TDI_INVALID_CONNECTION;
|
||
|
|
||
|
KeReleaseSpinLock(&AddrObjTableLock, Irql0);
|
||
|
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* ProcessUserOptions - Process options from the user.
|
||
|
//
|
||
|
// A utility routine to process options from the user. We fill in the
|
||
|
// optinfo structure, and if we have options we call ip to check on them.
|
||
|
//
|
||
|
TDI_STATUS // Returns: TDI_STATUS of attempt.
|
||
|
ProcessUserOptions(
|
||
|
PTDI_CONNECTION_INFORMATION Info) // Contains options to be processed.
|
||
|
{
|
||
|
#if 0
|
||
|
TDI_STATUS Status;
|
||
|
|
||
|
if (Info != NULL && Info->Options != NULL) {
|
||
|
IP_STATUS OptStatus;
|
||
|
|
||
|
// REVIEW: IPv4 had code here to call into IP to copy options here.
|
||
|
|
||
|
if (OptStatus != IP_SUCCESS) {
|
||
|
if (OptStatus == IP_NO_RESOURCES)
|
||
|
Status = TDI_NO_RESOURCES;
|
||
|
else
|
||
|
Status = TDI_BAD_OPTION;
|
||
|
} else
|
||
|
Status = TDI_SUCCESS;
|
||
|
} else {
|
||
|
Status = TDI_SUCCESS;
|
||
|
}
|
||
|
|
||
|
return Status;
|
||
|
#else
|
||
|
return TDI_SUCCESS;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
//* InitTCBFromConn - Initialize a TCB from information in a Connection.
|
||
|
//
|
||
|
// Called from Connect and Listen processing to initialize a new TCB from
|
||
|
// information in the connection. We assume the AddrObjTableLock and
|
||
|
// TCPConnBlock locks are held when we are called, or that the caller has some
|
||
|
// other way of making sure that the referenced AO doesn't go away in the
|
||
|
// middle of operation.
|
||
|
//
|
||
|
// Input: Conn - Connection to initialize from.
|
||
|
// NewTCB - TCB to be initialized.
|
||
|
// Addr - Remote addressing and option info for NewTCB.
|
||
|
// AOLocked - True if the called has the address object locked.
|
||
|
//
|
||
|
|
||
|
//
|
||
|
TDI_STATUS // Returns: TDI_STATUS of init attempt.
|
||
|
InitTCBFromConn(
|
||
|
TCPConn *Conn, // Connection to initialize from.
|
||
|
TCB *NewTCB, // TCB to be initialized.
|
||
|
PTDI_CONNECTION_INFORMATION Addr, // Remove addr info, etc. for NewTCB.
|
||
|
uint AOLocked) // True if caller has addr object lock.
|
||
|
{
|
||
|
KIRQL OldIrql;
|
||
|
TDI_STATUS Status;
|
||
|
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
//
|
||
|
// We have a connection. Make sure it's associated with an address and
|
||
|
// doesn't already have a TCB attached.
|
||
|
//
|
||
|
if (Conn->tc_flags & CONN_INVALID)
|
||
|
return TDI_INVALID_CONNECTION;
|
||
|
|
||
|
if (Conn->tc_tcb == NULL) {
|
||
|
AddrObj *ConnAO;
|
||
|
|
||
|
ConnAO = Conn->tc_ao;
|
||
|
if (ConnAO != NULL) {
|
||
|
CHECK_STRUCT(ConnAO, ao);
|
||
|
|
||
|
if (!AOLocked) {
|
||
|
KeAcquireSpinLock(&ConnAO->ao_lock, &OldIrql);
|
||
|
}
|
||
|
if (!(NewTCB->tcb_flags & ACCEPT_PENDING)) {
|
||
|
//
|
||
|
// These fields are already initialized
|
||
|
// when ACCEPT_PENDING is on.
|
||
|
//
|
||
|
NewTCB->tcb_saddr = ConnAO->ao_addr;
|
||
|
NewTCB->tcb_sscope_id = ConnAO->ao_scope_id;
|
||
|
NewTCB->tcb_sport = ConnAO->ao_port;
|
||
|
NewTCB->tcb_defaultwin = Conn->tc_window;
|
||
|
NewTCB->tcb_rcvwin = Conn->tc_window;
|
||
|
}
|
||
|
|
||
|
NewTCB->tcb_rcvind = ConnAO->ao_rcv;
|
||
|
NewTCB->tcb_ricontext = ConnAO->ao_rcvcontext;
|
||
|
if (NewTCB->tcb_rcvind == NULL)
|
||
|
NewTCB->tcb_rcvhndlr = PendData;
|
||
|
else
|
||
|
NewTCB->tcb_rcvhndlr = IndicateData;
|
||
|
|
||
|
NewTCB->tcb_conncontext = Conn->tc_context;
|
||
|
NewTCB->tcb_flags |= Conn->tc_tcbflags;
|
||
|
|
||
|
if (Conn->tc_flags & CONN_WINSET)
|
||
|
NewTCB->tcb_flags |= WINDOW_SET;
|
||
|
|
||
|
if (NewTCB->tcb_flags & KEEPALIVE) {
|
||
|
NewTCB->tcb_alive = TCPTime;
|
||
|
NewTCB->tcb_kacount = 0;
|
||
|
}
|
||
|
|
||
|
if (!AOLocked) {
|
||
|
KeReleaseSpinLock(&ConnAO->ao_lock, OldIrql);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we've been given options, we need to process them now.
|
||
|
//
|
||
|
if (Addr != NULL && Addr->Options != NULL)
|
||
|
NewTCB->tcb_flags |= CLIENT_OPTIONS;
|
||
|
Status = ProcessUserOptions(Addr);
|
||
|
|
||
|
return Status;
|
||
|
} else
|
||
|
return TDI_NOT_ASSOCIATED;
|
||
|
} else
|
||
|
return TDI_CONNECTION_ACTIVE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TdiConnect - Establish a connection.
|
||
|
//
|
||
|
// The TDI connection establishment routine. Called when the client wants to
|
||
|
// establish a connection, we validate his incoming parameters and kick
|
||
|
// things off by sending a SYN.
|
||
|
//
|
||
|
// Note: The format of the timeout (TO) parameter is system specific -
|
||
|
// we use a macro to convert to ticks.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of attempt to connect.
|
||
|
TdiConnect(
|
||
|
PTDI_REQUEST Request, // This command request.
|
||
|
void *TO, // How long to wait for request.
|
||
|
PTDI_CONNECTION_INFORMATION RequestAddr, // Describes the destination.
|
||
|
PTDI_CONNECTION_INFORMATION ReturnAddr) // Where to return information.
|
||
|
{
|
||
|
TCPConnReq *ConnReq; // Connection request to use.
|
||
|
IPv6Addr DestAddr;
|
||
|
ulong DestScopeId;
|
||
|
ushort DestPort;
|
||
|
TCPConn *Conn;
|
||
|
TCB *NewTCB;
|
||
|
uint ConnID = PtrToUlong(Request->Handle.ConnectionContext);
|
||
|
KIRQL Irql0, Irql1, Irql2; // One per lock nesting level.
|
||
|
AddrObj *AO;
|
||
|
TDI_STATUS Status;
|
||
|
IP_STATUS IPStatus;
|
||
|
ushort MSS;
|
||
|
TCP_TIME *Timeout;
|
||
|
NetTableEntry *NTE;
|
||
|
NetTableEntryOrInterface *NTEorIF;
|
||
|
int WildcardSourceAddress = FALSE;
|
||
|
|
||
|
//
|
||
|
// First, get and validate the remote address.
|
||
|
//
|
||
|
if (RequestAddr == NULL || RequestAddr->RemoteAddress == NULL ||
|
||
|
!GetAddress((PTRANSPORT_ADDRESS)RequestAddr->RemoteAddress, &DestAddr,
|
||
|
&DestScopeId, &DestPort))
|
||
|
return TDI_BAD_ADDR;
|
||
|
|
||
|
//
|
||
|
// REVIEW: IPv4 performed other remote address sanity checks here.
|
||
|
// REVIEW: E.g., should we check that remote addr isn't multicast?
|
||
|
//
|
||
|
|
||
|
//
|
||
|
// REVIEW: I can't find an RFC which states 0 is not a valid port number.
|
||
|
//
|
||
|
if (DestPort == 0)
|
||
|
return TDI_BAD_ADDR;
|
||
|
|
||
|
//
|
||
|
// Get a connection request. If we can't, bail out now.
|
||
|
//
|
||
|
ConnReq = GetConnReq();
|
||
|
if (ConnReq == NULL)
|
||
|
return TDI_NO_RESOURCES;
|
||
|
|
||
|
//
|
||
|
// Get a TCB, assuming we'll need one.
|
||
|
//
|
||
|
NewTCB = AllocTCB();
|
||
|
if (NewTCB == NULL) {
|
||
|
// Couldn't get a TCB.
|
||
|
FreeConnReq(ConnReq);
|
||
|
return TDI_NO_RESOURCES;
|
||
|
}
|
||
|
|
||
|
Timeout = (TCP_TIME *)TO;
|
||
|
|
||
|
if (Timeout != NULL && !INFINITE_CONN_TO(*Timeout)) {
|
||
|
ulong Ticks = TCP_TIME_TO_TICKS(*Timeout);
|
||
|
|
||
|
if (Ticks > MAX_CONN_TO_TICKS)
|
||
|
Ticks = MAX_CONN_TO_TICKS;
|
||
|
else
|
||
|
Ticks++;
|
||
|
ConnReq->tcr_timeout = (ushort)Ticks;
|
||
|
} else
|
||
|
ConnReq->tcr_timeout = 0;
|
||
|
|
||
|
ConnReq->tcr_flags = 0;
|
||
|
ConnReq->tcr_conninfo = ReturnAddr;
|
||
|
ConnReq->tcr_addrinfo = NULL;
|
||
|
ConnReq->tcr_req.tr_rtn = Request->RequestNotifyObject;
|
||
|
ConnReq->tcr_req.tr_context = Request->RequestContext;
|
||
|
NewTCB->tcb_daddr = DestAddr;
|
||
|
NewTCB->tcb_dscope_id = DestScopeId;
|
||
|
NewTCB->tcb_dport = DestPort;
|
||
|
|
||
|
//
|
||
|
// Now find the real connection.
|
||
|
//
|
||
|
KeAcquireSpinLock(&AddrObjTableLock, &Irql0);
|
||
|
Conn = GetConnFromConnID(ConnID, &Irql1);
|
||
|
if (Conn != NULL) {
|
||
|
uint Inserted;
|
||
|
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
//
|
||
|
// We found the connection. Check for an associated address object.
|
||
|
//
|
||
|
AO = Conn->tc_ao;
|
||
|
if (AO != NULL) {
|
||
|
KeAcquireSpinLock(&AO->ao_lock, &Irql2);
|
||
|
|
||
|
CHECK_STRUCT(AO, ao);
|
||
|
|
||
|
Status = InitTCBFromConn(Conn, NewTCB, RequestAddr, TRUE);
|
||
|
if (Status == TDI_SUCCESS) {
|
||
|
//
|
||
|
// We've initialized our TCB. Mark it that we initiated this
|
||
|
// connection (i.e. active open). Also, we're done with the
|
||
|
// AddrObjTable, so we can free it's lock.
|
||
|
//
|
||
|
NewTCB->tcb_flags |= ACTIVE_OPEN;
|
||
|
KeReleaseSpinLock(&AddrObjTableLock, Irql2);
|
||
|
|
||
|
//
|
||
|
// Determine NTE to send on (if user cares).
|
||
|
//
|
||
|
if (IsUnspecified(&NewTCB->tcb_saddr)) {
|
||
|
//
|
||
|
// Caller didn't specify a source address.
|
||
|
// Let the routing code pick one.
|
||
|
//
|
||
|
NTE = NULL;
|
||
|
NTEorIF = NULL;
|
||
|
WildcardSourceAddress = TRUE;
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// Our TCB has a specific source address. Determine
|
||
|
// which NTE corresponds to it and the scope id.
|
||
|
//
|
||
|
NTE = FindNetworkWithAddress(&NewTCB->tcb_saddr,
|
||
|
NewTCB->tcb_sscope_id);
|
||
|
if (NTE == NULL) {
|
||
|
//
|
||
|
// Bad source address. We don't have a network with
|
||
|
// the requested address. Error out.
|
||
|
//
|
||
|
// REVIEW: Will the AddrObj code even let this happen?
|
||
|
//
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_USER_ERROR,
|
||
|
"TdiConnect: Bad source address\n"));
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql1);
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
Status = TDI_BAD_ADDR;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
NTEorIF = CastFromNTE(NTE);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the route.
|
||
|
//
|
||
|
ASSERT(NewTCB->tcb_rce == NULL);
|
||
|
IPStatus = RouteToDestination(&DestAddr, DestScopeId,
|
||
|
NTEorIF, RTD_FLAG_NORMAL,
|
||
|
&NewTCB->tcb_rce);
|
||
|
if (NTE != NULL)
|
||
|
ReleaseNTE(NTE);
|
||
|
NTE = NULL; // Protect against accidental use.
|
||
|
if (IPStatus != IP_SUCCESS) {
|
||
|
//
|
||
|
// Failed to get a route to the destination. Error out.
|
||
|
//
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
|
||
|
"TdiConnect: Failed to get route to dest.\n"));
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql1);
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
if ((IPStatus == IP_PARAMETER_PROBLEM) ||
|
||
|
(IPStatus == IP_BAD_ROUTE))
|
||
|
Status = TDI_BAD_ADDR;
|
||
|
else if (IPStatus == IP_NO_RESOURCES)
|
||
|
Status = TDI_NO_RESOURCES;
|
||
|
else
|
||
|
Status = TDI_DEST_UNREACHABLE;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
ASSERT(NewTCB->tcb_rce != NULL);
|
||
|
if (IsDisconnectedAndNotLoopbackRCE(NewTCB->tcb_rce)) {
|
||
|
//
|
||
|
// Fail new connection requests for TCBs with a
|
||
|
// disconnected outgoing interface, except when a
|
||
|
// loopback route is used.
|
||
|
//
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
|
||
|
"TdiConnect: Interface disconnected.\n"));
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql1);
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
|
||
|
//
|
||
|
// Drop the reference on the route we obtained.
|
||
|
//
|
||
|
ReleaseRCE(NewTCB->tcb_rce);
|
||
|
|
||
|
Status = TDI_INVALID_STATE;
|
||
|
goto error;
|
||
|
}
|
||
|
|
||
|
|
||
|
//
|
||
|
// OK, we got a route. Enter the TCB into the connection
|
||
|
// and send a SYN.
|
||
|
//
|
||
|
KeAcquireSpinLock(&NewTCB->tcb_lock, &Irql2);
|
||
|
Conn->tc_tcb = NewTCB;
|
||
|
Conn->tc_refcnt++;
|
||
|
NewTCB->tcb_conn = Conn;
|
||
|
NewTCB->tcb_connid = Conn->tc_connid;
|
||
|
REMOVEQ(&Conn->tc_q);
|
||
|
ENQUEUE(&AO->ao_activeq, &Conn->tc_q);
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql2);
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql1);
|
||
|
|
||
|
//
|
||
|
// Initialize path-specific TCB settings, based on the RCE:
|
||
|
//
|
||
|
// If packets on the path will be looped back in software,
|
||
|
// don't use the Nagle algorithm for this TCB.
|
||
|
//
|
||
|
if (IsLoopbackRCE(NewTCB->tcb_rce)) {
|
||
|
NewTCB->tcb_flags &= ~NAGLING;
|
||
|
}
|
||
|
|
||
|
if (WildcardSourceAddress) {
|
||
|
//
|
||
|
// We let the routing code pick the source NTE above.
|
||
|
// Remember this address for later use.
|
||
|
//
|
||
|
// REVIEW: Hold onto the NTE instead? It's more changes...
|
||
|
//
|
||
|
NewTCB->tcb_saddr = NewTCB->tcb_rce->NTE->Address;
|
||
|
NewTCB->tcb_sscope_id =
|
||
|
DetermineScopeId(&NewTCB->tcb_saddr,
|
||
|
NewTCB->tcb_rce->NTE->IF);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Similarly, the routing code may have picked
|
||
|
// the destination scope id if it was left unspecified.
|
||
|
// REVIEW - getpeername will not return the new DestScopeId.
|
||
|
//
|
||
|
DestScopeId = DetermineScopeId(&NewTCB->tcb_daddr,
|
||
|
NewTCB->tcb_rce->NTE->IF);
|
||
|
ASSERT((NewTCB->tcb_dscope_id == DestScopeId) ||
|
||
|
(NewTCB->tcb_dscope_id == 0));
|
||
|
NewTCB->tcb_dscope_id = DestScopeId;
|
||
|
|
||
|
//
|
||
|
// Initialize our Maximum Segment Size (MSS).
|
||
|
// Cache our current Path Maximum Transmission Unit (PMTU)
|
||
|
// so that we'll know if it changes.
|
||
|
//
|
||
|
NewTCB->tcb_pmtu = GetEffectivePathMTUFromRCE(NewTCB->tcb_rce);
|
||
|
IF_TCPDBG(TCP_DEBUG_MSS) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_TCPDBG,
|
||
|
"TCP TdiConnect: PMTU from RCE is %d\n",
|
||
|
NewTCB->tcb_pmtu));
|
||
|
}
|
||
|
NewTCB->tcb_remmss = MAXUSHORT;
|
||
|
NewTCB->tcb_security = SecurityStateValidationCounter;
|
||
|
CalculateMSSForTCB(NewTCB);
|
||
|
|
||
|
// Now initialize our send state.
|
||
|
InitSendState(NewTCB);
|
||
|
NewTCB->tcb_refcnt = 1;
|
||
|
NewTCB->tcb_state = TCB_SYN_SENT;
|
||
|
TStats.ts_activeopens++;
|
||
|
|
||
|
// Need to put the ConnReq on the TCB now, in case the timer
|
||
|
// fires after we've inserted.
|
||
|
NewTCB->tcb_connreq = ConnReq;
|
||
|
KeReleaseSpinLock(&NewTCB->tcb_lock, Irql0);
|
||
|
|
||
|
Inserted = InsertTCB(NewTCB);
|
||
|
KeAcquireSpinLock(&NewTCB->tcb_lock, &Irql0);
|
||
|
|
||
|
if (!Inserted) {
|
||
|
// Insert failed. We must already have a connection. Pull
|
||
|
// the connreq from the TCB first, so we can return the
|
||
|
// correct error code for it.
|
||
|
NewTCB->tcb_connreq = NULL;
|
||
|
NewTCB->tcb_refcnt--;
|
||
|
TryToCloseTCB(NewTCB, TCB_CLOSE_ABORTED, Irql0);
|
||
|
FreeConnReq(ConnReq);
|
||
|
return TDI_ADDR_IN_USE;
|
||
|
}
|
||
|
|
||
|
// If it's closing somehow, stop now. It can't have gone to
|
||
|
// closed, as we hold a reference on it. It could have gone
|
||
|
// to some other state (for example SYN-RCVD) so we need to
|
||
|
// check that now too.
|
||
|
if (!CLOSING(NewTCB) && NewTCB->tcb_state == TCB_SYN_SENT) {
|
||
|
SendSYN(NewTCB, Irql0);
|
||
|
KeAcquireSpinLock(&NewTCB->tcb_lock, &Irql0);
|
||
|
}
|
||
|
DerefTCB(NewTCB, Irql0);
|
||
|
|
||
|
return TDI_PENDING;
|
||
|
} else
|
||
|
KeReleaseSpinLock(&AO->ao_lock, Irql2);
|
||
|
} else
|
||
|
Status = TDI_NOT_ASSOCIATED;
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
} else
|
||
|
Status = TDI_INVALID_CONNECTION;
|
||
|
|
||
|
KeReleaseSpinLock(&AddrObjTableLock, Irql0);
|
||
|
error:
|
||
|
FreeTCB(NewTCB);
|
||
|
FreeConnReq(ConnReq);
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TdiListen - Listen for a connection.
|
||
|
//
|
||
|
// The TDI listen handling routine. Called when the client wants to
|
||
|
// post a listen, we validate his incoming parameters, allocate a TCB
|
||
|
// and return.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of attempt to connect.
|
||
|
TdiListen(
|
||
|
PTDI_REQUEST Request, // Structure for this request.
|
||
|
ushort Flags, // Listen flags for listen.
|
||
|
PTDI_CONNECTION_INFORMATION AcceptableAddr, // Acceptable remote addrs.
|
||
|
PTDI_CONNECTION_INFORMATION ConnectedAddr) // Where to return conn addr.
|
||
|
{
|
||
|
TCPConnReq *ConnReq; // Connection request to use.
|
||
|
IPv6Addr RemoteAddr; // Remote address to take conn. from.
|
||
|
ulong RemoteScopeId; // Scope identifier for remote addr (0 is none).
|
||
|
ushort RemotePort; // Acceptable remote port.
|
||
|
TCPConn *Conn; // Pointer to the Connection being listened upon.
|
||
|
TCB *NewTCB; // Pointer to the new TCB we'll use.
|
||
|
uint ConnID = PtrToUlong(Request->Handle.ConnectionContext);
|
||
|
KIRQL OldIrql; // Save IRQL value prior to taking lock.
|
||
|
TDI_STATUS Status;
|
||
|
|
||
|
//
|
||
|
// If we've been given remote addressing criteria, check it out.
|
||
|
//
|
||
|
if (AcceptableAddr != NULL && AcceptableAddr->RemoteAddress != NULL) {
|
||
|
if (!GetAddress((PTRANSPORT_ADDRESS)AcceptableAddr->RemoteAddress,
|
||
|
&RemoteAddr, &RemoteScopeId, &RemotePort))
|
||
|
return TDI_BAD_ADDR;
|
||
|
|
||
|
//
|
||
|
// REVIEW: IPv4 version did some other address sanity checks here.
|
||
|
// REVIEW: E.g., should we check that remote addr isn't multicast?
|
||
|
//
|
||
|
|
||
|
} else {
|
||
|
RemoteAddr = UnspecifiedAddr;
|
||
|
RemoteScopeId = 0;
|
||
|
RemotePort = 0;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// The remote address is valid. Get a ConnReq, and maybe a TCB.
|
||
|
//
|
||
|
ConnReq = GetConnReq();
|
||
|
if (ConnReq == NULL)
|
||
|
return TDI_NO_RESOURCES; // Couldn't get one.
|
||
|
|
||
|
//
|
||
|
// Now try to get a TCB.
|
||
|
//
|
||
|
NewTCB = AllocTCB();
|
||
|
if (NewTCB == NULL) {
|
||
|
//
|
||
|
// Couldn't get a TCB. Return an error.
|
||
|
//
|
||
|
FreeConnReq(ConnReq);
|
||
|
return TDI_NO_RESOURCES;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// We have the resources we need. Initialize them, and then check the
|
||
|
// state of the connection.
|
||
|
//
|
||
|
ConnReq->tcr_flags = Flags;
|
||
|
ConnReq->tcr_conninfo = ConnectedAddr;
|
||
|
ConnReq->tcr_addrinfo = NULL;
|
||
|
ConnReq->tcr_req.tr_rtn = Request->RequestNotifyObject;
|
||
|
ConnReq->tcr_req.tr_context = Request->RequestContext;
|
||
|
NewTCB->tcb_connreq = ConnReq;
|
||
|
NewTCB->tcb_daddr = RemoteAddr;
|
||
|
NewTCB->tcb_dscope_id = RemoteScopeId;
|
||
|
NewTCB->tcb_dport = RemotePort;
|
||
|
NewTCB->tcb_state = TCB_LISTEN;
|
||
|
|
||
|
//
|
||
|
// Now find the real connection. If we find it, we'll make sure it's
|
||
|
// associated.
|
||
|
//
|
||
|
Conn = GetConnFromConnID(ConnID, &OldIrql);
|
||
|
if (Conn != NULL) {
|
||
|
AddrObj *ConnAO;
|
||
|
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
//
|
||
|
// We have a connection. Make sure it's associated with an address and
|
||
|
// doesn't already have a TCB attached.
|
||
|
//
|
||
|
ConnAO = Conn->tc_ao;
|
||
|
|
||
|
if (ConnAO != NULL) {
|
||
|
CHECK_STRUCT(ConnAO, ao);
|
||
|
KeAcquireSpinLockAtDpcLevel(&ConnAO->ao_lock);
|
||
|
|
||
|
if (AO_VALID(ConnAO)) {
|
||
|
Status = InitTCBFromConn(Conn, NewTCB, AcceptableAddr, TRUE);
|
||
|
} else {
|
||
|
Status = TDI_ADDR_INVALID;
|
||
|
}
|
||
|
|
||
|
if (Status == TDI_SUCCESS) {
|
||
|
//
|
||
|
// The initialization worked. Assign the new TCB to the
|
||
|
// connection, and return.
|
||
|
//
|
||
|
REMOVEQ(&Conn->tc_q);
|
||
|
PUSHQ(&ConnAO->ao_listenq, &Conn->tc_q);
|
||
|
|
||
|
Conn->tc_tcb = NewTCB;
|
||
|
NewTCB->tcb_conn = Conn;
|
||
|
NewTCB->tcb_connid = Conn->tc_connid;
|
||
|
Conn->tc_refcnt++;
|
||
|
|
||
|
ConnAO->ao_listencnt++;
|
||
|
KeReleaseSpinLockFromDpcLevel(&ConnAO->ao_lock);
|
||
|
|
||
|
Status = TDI_PENDING;
|
||
|
} else {
|
||
|
FreeTCB(NewTCB);
|
||
|
KeReleaseSpinLockFromDpcLevel(&ConnAO->ao_lock);
|
||
|
}
|
||
|
} else {
|
||
|
FreeTCB(NewTCB);
|
||
|
Status = TDI_NOT_ASSOCIATED;
|
||
|
}
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, OldIrql);
|
||
|
} else {
|
||
|
FreeTCB(NewTCB);
|
||
|
Status = TDI_INVALID_CONNECTION;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// We're all done.
|
||
|
//
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* InitRCE - Initialize an RCE.
|
||
|
//
|
||
|
// A utility routine to open an RCE and determine the maximum segment size
|
||
|
// for a connection. This function is called with the TCB lock held
|
||
|
// when transitioning out of the SYN_SENT or LISTEN states.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
InitRCE(
|
||
|
TCB *NewTCB) // TCB for which an RCE is to be opened.
|
||
|
{
|
||
|
NetTableEntry *NTE;
|
||
|
IP_STATUS Status;
|
||
|
ushort MSS;
|
||
|
|
||
|
//
|
||
|
// We are called when receiving an incoming connection attempt,
|
||
|
// so tcb_saddr will always be initialized.
|
||
|
//
|
||
|
ASSERT(! IsUnspecified(&NewTCB->tcb_saddr));
|
||
|
|
||
|
//
|
||
|
// Determine NTE we're using for this connection.
|
||
|
//
|
||
|
NTE = FindNetworkWithAddress(&NewTCB->tcb_saddr,
|
||
|
NewTCB->tcb_sscope_id);
|
||
|
if (NTE == NULL) {
|
||
|
//
|
||
|
// Failed to get a route to the destination.
|
||
|
//
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
|
||
|
"TCP InitRCE: Can't find the NTE for address?!?\n"));
|
||
|
goto ErrorReturn;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Get the route.
|
||
|
//
|
||
|
ASSERT(NewTCB->tcb_rce == NULL);
|
||
|
Status = RouteToDestination(&NewTCB->tcb_daddr, NewTCB->tcb_dscope_id,
|
||
|
CastFromNTE(NTE), RTD_FLAG_NORMAL,
|
||
|
&NewTCB->tcb_rce);
|
||
|
ReleaseNTE(NTE);
|
||
|
if (Status != IP_SUCCESS) {
|
||
|
//
|
||
|
// Failed to get a route to the destination.
|
||
|
//
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INTERNAL_ERROR,
|
||
|
"TCP InitRCE: Can't get a route?!?\n"));
|
||
|
ErrorReturn:
|
||
|
//
|
||
|
// Until we have a real route, use conservative values.
|
||
|
//
|
||
|
NewTCB->tcb_pmtu = IPv6_MINIMUM_MTU;
|
||
|
NewTCB->tcb_mss = (ushort)MIN(DEFAULT_MSS, NewTCB->tcb_remmss);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize path-specific TCB settings, based on the RCE:
|
||
|
//
|
||
|
// If packets on the path will be looped back in software,
|
||
|
// don't use the Nagle algorithm for this TCB.
|
||
|
//
|
||
|
if (IsLoopbackRCE(NewTCB->tcb_rce)) {
|
||
|
NewTCB->tcb_flags &= ~NAGLING;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Initialize the maximum segement size (MSS) for this connection.
|
||
|
// Cache our current Path Maximum Transmission Unit (PMTU)
|
||
|
// so that we'll know if it changes.
|
||
|
//
|
||
|
NewTCB->tcb_pmtu = GetEffectivePathMTUFromRCE(NewTCB->tcb_rce);
|
||
|
IF_TCPDBG(TCP_DEBUG_MSS) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_TCPDBG,
|
||
|
"TCP InitRCE: PMTU from RCE is %d\n", NewTCB->tcb_pmtu));
|
||
|
}
|
||
|
NewTCB->tcb_security = SecurityStateValidationCounter;
|
||
|
CalculateMSSForTCB(NewTCB);
|
||
|
}
|
||
|
|
||
|
|
||
|
//* AcceptConn - Accept a connection on a TCB.
|
||
|
//
|
||
|
// Called to accept a connection on a TCB, either from an incoming
|
||
|
// receive segment or via a user's accept. We initialize the RCE
|
||
|
// and the send state, and send out a SYN. We assume the TCB is locked
|
||
|
// and referenced when we get it.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
AcceptConn(
|
||
|
TCB *AcceptTCB, // TCB to accept on.
|
||
|
KIRQL PreLockIrql) // IRQL prior to acquiring TCB lock.
|
||
|
{
|
||
|
CHECK_STRUCT(AcceptTCB, tcb);
|
||
|
ASSERT(AcceptTCB->tcb_refcnt != 0);
|
||
|
|
||
|
InitRCE(AcceptTCB);
|
||
|
InitSendState(AcceptTCB);
|
||
|
|
||
|
AdjustRcvWin(AcceptTCB);
|
||
|
SendSYN(AcceptTCB, PreLockIrql);
|
||
|
|
||
|
KeAcquireSpinLock(&AcceptTCB->tcb_lock, &PreLockIrql);
|
||
|
|
||
|
|
||
|
DerefTCB(AcceptTCB, PreLockIrql);
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TdiAccept - Accept a connection.
|
||
|
//
|
||
|
// The TDI accept routine. Called when the client wants to
|
||
|
// accept a connection for which a listen had previously completed. We
|
||
|
// examine the state of the connection - it has to be in SYN-RCVD, with
|
||
|
// a TCB, with no pending connreq, etc.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of attempt to connect.
|
||
|
TdiAccept(
|
||
|
PTDI_REQUEST Request, // Structure for this request.
|
||
|
PTDI_CONNECTION_INFORMATION AcceptInfo, // Info for this accept.
|
||
|
PTDI_CONNECTION_INFORMATION ConnectedInfo) // Where to return conn addr.
|
||
|
{
|
||
|
TCPConnReq *ConnReq; // ConnReq we'll use for this connection.
|
||
|
uint ConnID = PtrToUlong(Request->Handle.ConnectionContext);
|
||
|
TCPConn *Conn; // Connection being accepted upon.
|
||
|
TCB *AcceptTCB; // TCB for Conn.
|
||
|
KIRQL Irql0, Irql1; // One per lock nesting level.
|
||
|
TDI_STATUS Status;
|
||
|
|
||
|
//
|
||
|
// First, get the ConnReq we'll need.
|
||
|
//
|
||
|
ConnReq = GetConnReq();
|
||
|
if (ConnReq == NULL)
|
||
|
return TDI_NO_RESOURCES;
|
||
|
|
||
|
ConnReq->tcr_conninfo = ConnectedInfo;
|
||
|
ConnReq->tcr_addrinfo = NULL;
|
||
|
ConnReq->tcr_req.tr_rtn = Request->RequestNotifyObject;
|
||
|
ConnReq->tcr_req.tr_context = Request->RequestContext;
|
||
|
|
||
|
//
|
||
|
// Now look up the connection.
|
||
|
//
|
||
|
Conn = GetConnFromConnID(ConnID, &Irql0);
|
||
|
if (Conn != NULL) {
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
//
|
||
|
// We have the connection. Make sure is has a TCB, and that the
|
||
|
// TCB is in the SYN-RCVD state, etc.
|
||
|
//
|
||
|
AcceptTCB = Conn->tc_tcb;
|
||
|
|
||
|
if (AcceptTCB != NULL) {
|
||
|
CHECK_STRUCT(AcceptTCB, tcb);
|
||
|
|
||
|
KeAcquireSpinLock(&AcceptTCB->tcb_lock, &Irql1);
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
|
||
|
if (!CLOSING(AcceptTCB) && AcceptTCB->tcb_state == TCB_SYN_RCVD) {
|
||
|
//
|
||
|
// State is valid. Make sure this TCB had a delayed accept on
|
||
|
// it, and that there is currently no connect request pending.
|
||
|
//
|
||
|
if (!(AcceptTCB->tcb_flags & CONN_ACCEPTED) &&
|
||
|
AcceptTCB->tcb_connreq == NULL) {
|
||
|
|
||
|
//
|
||
|
// If the caller gave us options, they'll override any
|
||
|
// that are already present, if they're valid.
|
||
|
//
|
||
|
if (AcceptInfo != NULL && AcceptInfo->Options != NULL) {
|
||
|
//
|
||
|
// We have options.
|
||
|
// Copy them to make sure they're valid.
|
||
|
//
|
||
|
Status = ProcessUserOptions(AcceptInfo);
|
||
|
if (Status == TDI_SUCCESS) {
|
||
|
AcceptTCB->tcb_flags |= CLIENT_OPTIONS;
|
||
|
} else
|
||
|
goto connerror;
|
||
|
}
|
||
|
|
||
|
AcceptTCB->tcb_connreq = ConnReq;
|
||
|
AcceptTCB->tcb_flags |= CONN_ACCEPTED;
|
||
|
AcceptTCB->tcb_refcnt++;
|
||
|
//
|
||
|
// Everything's set. Accept the connection now.
|
||
|
//
|
||
|
AcceptConn(AcceptTCB, Irql0);
|
||
|
return TDI_PENDING;
|
||
|
}
|
||
|
}
|
||
|
connerror:
|
||
|
KeReleaseSpinLock(&AcceptTCB->tcb_lock, Irql0);
|
||
|
Status = TDI_INVALID_CONNECTION;
|
||
|
goto error;
|
||
|
}
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
}
|
||
|
Status = TDI_INVALID_CONNECTION;
|
||
|
|
||
|
error:
|
||
|
FreeConnReq(ConnReq);
|
||
|
return Status;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* TdiDisConnect - Disconnect a connection.
|
||
|
//
|
||
|
// The TDI disconnection routine. Called when the client wants to disconnect
|
||
|
// a connection. There are two types of disconnection we support, graceful
|
||
|
// and abortive. A graceful close will cause us to send a FIN and not complete
|
||
|
// the request until we get the ACK back. An abortive close causes us to send
|
||
|
// a RST. In that case we'll just get things going and return immediately.
|
||
|
//
|
||
|
// Note: The format of the Timeout (TO) is system specific - we use
|
||
|
// a macro to convert to ticks.
|
||
|
//
|
||
|
TDI_STATUS // Returns: Status of attempt to disconnect.
|
||
|
TdiDisconnect(
|
||
|
PTDI_REQUEST Request, // Structure for this request.
|
||
|
void *TO, // How long to wait.
|
||
|
ushort Flags, // Type of disconnect.
|
||
|
PTDI_CONNECTION_INFORMATION DiscConnInfo, // Ignored.
|
||
|
PTDI_CONNECTION_INFORMATION ReturnInfo) // Ignored.
|
||
|
{
|
||
|
TCPConnReq *ConnReq; // Connection request to use.
|
||
|
TCPConn *Conn;
|
||
|
TCB *DiscTCB;
|
||
|
KIRQL Irql0, Irql1; // One per lock nesting level.
|
||
|
TDI_STATUS Status;
|
||
|
TCP_TIME *Timeout;
|
||
|
|
||
|
Conn = GetConnFromConnID(PtrToUlong(Request->Handle.ConnectionContext),
|
||
|
&Irql0);
|
||
|
|
||
|
if (Conn != NULL) {
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
|
||
|
DiscTCB = Conn->tc_tcb;
|
||
|
if (DiscTCB != NULL) {
|
||
|
CHECK_STRUCT(DiscTCB, tcb);
|
||
|
KeAcquireSpinLock(&DiscTCB->tcb_lock, &Irql1);
|
||
|
|
||
|
//
|
||
|
// We have the TCB. See what kind of disconnect this is.
|
||
|
//
|
||
|
if (Flags & TDI_DISCONNECT_ABORT) {
|
||
|
//
|
||
|
// This is an abortive disconnect. If we're not already
|
||
|
// closed or closing, blow the connection away.
|
||
|
//
|
||
|
if (DiscTCB->tcb_state != TCB_CLOSED) {
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
|
||
|
if (!CLOSING(DiscTCB)) {
|
||
|
DiscTCB->tcb_flags |= NEED_RST;
|
||
|
TryToCloseTCB(DiscTCB, TCB_CLOSE_ABORTED,
|
||
|
Irql0);
|
||
|
} else
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
|
||
|
return TDI_SUCCESS;
|
||
|
} else {
|
||
|
//
|
||
|
// The TCB isn't connected.
|
||
|
//
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
return TDI_INVALID_STATE;
|
||
|
}
|
||
|
} else {
|
||
|
//
|
||
|
// This is not an abortive close. For graceful close we'll
|
||
|
// need a ConnReq.
|
||
|
//
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
|
||
|
//
|
||
|
// Make sure we aren't in the middle of an abortive close.
|
||
|
//
|
||
|
if (CLOSING(DiscTCB)) {
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
return TDI_INVALID_CONNECTION;
|
||
|
}
|
||
|
|
||
|
ConnReq = GetConnReq();
|
||
|
if (ConnReq != NULL) {
|
||
|
//
|
||
|
// Got the ConnReq. See if this is a DISCONNECT_WAIT
|
||
|
// primitive or not.
|
||
|
//
|
||
|
ConnReq->tcr_flags = 0;
|
||
|
ConnReq->tcr_conninfo = NULL;
|
||
|
ConnReq->tcr_addrinfo = NULL;
|
||
|
ConnReq->tcr_req.tr_rtn = Request->RequestNotifyObject;
|
||
|
ConnReq->tcr_req.tr_context = Request->RequestContext;
|
||
|
|
||
|
if (!(Flags & TDI_DISCONNECT_WAIT)) {
|
||
|
Timeout = (TCP_TIME *)TO;
|
||
|
|
||
|
if (Timeout != NULL && !INFINITE_CONN_TO(*Timeout)) {
|
||
|
ulong Ticks = TCP_TIME_TO_TICKS(*Timeout);
|
||
|
if (Ticks > MAX_CONN_TO_TICKS)
|
||
|
Ticks = MAX_CONN_TO_TICKS;
|
||
|
else
|
||
|
Ticks++;
|
||
|
ConnReq->tcr_timeout = (ushort)Ticks;
|
||
|
} else
|
||
|
ConnReq->tcr_timeout = 0;
|
||
|
|
||
|
//
|
||
|
// OK, we're just about set. We need to update
|
||
|
// the TCB state, and send the FIN.
|
||
|
//
|
||
|
if (DiscTCB->tcb_state == TCB_ESTAB) {
|
||
|
DiscTCB->tcb_state = TCB_FIN_WAIT1;
|
||
|
//
|
||
|
// Since we left established, we're off the fast
|
||
|
// receive path.
|
||
|
//
|
||
|
DiscTCB->tcb_slowcount++;
|
||
|
DiscTCB->tcb_fastchk |= TCP_FLAG_SLOW;
|
||
|
} else
|
||
|
if (DiscTCB->tcb_state == TCB_CLOSE_WAIT)
|
||
|
DiscTCB->tcb_state = TCB_LAST_ACK;
|
||
|
else {
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
FreeConnReq(ConnReq);
|
||
|
return TDI_INVALID_STATE;
|
||
|
}
|
||
|
|
||
|
TStats.ts_currestab--; // Update SNMP info.
|
||
|
ASSERT(*(int *)&TStats.ts_currestab >= 0);
|
||
|
|
||
|
ASSERT(DiscTCB->tcb_connreq == NULL);
|
||
|
DiscTCB->tcb_connreq = ConnReq;
|
||
|
DiscTCB->tcb_flags |= FIN_NEEDED;
|
||
|
DiscTCB->tcb_refcnt++;
|
||
|
TCPSend(DiscTCB, Irql0);
|
||
|
|
||
|
return TDI_PENDING;
|
||
|
} else {
|
||
|
//
|
||
|
// This is a DISC_WAIT request.
|
||
|
//
|
||
|
ConnReq->tcr_timeout = 0;
|
||
|
if (DiscTCB->tcb_discwait == NULL) {
|
||
|
DiscTCB->tcb_discwait = ConnReq;
|
||
|
Status = TDI_PENDING;
|
||
|
} else
|
||
|
Status = TDI_INVALID_STATE;
|
||
|
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
return Status;
|
||
|
}
|
||
|
} else {
|
||
|
//
|
||
|
// Couldn't get a ConnReq.
|
||
|
//
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
return TDI_NO_RESOURCES;
|
||
|
}
|
||
|
}
|
||
|
} else
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql0);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// No Conn, or no TCB on conn. Return an error.
|
||
|
//
|
||
|
return TDI_INVALID_CONNECTION;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* OKToNotify - See if it's OK to notify about a DISC.
|
||
|
//
|
||
|
// A little utility function, called to see it it's OK to notify the client
|
||
|
// of an incoming FIN.
|
||
|
//
|
||
|
uint // Returns: TRUE if it's OK, False otherwise.
|
||
|
OKToNotify(
|
||
|
TCB *NotifyTCB) // TCB to check.
|
||
|
{
|
||
|
CHECK_STRUCT(NotifyTCB, tcb);
|
||
|
if (NotifyTCB->tcb_pendingcnt == 0 && NotifyTCB->tcb_urgcnt == 0 &&
|
||
|
NotifyTCB->tcb_rcvhead == NULL && NotifyTCB->tcb_exprcv == NULL)
|
||
|
return TRUE;
|
||
|
else
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* NotifyOfDisc - Notify a client that a TCB is being disconnected.
|
||
|
//
|
||
|
// Called when we're disconnecting a TCB because we've received a FIN or
|
||
|
// RST from the remote peer, or because we're aborting for some reason.
|
||
|
// We'll complete a DISCONNECT_WAIT request if we have one, or try and
|
||
|
// issue an indication otherwise. This is only done if we're in a
|
||
|
// synchronized state and not in TIMED-WAIT.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
NotifyOfDisc(
|
||
|
TCB *DiscTCB, // TCB we're notifying.
|
||
|
TDI_STATUS Status) // Status code for notification.
|
||
|
{
|
||
|
KIRQL Irql0, Irql1;
|
||
|
TCPConnReq *DiscReq;
|
||
|
TCPConn *Conn;
|
||
|
AddrObj *DiscAO;
|
||
|
PVOID ConnContext;
|
||
|
|
||
|
CHECK_STRUCT(DiscTCB, tcb);
|
||
|
ASSERT(DiscTCB->tcb_refcnt != 0);
|
||
|
|
||
|
KeAcquireSpinLock(&DiscTCB->tcb_lock, &Irql0);
|
||
|
if (SYNC_STATE(DiscTCB->tcb_state) &&
|
||
|
!(DiscTCB->tcb_flags & DISC_NOTIFIED)) {
|
||
|
|
||
|
//
|
||
|
// We can't notify him if there's still data to be taken.
|
||
|
//
|
||
|
if (Status == TDI_GRACEFUL_DISC && !OKToNotify(DiscTCB)) {
|
||
|
DiscTCB->tcb_flags |= DISC_PENDING;
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DiscTCB->tcb_flags |= DISC_NOTIFIED;
|
||
|
DiscTCB->tcb_flags &= ~DISC_PENDING;
|
||
|
|
||
|
//
|
||
|
// We're in a state where a disconnect is meaningful, and we haven't
|
||
|
// already notified the client.
|
||
|
// See if we have a DISC-WAIT request pending.
|
||
|
//
|
||
|
if ((DiscReq = DiscTCB->tcb_discwait) != NULL) {
|
||
|
//
|
||
|
// We have a disconnect wait request. Complete it and we're done.
|
||
|
//
|
||
|
DiscTCB->tcb_discwait = NULL;
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
(*DiscReq->tcr_req.tr_rtn)(DiscReq->tcr_req.tr_context, Status, 0);
|
||
|
FreeConnReq(DiscReq);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// No DISC-WAIT. Find the AddrObj for the connection, and see if
|
||
|
// there is a disconnect handler registered.
|
||
|
//
|
||
|
ConnContext = DiscTCB->tcb_conncontext;
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
|
||
|
KeAcquireSpinLock(&AddrObjTableLock, &Irql0);
|
||
|
if ((Conn = DiscTCB->tcb_conn) != NULL) {
|
||
|
CHECK_STRUCT(Conn, tc);
|
||
|
KeAcquireSpinLock(&Conn->tc_ConnBlock->cb_lock, &Irql1);
|
||
|
|
||
|
DiscAO = Conn->tc_ao;
|
||
|
if (DiscAO != NULL) {
|
||
|
KIRQL Irql2;
|
||
|
PDisconnectEvent DiscEvent;
|
||
|
PVOID DiscContext;
|
||
|
|
||
|
CHECK_STRUCT(DiscAO, ao);
|
||
|
KeAcquireSpinLock(&DiscAO->ao_lock, &Irql2);
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql2);
|
||
|
KeReleaseSpinLock(&AddrObjTableLock, Irql1);
|
||
|
|
||
|
DiscEvent = DiscAO->ao_disconnect;
|
||
|
DiscContext = DiscAO->ao_disconncontext;
|
||
|
|
||
|
if (DiscEvent != NULL) {
|
||
|
|
||
|
REF_AO(DiscAO);
|
||
|
KeReleaseSpinLock(&DiscAO->ao_lock, Irql0);
|
||
|
|
||
|
IF_TCPDBG(TCP_DEBUG_CLOSE) {
|
||
|
KdPrintEx((DPFLTR_TCPIP6_ID, DPFLTR_INFO_TCPDBG,
|
||
|
"TCP: indicating %s disconnect\n",
|
||
|
(Status == TDI_GRACEFUL_DISC) ?
|
||
|
"graceful" : "abortive"));
|
||
|
}
|
||
|
|
||
|
(*DiscEvent)(DiscContext, ConnContext, 0, NULL, 0,
|
||
|
NULL, (Status == TDI_GRACEFUL_DISC) ?
|
||
|
TDI_DISCONNECT_RELEASE :
|
||
|
TDI_DISCONNECT_ABORT);
|
||
|
|
||
|
DELAY_DEREF_AO(DiscAO);
|
||
|
return;
|
||
|
} else {
|
||
|
KeReleaseSpinLock(&DiscAO->ao_lock, Irql0);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
KeReleaseSpinLock(&Conn->tc_ConnBlock->cb_lock, Irql1);
|
||
|
}
|
||
|
|
||
|
KeReleaseSpinLock(&AddrObjTableLock, Irql0);
|
||
|
return;
|
||
|
|
||
|
}
|
||
|
KeReleaseSpinLock(&DiscTCB->tcb_lock, Irql0);
|
||
|
}
|
||
|
|
||
|
|
||
|
//* GracefulClose - Complete the transition to a gracefully closed state.
|
||
|
//
|
||
|
// Called when we need to complete the transition to a gracefully closed
|
||
|
// state, either TIME_WAIT or CLOSED. This completion involves removing
|
||
|
// the TCB from it's associated connection (if it has one), notifying the
|
||
|
// upper layer client either via completing a request or calling a disc.
|
||
|
// notification handler, and actually doing the transition.
|
||
|
//
|
||
|
// The tricky part here is if we need to notify him (instead of completing
|
||
|
// a graceful disconnect request). We can't notify him if there is pending
|
||
|
// data on the connection, so in that case we have to pend the disconnect
|
||
|
// notification until we deliver the data.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
GracefulClose(
|
||
|
TCB *CloseTCB, // TCB to transition.
|
||
|
uint ToTimeWait, // TRUE if we're going to TIME_WAIT, FALSE if
|
||
|
// we're going to close the TCB.
|
||
|
uint Notify, // TRUE if via notification, FALSE if via completing
|
||
|
// a disconnect request.
|
||
|
KIRQL PreLockIrql) // IRQL prior to acquiring TCB lock.
|
||
|
{
|
||
|
|
||
|
CHECK_STRUCT(CloseTCB, tcb);
|
||
|
ASSERT(CloseTCB->tcb_refcnt != 0);
|
||
|
|
||
|
//
|
||
|
// First, see if we need to notify the client of a FIN.
|
||
|
//
|
||
|
if (Notify) {
|
||
|
//
|
||
|
// We do need to notify him. See if it's OK to do so.
|
||
|
//
|
||
|
if (OKToNotify(CloseTCB)) {
|
||
|
//
|
||
|
// We can notify him. Change his state, pull him from the conn.,
|
||
|
// and notify him.
|
||
|
//
|
||
|
if (ToTimeWait) {
|
||
|
//
|
||
|
// Save the time we went into time wait, in case we need to
|
||
|
// scavenge.
|
||
|
//
|
||
|
CloseTCB->tcb_alive = SystemUpTime();
|
||
|
CloseTCB->tcb_state = TCB_TIME_WAIT;
|
||
|
KeReleaseSpinLock(&CloseTCB->tcb_lock, PreLockIrql);
|
||
|
} else {
|
||
|
//
|
||
|
// He's going to close. Mark him as closing with TryToCloseTCB
|
||
|
// (he won't actually close since we have a ref. on him). We
|
||
|
// do this so that anyone touching him after we free the
|
||
|
// lock will fail.
|
||
|
//
|
||
|
TryToCloseTCB(CloseTCB, TDI_SUCCESS, PreLockIrql);
|
||
|
}
|
||
|
|
||
|
RemoveTCBFromConn(CloseTCB);
|
||
|
NotifyOfDisc(CloseTCB, TDI_GRACEFUL_DISC);
|
||
|
|
||
|
} else {
|
||
|
//
|
||
|
// Can't notify him now. Set the appropriate flags, and return.
|
||
|
//
|
||
|
CloseTCB->tcb_flags |= (GC_PENDING |
|
||
|
(ToTimeWait ? TW_PENDING : 0));
|
||
|
DerefTCB(CloseTCB, PreLockIrql);
|
||
|
return;
|
||
|
}
|
||
|
} else {
|
||
|
//
|
||
|
// We're not notifying this guy, we just need to complete a conn. req.
|
||
|
// We need to check and see if he's been notified, and if not
|
||
|
// we'll complete the request and notify him later.
|
||
|
//
|
||
|
if (CloseTCB->tcb_flags & DISC_NOTIFIED) {
|
||
|
//
|
||
|
// He's been notified.
|
||
|
//
|
||
|
if (ToTimeWait) {
|
||
|
//
|
||
|
// Save the time we went into time wait, in case we need to
|
||
|
// scavenge.
|
||
|
//
|
||
|
CloseTCB->tcb_alive = SystemUpTime();
|
||
|
CloseTCB->tcb_state = TCB_TIME_WAIT;
|
||
|
KeReleaseSpinLock(&CloseTCB->tcb_lock, PreLockIrql);
|
||
|
} else {
|
||
|
//
|
||
|
// Mark him as closed. See comments above.
|
||
|
//
|
||
|
TryToCloseTCB(CloseTCB, TDI_SUCCESS, PreLockIrql);
|
||
|
}
|
||
|
|
||
|
RemoveTCBFromConn(CloseTCB);
|
||
|
|
||
|
KeAcquireSpinLock(&CloseTCB->tcb_lock, &PreLockIrql);
|
||
|
CompleteConnReq(CloseTCB, TDI_SUCCESS);
|
||
|
KeReleaseSpinLock(&CloseTCB->tcb_lock, PreLockIrql);
|
||
|
} else {
|
||
|
//
|
||
|
// He hasn't been notified. He should be pending already.
|
||
|
//
|
||
|
ASSERT(CloseTCB->tcb_flags & DISC_PENDING);
|
||
|
CloseTCB->tcb_flags |= (GC_PENDING |
|
||
|
(ToTimeWait ? TW_PENDING : 0));
|
||
|
|
||
|
CompleteConnReq(CloseTCB, TDI_SUCCESS);
|
||
|
|
||
|
DerefTCB(CloseTCB, PreLockIrql);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// If we're going to TIME_WAIT, start the TIME_WAIT timer now.
|
||
|
// Otherwise close the TCB.
|
||
|
//
|
||
|
KeAcquireSpinLock(&CloseTCB->tcb_lock, &PreLockIrql);
|
||
|
if (!CLOSING(CloseTCB) && ToTimeWait) {
|
||
|
START_TCB_TIMER(CloseTCB->tcb_rexmittimer, MAX_REXMIT_TO);
|
||
|
KeReleaseSpinLock(&CloseTCB->tcb_lock, PreLockIrql);
|
||
|
RemoveConnFromTCB(CloseTCB);
|
||
|
KeAcquireSpinLock(&CloseTCB->tcb_lock, &PreLockIrql);
|
||
|
}
|
||
|
|
||
|
DerefTCB(CloseTCB, PreLockIrql);
|
||
|
}
|
||
|
|
||
|
#if 0 // REVIEW: Unused function?
|
||
|
//* ConnCheckPassed - Check to see if we have exceeded the connect limit.
|
||
|
//
|
||
|
// Called when a SYN is received to determine whether we will accept
|
||
|
// the incoming connection. If there is an empty slot or if the IP address
|
||
|
// is already in the table, we accept it.
|
||
|
//
|
||
|
int // Returns: TRUE is connect is accepted, FALSE if rejected.
|
||
|
ConnCheckPassed(
|
||
|
IPv6Addr *Src, // Source address of incoming connection.
|
||
|
ulong Prt) // Destination port of incoming connection.
|
||
|
{
|
||
|
UNREFERENCED_PARAMETER(Src);
|
||
|
UNREFERENCED_PARAMETER(Prt);
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
void InitAddrChecks()
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
//* EnumerateConnectionList - Enumerate Connection List database.
|
||
|
//
|
||
|
// This routine enumerates the contents of the connection limit database.
|
||
|
//
|
||
|
// Note: The comments found with this routine upon IPv6 port imply that
|
||
|
// there may have been code here once that actually did something.
|
||
|
// What's here now is a no-op.
|
||
|
//
|
||
|
void // Returns: Nothing.
|
||
|
EnumerateConnectionList(
|
||
|
uchar *Buffer, // Buffer to fill with connection list entries.
|
||
|
ulong BufferSize, // Size of Buffer in bytes.
|
||
|
ulong *EntriesReturned, // Where to put the number of entries returned.
|
||
|
ulong *EntriesAvailable) // Where to return number of avail conn. entries.
|
||
|
{
|
||
|
|
||
|
UNREFERENCED_PARAMETER(Buffer);
|
||
|
UNREFERENCED_PARAMETER(BufferSize);
|
||
|
|
||
|
*EntriesAvailable = 0;
|
||
|
*EntriesReturned = 0;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
#pragma BEGIN_INIT
|
||
|
|
||
|
//* InitTCPConn - Initialize TCP connection management code.
|
||
|
//
|
||
|
// Called during init time to initialize our TCP connection management.
|
||
|
//
|
||
|
int // Returns: TRUE.
|
||
|
InitTCPConn(
|
||
|
void) // Input: Nothing.
|
||
|
{
|
||
|
ExInitializeSListHead(&ConnReqFree);
|
||
|
KeInitializeSpinLock(&ConnReqFreeLock);
|
||
|
KeInitializeSpinLock(&ConnTableLock);
|
||
|
MaxAllocatedConnBlocks = 0;
|
||
|
ConnTable = ExAllocatePool(NonPagedPool,
|
||
|
MaxConnBlocks * sizeof(TCPConnBlock *));
|
||
|
if (ConnTable == NULL) {
|
||
|
return FALSE;
|
||
|
}
|
||
|
|
||
|
return TRUE;
|
||
|
}
|
||
|
|
||
|
#pragma END_INIT
|
||
|
|
||
|
//* UnloadTCPConn
|
||
|
//
|
||
|
// Cleanup and prepare for stack unload.
|
||
|
//
|
||
|
void
|
||
|
UnloadTCPConn(void)
|
||
|
{
|
||
|
PSLIST_ENTRY BufferLink;
|
||
|
KIRQL OldIrql;
|
||
|
TCPConnBlock **OldTable;
|
||
|
|
||
|
while ((BufferLink = ExInterlockedPopEntrySList(&ConnReqFree,
|
||
|
&ConnReqFreeLock))
|
||
|
!= NULL) {
|
||
|
Queue *QueuePtr = CONTAINING_RECORD(BufferLink, Queue, q_next);
|
||
|
TCPReq *Req = CONTAINING_RECORD(QueuePtr, TCPReq, tr_q);
|
||
|
TCPConnReq *ConnReq = CONTAINING_RECORD(Req, TCPConnReq, tcr_req);
|
||
|
|
||
|
CHECK_STRUCT(ConnReq, tcr);
|
||
|
ExFreePool(ConnReq);
|
||
|
}
|
||
|
|
||
|
KeAcquireSpinLock(&ConnTableLock, &OldIrql);
|
||
|
OldTable = ConnTable;
|
||
|
ConnTable = NULL;
|
||
|
KeReleaseSpinLock(&ConnTableLock, OldIrql);
|
||
|
|
||
|
if (OldTable != NULL) {
|
||
|
uint i;
|
||
|
for (i = 0; i < MaxAllocatedConnBlocks; i++) {
|
||
|
ExFreePool(OldTable[i]);
|
||
|
}
|
||
|
ExFreePool(OldTable);
|
||
|
}
|
||
|
}
|