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

1253 lines
38 KiB

/*++
Copyright (c) 1989-2001 Microsoft Corporation
Module Name:
connect.c
Abstract:
Implement the session setup/shutdown (TDI_CONNECT and TDI_DISCONNECT)
Author:
Jiandong Ruan
Revision History:
--*/
#include "precomp.h"
#include "connect.tmh"
NTSTATUS
FindTdiAddress(
PVOID TdiAddress,
ULONG TdiAddressLength,
USHORT AddressType,
PVOID *AddressFound,
PULONG AddressLength
);
void
SmbGetHostCompletion(
PSMB_GETHOST_CONTEXT Context
);
VOID
SmbStartTcpSession (
PSMB_CONNECT_CONTEXT pSmbConnectContext
);
void
SmbStartTcpSessionCompletion(
PSMB_CONNECT_CONTEXT Context
);
NTSTATUS
SmbQueueStartTcpSession (
IN PSMB_CONNECT_CONTEXT pSmbConnectContext
);
NTSTATUS
SmbCheckConnect(
PSMB_CONNECT ConnectObject
)
/*++
Routine Description:
This function checks if a connection object is in a valid state
for making a connection.
Arguments:
Return Value:
--*/
{
ASSERT(KeGetCurrentIrql() >= DISPATCH_LEVEL);
if (IsDisAssociated(ConnectObject)) {
ASSERT(0);
return STATUS_INVALID_HANDLE;
}
if (IsBusy(ConnectObject)) {
return STATUS_DEVICE_BUSY;
}
if (!IsDisconnected(ConnectObject)) {
return STATUS_INVALID_DEVICE_REQUEST;
}
//
// Don't allow SRV to make outbound connection
//
if (ConnectObject->ClientObject == ConnectObject->Device->SmbServer) {
ASSERT(0);
return STATUS_INVALID_DEVICE_REQUEST;
}
if (ConnectObject->TcpContext) {
ASSERT(0);
return STATUS_INVALID_DEVICE_REQUEST;
}
return STATUS_SUCCESS;
}
NTSTATUS
SmbConnect(
PSMB_DEVICE Device,
PIRP Irp
)
/*++
Routine Description:
TDI_CONNECT
Note:
Starting from Whistler, RDR always send us a TDI_NETBIOS_UNICODE_EX address. Support for
other address type is postponed. TDI_NETBIOS_UNICODE_EX is a superset of TDI_NETBIOS and
TDI_NETBIOS_EX.
Arguments:
Return Value:
Note:
Since it is inconvinient to clean up TCP connection (it requires PASSIVE_LEVEL),
we don't cleanup it. We still keep the TCP connection even though we got an error.
The cleanup of TCP connections will be postponed until the client call TDI_DISCONNECT,
TDI_DISASSOCIATE_ADDRESS or close the connection.
--*/
{
DWORD Size = 0;
PSMB_GETHOST_CONTEXT Context = NULL;
PIO_STACK_LOCATION IrpSp = NULL;
KIRQL Irql;
PSMB_CONNECT ConnectObject = NULL;
NTSTATUS status;
ULONG AddressLength;
PTDI_REQUEST_KERNEL pRequestKernel = NULL;
UNICODE_STRING RemoteName;
PTDI_ADDRESS_NETBIOS_UNICODE_EX pUnicodeAddress = NULL;
PAGED_CODE();
IrpSp = IoGetCurrentIrpStackLocation(Irp);
ConnectObject = SmbVerifyAndReferenceConnect(IrpSp->FileObject, SMB_REF_CONNECT);
if (NULL == ConnectObject) {
ASSERT(0);
return STATUS_INVALID_HANDLE;
}
pRequestKernel = (PTDI_REQUEST_KERNEL)&IrpSp->Parameters;
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: pIrp %p ConnectOb %p", Irp, ConnectObject));
SmbPrint (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p\n", ConnectObject));
//
// Mark ConnectObject pending
//
SMB_ACQUIRE_SPINLOCK(ConnectObject, Irql);
status = SmbCheckConnect(ConnectObject);
if (STATUS_SUCCESS != status) {
SMB_RELEASE_SPINLOCK(ConnectObject, Irql);
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p %!status!", ConnectObject, status));
ASSERT(status != STATUS_PENDING);
SmbDereferenceConnect(ConnectObject, SMB_REF_CONNECT);
return status;
}
IoMarkIrpPending(Irp);
SmbReuseConnectObject(ConnectObject);
ConnectObject->State = SMB_CONNECTING;
ConnectObject->PendingIRPs[SMB_PENDING_CONNECT] = Irp;
ConnectObject->Originator = TRUE;
SMB_RELEASE_SPINLOCK(ConnectObject, Irql);
//
// Get remote name
//
status = FindTdiAddress(
pRequestKernel->RequestConnectionInformation->RemoteAddress,
pRequestKernel->RequestConnectionInformation->RemoteAddressLength,
TDI_ADDRESS_TYPE_NETBIOS_UNICODE_EX,
&pUnicodeAddress,
&AddressLength
);
if (status != STATUS_SUCCESS || AddressLength < sizeof(TDI_ADDRESS_NETBIOS_UNICODE_EX)) {
status = STATUS_INVALID_ADDRESS_COMPONENT;
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p %!status!", ConnectObject, status));
goto cleanup;
}
//
// We support only NBT_READWRITE buffer type
//
if (pUnicodeAddress->NameBufferType != NBT_READWRITE) {
status = STATUS_INVALID_ADDRESS_COMPONENT;
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p %!status!", ConnectObject, status));
goto cleanup;
}
if (pUnicodeAddress->RemoteName.Buffer != pUnicodeAddress->RemoteNameBuffer) {
status = STATUS_INVALID_ADDRESS_COMPONENT;
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p %!status!", ConnectObject, status));
goto cleanup;
}
if (pUnicodeAddress->RemoteName.MaximumLength < sizeof(WCHAR)) {
ASSERT(0);
status = STATUS_INVALID_ADDRESS_COMPONENT;
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p %!status!", ConnectObject, status));
goto cleanup;
}
if (AddressLength < pUnicodeAddress->RemoteName.MaximumLength + sizeof(TDI_ADDRESS_NETBIOS_UNICODE_EX) - 2) {
ASSERT(0);
status = STATUS_INVALID_ADDRESS_COMPONENT;
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p %!status!", ConnectObject, status));
goto cleanup;
}
//
// Do DNS name resolution
//
Size = ALIGN(sizeof(SMB_GETHOST_CONTEXT)) + DNS_NAME_BUFFER_LENGTH * sizeof(WCHAR);
Context = (PSMB_GETHOST_CONTEXT)ExAllocatePoolWithTag(
NonPagedPool,
Size,
'hBMS'
);
if (NULL == Context) {
status = STATUS_NO_MEMORY;
SmbTrace (SMB_TRACE_CONNECT, ("TDI_CONNECT: %p %!status!", ConnectObject, status));
goto cleanup;
}
SmbInitAsyncContext(
(PSMB_ASYNC_CONTEXT)Context,
(PSMB_TDI_COMPLETION)SmbGetHostCompletion,
ConnectObject,
SmbCfg.DnsTimeout
);
Context->pUnicodeAddress = pUnicodeAddress;
Context->FQDN.Buffer = (WCHAR*)(((PUCHAR)Context) + ALIGN(sizeof(SMB_GETHOST_CONTEXT)));
Context->FQDN.Length = 0;
Context->FQDN.MaximumLength = DNS_NAME_BUFFER_LENGTH * sizeof(WCHAR);
RemoteName = pUnicodeAddress->RemoteName;
SmbTrace (SMB_TRACE_CONNECT, ("%p: Resolving %Z", ConnectObject, &RemoteName));
SmbPrint (SMB_TRACE_CONNECT, ("%p: Resolving %Z\n", ConnectObject, &RemoteName));
SmbAsyncGetHostByName(&RemoteName, Context);
return STATUS_PENDING;
cleanup:
ASSERT (NULL == Context);
SmbSessionCompleteRequest(
ConnectObject,
STATUS_BAD_NETWORK_PATH,
0
);
return STATUS_PENDING;
}
BOOL
IsSmbBoundToOutgoingInterface4(
ULONG DestIp
)
{
NTSTATUS status;
ULONG Metric, OutgoingIfIndex;
KIRQL Irql;
PIP4FASTQUERY FastQuery = NULL;
PLIST_ENTRY entry = NULL;
PSMB_TCP_DEVICE pIf = NULL;
BOOL found = FALSE;
FastQuery = SmbCfg.SmbDeviceObject->Tcp4.FastQuery;
if (NULL == FastQuery) {
return TRUE;
}
if (INVALID_INTERFACE_INDEX == SmbCfg.SmbDeviceObject->Tcp4.LoopbackInterfaceIndex) {
status = ((PIP4FASTQUERY)(FastQuery))(ntohl(INADDR_LOOPBACK), &OutgoingIfIndex, &Metric);
if (status == STATUS_SUCCESS) {
SmbCfg.SmbDeviceObject->Tcp4.LoopbackInterfaceIndex = OutgoingIfIndex;
SmbPrint(SMB_TRACE_TCP, ("Loopback Interface Index = %d\n", OutgoingIfIndex));
SmbTrace(SMB_TRACE_TCP, ("Loopback Interface Index = %d", OutgoingIfIndex));
} else {
SmbPrint(SMB_TRACE_TCP, ("Query loopback Interface Index returns 0x%08lx\n", status));
SmbTrace(SMB_TRACE_TCP, ("Query loopback Interface Index returns %!status!", status));
SmbCfg.SmbDeviceObject->Tcp4.LoopbackInterfaceIndex = INVALID_INTERFACE_INDEX;
}
}
status = (FastQuery)(DestIp, &OutgoingIfIndex, &Metric);
if (STATUS_SUCCESS != status || OutgoingIfIndex == INVALID_INTERFACE_INDEX) {
//
// TCP cannot find a route, return TRUE anyway
//
return TRUE;
}
if (OutgoingIfIndex == SmbCfg.SmbDeviceObject->Tcp4.LoopbackInterfaceIndex) {
//
// This is a local address
//
return TRUE;
}
//
// This is a remote address
//
found = FALSE;
SMB_ACQUIRE_SPINLOCK(&SmbCfg, Irql);
entry = SmbCfg.IPDeviceList.Flink;
while(entry != &SmbCfg.IPDeviceList) {
pIf = CONTAINING_RECORD(entry, SMB_TCP_DEVICE, Linkage);
entry = entry->Flink;
if (pIf->InterfaceIndex != OutgoingIfIndex) {
continue;
}
if (pIf->PrimaryIpAddress.sin_family == SMB_AF_INET && pIf->PrimaryIpAddress.ip4.sin4_addr) {
found = pIf->EnableOutbound;
break;
}
}
SMB_RELEASE_SPINLOCK(&SmbCfg, Irql);
return found;
}
VOID
SmbCompleteConnectAttempts (
PSMB_CONNECT_CONTEXT pSmbConnectContext
)
/*++
Routine Description:
Complete the connect attempts
Arguments:
Return Value:
--*/
{
PSMB_GETHOST_CONTEXT pSmbGetHostContext = pSmbConnectContext->pSmbGetHostContext;
PSMB_CONNECT ConnectObject = (PSMB_CONNECT)pSmbGetHostContext->ClientContext;
NTSTATUS status = pSmbConnectContext->status;
//
// Set the proper status
//
if (pSmbConnectContext->usCurrentIP >= pSmbGetHostContext->ipaddr_num &&
STATUS_INSUFFICIENT_RESOURCES != status) {
status = STATUS_BAD_NETWORK_PATH;
}
SmbSessionCompleteRequest(ConnectObject, status, 0);
ExFreePool(pSmbGetHostContext);
ExFreePool(pSmbConnectContext);
}
VOID
SmbDelayedStartTcpSession (
IN PDEVICE_OBJECT pDeviceObject,
IN PVOID pContext
)
{
PSMB_CONNECT_CONTEXT pSmbConnectContext = pContext;
PAGED_CODE();
IoFreeWorkItem (pSmbConnectContext->pIoWorkItem);
pSmbConnectContext->pIoWorkItem = NULL;
SmbStartTcpSession (pSmbConnectContext);
}
void
SmbStartTcpSessionCompletion(
PSMB_CONNECT_CONTEXT pSmbConnectContext
)
/*++
Routine Description:
This routine will be called after TCP level session setup is finished.
Arguments:
Return Value:
--*/
{
PSMB_GETHOST_CONTEXT pSmbGetHostContext = pSmbConnectContext->pSmbGetHostContext;
PSMB_CONNECT ConnectObject = (PSMB_CONNECT)pSmbConnectContext->ClientContext;
KIRQL Irql = 0;
PSMB_TCP_CONTEXT pTcpContext = NULL;
NTSTATUS status = STATUS_SUCCESS;
SmbTrace (SMB_TRACE_CONNECT, ("TcpSession Complete: %p %!status!", ConnectObject, pSmbConnectContext->status));
SmbPrint (SMB_TRACE_CONNECT, ("TcpSession Complete: %p %08lx\n", ConnectObject, pSmbConnectContext->status));
switch (pSmbConnectContext->status) {
case STATUS_SUCCESS:
case STATUS_INSUFFICIENT_RESOURCES:
case STATUS_CANCELLED:
goto done;
}
//
// Advance to the next IP address
//
pSmbConnectContext->usCurrentIP++;
if (pSmbConnectContext->usCurrentIP >= pSmbGetHostContext->ipaddr_num) {
goto done;
}
SMB_ACQUIRE_SPINLOCK(ConnectObject, Irql);
pTcpContext = ConnectObject->TcpContext;
ConnectObject->TcpContext = NULL;
SMB_RELEASE_SPINLOCK(ConnectObject, Irql);
SmbDelayedDestroyTcpContext (pTcpContext);
status = SmbQueueStartTcpSession(pSmbConnectContext);
if (STATUS_SUCCESS != status) {
pSmbConnectContext->status = status;
goto done;
}
return;
done:
SmbCompleteConnectAttempts (pSmbConnectContext);
}
NTSTATUS
SmbQueueStartTcpSession (
IN PSMB_CONNECT_CONTEXT pSmbConnectContext
)
{
PIO_WORKITEM pIoWorkItem = NULL;
NTSTATUS status = STATUS_SUCCESS;
pIoWorkItem = IoAllocateWorkItem ((PDEVICE_OBJECT)SmbCfg.SmbDeviceObject);
if (NULL == pIoWorkItem) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto done;
}
pSmbConnectContext->pIoWorkItem = pIoWorkItem;
IoQueueWorkItem (
pIoWorkItem,
SmbDelayedStartTcpSession,
DelayedWorkQueue,
pSmbConnectContext
);
done:
return status;
}
VOID
SmbStartTcpSession (
PSMB_CONNECT_CONTEXT pSmbConnectContext
)
{
PSMB_GETHOST_CONTEXT pSmbGetHostContext = pSmbConnectContext->pSmbGetHostContext;
PSMB_CONNECT ConnectObject = (PSMB_CONNECT)pSmbGetHostContext->ClientContext;
NTSTATUS status = STATUS_SUCCESS;
PSMB_TCP_CONTEXT TcpContext = NULL;
USHORT Port;
PSMB_IP_ADDRESS pSmbIpAddress = NULL;
BOOLEAN bTryAllocateResource = FALSE;
KIRQL Irql = 0;
ASSERT (KeGetCurrentIrql() == PASSIVE_LEVEL);
ASSERT (ConnectObject->TcpContext == NULL);
for (
TcpContext = NULL, bTryAllocateResource = FALSE;
pSmbConnectContext->usCurrentIP < pSmbGetHostContext->ipaddr_num;
pSmbConnectContext->usCurrentIP++
) {
pSmbIpAddress = &pSmbGetHostContext->ipaddr[pSmbConnectContext->usCurrentIP];
switch (pSmbIpAddress->sin_family) {
case SMB_AF_INET:
if (SmbCfg.Tcp4Available &&
IsSmbBoundToOutgoingInterface4(pSmbIpAddress->ip4.sin4_addr)) {
TcpContext = SmbAllocateOutbound(&ConnectObject->Device->Tcp4);
Port = ConnectObject->Device->Tcp4.Port;
ConnectObject->FastSend = ConnectObject->Device->Tcp4.FastSend;
bTryAllocateResource = TRUE;
SmbTrace (SMB_TRACE_CONNECT, ("%p: try %d-th IP address: %!ipaddr!",
ConnectObject,
pSmbConnectContext->usCurrentIP + 1,
pSmbIpAddress->ip4.sin4_addr));
SmbPrint (SMB_TRACE_CONNECT, ("%p: try %d-th IP address: IPv4\n",
ConnectObject,
pSmbConnectContext->usCurrentIP + 1));
} else {
SmbTrace (SMB_TRACE_CONNECT, ("%p: skip %d-th IP address: %!ipaddr!",
ConnectObject,
pSmbConnectContext->usCurrentIP + 1,
pSmbIpAddress->ip4.sin4_addr));
SmbPrint (SMB_TRACE_CONNECT, ("%p: skip %d-th IP address: IPv4\n",
ConnectObject,
pSmbConnectContext->usCurrentIP + 1));
}
break;
case SMB_AF_INET6:
if (SmbCfg.Tcp6Available &&
(SmbCfg.bIPv6EnableOutboundGlobal ||
SMB_IS_ADDRESS_ALLOWED(pSmbIpAddress->ip6.sin6_addr_bytes))) {
TcpContext = SmbAllocateOutbound(&ConnectObject->Device->Tcp6);
Port = ConnectObject->Device->Tcp6.Port;
ConnectObject->FastSend = ConnectObject->Device->Tcp6.FastSend;
bTryAllocateResource = TRUE;
SmbTrace (SMB_TRACE_CONNECT, ("%p: try %d-th IP address: %!IPV6ADDR!",
ConnectObject,
pSmbConnectContext->usCurrentIP + 1,
(PVOID)pSmbIpAddress->ip6.sin6_addr
));
} else {
SmbTrace (SMB_TRACE_CONNECT, ("%p: skip %d-th IP address: %!IPV6ADDR!",
ConnectObject,
pSmbConnectContext->usCurrentIP + 1,
(PVOID)pSmbIpAddress->ip6.sin6_addr
));
}
break;
default:
ASSERT (0);
break;
}
if (NULL != TcpContext) {
break;
}
}
if (NULL == TcpContext) {
status = (bTryAllocateResource)? STATUS_INSUFFICIENT_RESOURCES: STATUS_BAD_NETWORK_PATH;
goto cleanup;
}
ASSERT (pSmbConnectContext->usCurrentIP < pSmbGetHostContext->ipaddr_num);
SMB_ACQUIRE_SPINLOCK(ConnectObject, Irql);
ConnectObject->TcpContext = TcpContext;
ConnectObject->StateRcvHandler = WaitingHeader;
ConnectObject->HeaderBytesRcved = 0;
TcpContext->Connect.pLastUprCnt = TcpContext->Connect.UpperConnect = ConnectObject;
SMB_RELEASE_SPINLOCK(ConnectObject, Irql);
//
// TBD: SmbAsyncConnect should honor the timeout
//
SmbInitAsyncContext(
(PSMB_ASYNC_CONTEXT)pSmbConnectContext,
(PSMB_TDI_COMPLETION)SmbStartTcpSessionCompletion,
ConnectObject,
5000 // 5 seconds timeout. It is not honored by SmbAysncConnect
);
ConnectObject->RemoteIpAddress = pSmbIpAddress[0];
pSmbConnectContext->TcpConnect = TcpContext->Connect;
SmbAsyncConnect(
pSmbIpAddress,
Port,
pSmbConnectContext
);
return;
cleanup:
ASSERT (pSmbConnectContext->usCurrentIP >= pSmbGetHostContext->ipaddr_num);
ASSERT (status != STATUS_SUCCESS);
pSmbConnectContext->status = status;
SmbCompleteConnectAttempts (pSmbConnectContext);
}
void
SmbGetHostCompletion(
PSMB_GETHOST_CONTEXT Context
)
/*++
Routine Description:
This routine will be called after we get a result for dns name resolution
Arguments:
Return Value:
--*/
{
PSMB_CONNECT ConnectObject = (PSMB_CONNECT)Context->ClientContext;
NTSTATUS status = Context->status;
PSMB_CONNECT_CONTEXT pSmbConnectContext = NULL;
ASSERT(NULL != ConnectObject);
SmbTrace (SMB_TRACE_CONNECT, ("%p: name resolution completed with %!status!", ConnectObject, status));
SmbPrint (SMB_TRACE_CONNECT, ("%p: name resolution completed with %08lx\n", ConnectObject, status));
if (STATUS_SUCCESS != status) {
if (status != STATUS_CANCELLED) {
status = STATUS_BAD_NETWORK_PATH;
}
goto cleanup;
}
pSmbConnectContext = (PSMB_CONNECT_CONTEXT)ExAllocatePoolWithTag(
NonPagedPool,
sizeof(SMB_CONNECT_CONTEXT),
'sBMS'
);
if (NULL == pSmbConnectContext) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto cleanup;
}
RtlZeroMemory (pSmbConnectContext, sizeof(SMB_CONNECT_CONTEXT));
pSmbConnectContext->ClientContext = ConnectObject;
pSmbConnectContext->usCurrentIP = 0;
pSmbConnectContext->pSmbGetHostContext = Context;
pSmbConnectContext->pIoWorkItem = NULL;
SmbTrace (SMB_TRACE_CONNECT, ("%p: find %d IP address", ConnectObject, Context->ipaddr_num));
SmbPrint (SMB_TRACE_CONNECT, ("%p: find %d IP address\n", ConnectObject, Context->ipaddr_num));
status = SmbQueueStartTcpSession(pSmbConnectContext);
if (STATUS_SUCCESS != status) {
goto cleanup;
}
return;
cleanup:
if (NULL != Context) {
ExFreePool(Context);
}
if (NULL != pSmbConnectContext) {
ExFreePool(pSmbConnectContext);
}
SmbSessionCompleteRequest(ConnectObject, status, 0);
}
NTSTATUS
DisconnDone(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PSMB_TCP_CONTEXT TcpContext
)
{
KIRQL Irql;
PSMB_DEVICE SmbDevice;
SmbDevice = SmbCfg.SmbDeviceObject;
ASSERT(TcpContext->DisconnectIrp == Irp);
SMB_ACQUIRE_SPINLOCK(SmbDevice, Irql);
ASSERT(EntryIsInList(&SmbDevice->PendingDisconnectList, &TcpContext->Linkage));
RemoveEntryList(&TcpContext->Linkage);
InitializeListHead(&TcpContext->Linkage);
SmbDevice->PendingDisconnectListNumber--;
ASSERT(SmbDevice->PendingDisconnectListNumber >= 0);
TcpContext->DisconnectIrp = NULL;
if (IsListEmpty(&SmbDevice->PendingDisconnectList)) {
KeSetEvent(&SmbDevice->PendingDisconnectListEmptyEvent, 0, FALSE);
ASSERT(SmbDevice->PendingDisconnectListNumber == 0);
}
SMB_RELEASE_SPINLOCK(SmbDevice, Irql);
if (Irp->IoStatus.Status == STATUS_SUCCESS) {
SmbFreeTcpContext(TcpContext);
} else {
SmbDelayedDestroyTcpContext(TcpContext);
}
SmbFreeIrp(Irp);
return STATUS_MORE_PROCESSING_REQUIRED;
}
NTSTATUS
SmbAsynchTcpDisconnect(
PSMB_TCP_CONTEXT TcpContext,
ULONG Flags
)
{
PIRP Irp;
PFILE_OBJECT FileObject;
PDEVICE_OBJECT DeviceObject;
NTSTATUS status;
FileObject = TcpContext->Connect.ConnectObject;
if (NULL == FileObject) {
ASSERT (0);
SmbTrace(SMB_TRACE_TCP, ("NULL FileObject !!!!"));
return STATUS_INVALID_PARAMETER;
}
DeviceObject = IoGetRelatedDeviceObject(FileObject);
Irp = SmbAllocIrp(DeviceObject->StackSize);
if (NULL == Irp) {
SmbTrace(SMB_TRACE_TCP, ("no free IRP !!!!"));
return STATUS_INSUFFICIENT_RESOURCES;
}
TcpContext->DisconnectIrp = Irp;
TdiBuildDisconnect(
Irp,
DeviceObject,
FileObject,
(PIO_COMPLETION_ROUTINE)DisconnDone,
TcpContext,
NULL,
Flags,
NULL,
NULL
);
status = IoCallDriver(DeviceObject, Irp);
SmbTrace (SMB_TRACE_CONNECT, ("return %!status!", status));
return STATUS_PENDING;
}
VOID
SmbDelayedDisconnectTcp(
IN PSMB_DEVICE DeviceObject,
IN PIO_WORKITEM WorkItem
)
{
KIRQL Irql;
PLIST_ENTRY entry;
NTSTATUS status;
ULONG DisconnectFlag;
PSMB_TCP_CONTEXT TcpContext;
PAGED_CODE();
ASSERT (DeviceObject->DisconnectWorkerRunning == TRUE);
while(1) {
SMB_ACQUIRE_SPINLOCK(DeviceObject, Irql);
if (IsListEmpty(&DeviceObject->DelayedDisconnectList)) {
SMB_RELEASE_SPINLOCK(DeviceObject, Irql);
DeviceObject->DisconnectWorkerRunning = FALSE;
break;
}
entry = RemoveHeadList(&DeviceObject->DelayedDisconnectList);
InsertTailList(&DeviceObject->PendingDisconnectList, entry);
DeviceObject->PendingDisconnectListNumber++;
SMB_RELEASE_SPINLOCK(DeviceObject, Irql);
TcpContext = CONTAINING_RECORD(entry, SMB_TCP_CONTEXT, Linkage);
//
// FIN attacking detection and protection
// The idea is couting the disconnect request pending in TCP.
// If we found extraodinary high number of disconnect request pending
// in TCP, use abort instead of graceful disconnect for the remaining
// disconnect request.
//
ASSERT(DeviceObject->EnterFAPM > DeviceObject->LeaveFAPM);
ASSERT(DeviceObject->LeaveFAPM > 0);
if (DeviceObject->PendingDisconnectListNumber >= DeviceObject->EnterFAPM) {
DeviceObject->FinAttackProtectionMode = TRUE;
}
if (DeviceObject->PendingDisconnectListNumber <= DeviceObject->LeaveFAPM) {
DeviceObject->FinAttackProtectionMode = FALSE;
}
if (DeviceObject->FinAttackProtectionMode) {
DisconnectFlag = TDI_DISCONNECT_ABORT;
} else {
DisconnectFlag = TDI_DISCONNECT_RELEASE;
}
status = SmbAsynchTcpDisconnect(TcpContext, DisconnectFlag);
if (status == STATUS_PENDING) {
continue;
}
//
// Should be out of resources. Put it back to the DelayedDisconnectList and
// wait for the next round. (Resource may be available at that time)
//
ASSERT(status == STATUS_INSUFFICIENT_RESOURCES);
SMB_ACQUIRE_SPINLOCK(DeviceObject, Irql);
RemoveEntryList(entry);
InsertTailList(&DeviceObject->DelayedDisconnectList, entry);
SMB_RELEASE_SPINLOCK(DeviceObject, Irql);
}
IoFreeWorkItem(WorkItem);
SMB_ACQUIRE_SPINLOCK(DeviceObject, Irql);
if (IsListEmpty(&DeviceObject->PendingDisconnectList)) {
KeSetEvent(&DeviceObject->PendingDisconnectListEmptyEvent, 0, FALSE);
ASSERT(DeviceObject->PendingDisconnectListNumber == 0);
} else {
KeResetEvent(&DeviceObject->PendingDisconnectListEmptyEvent);
}
SMB_RELEASE_SPINLOCK(DeviceObject, Irql);
}
NTSTATUS
SmbQueueDisconnectWorkItem(
IN PSMB_DEVICE DeviceObject,
IN PSMB_TCP_CONTEXT TcpContext
)
{
PIO_WORKITEM WorkItem;
//
// The DeviceObject's spinlock should be held
//
ASSERT (KeGetCurrentIrql() == DISPATCH_LEVEL);
InsertTailList(&DeviceObject->DelayedDisconnectList, &TcpContext->Linkage);
//
// Be nice to others!!! Don't start too many worker threads
//
if (!DeviceObject->DisconnectWorkerRunning) {
//
// This is not a critical error.
// Since we have queued the Tcp endpoint in our list, we can
// handle the termporarily out of resource.
// If the resource is temporarily unavailable, we can fire another
// work item the next time this routine is called. The only downside
// is that the disconnecting could be delayed for longer time.
//
WorkItem = IoAllocateWorkItem(&DeviceObject->DeviceObject);
if (NULL == WorkItem) {
return STATUS_INSUFFICIENT_RESOURCES;
}
DeviceObject->DisconnectWorkerRunning = TRUE;
IoQueueWorkItem(
WorkItem,
(PIO_WORKITEM_ROUTINE)SmbDelayedDisconnectTcp,
DelayedWorkQueue,
WorkItem
);
}
return STATUS_SUCCESS;
}
VOID
SmbDisconnectCleanup(
IN PSMB_DEVICE DeviceObject,
IN PSMB_CLIENT_ELEMENT ClientObject,
IN PSMB_CONNECT ConnectObject,
IN PSMB_TCP_CONTEXT TcpContext,
IN DWORD dwFlag,
IN BOOL bWait
)
/*++
Routine Description:
Cleanup the connection object.
Arguments:
Return Value:
--*/
{
PIRP PendingIrp = NULL;
LIST_ENTRY PendingIrpList = { NULL };
KIRQL Irql = 0;
NTSTATUS status = STATUS_SUCCESS;
ASSERT(ConnectObject->TcpContext == NULL);
ASSERT(ClientObject != NULL);
//
// Grab the global lock to synchronize with TdiAcceptCompletion
//
SMB_ACQUIRE_SPINLOCK(DeviceObject, Irql);
SMB_ACQUIRE_SPINLOCK_DPC(ClientObject);
SMB_ACQUIRE_SPINLOCK_DPC(ConnectObject);
KeRemoveQueueDpc(&ConnectObject->SmbHeaderDpc);
ConnectObject->State = SMB_IDLE;
ConnectObject->DpcRequestQueued = FALSE;
//
// If the TDI_ACCEPT is still pending, remove it now.
// TBD: wait for the completion of TDI accept
//
if (ConnectObject->PendingIRPs[SMB_PENDING_ACCEPT]) {
ConnectObject->PendingIRPs[SMB_PENDING_ACCEPT] = NULL;
ASSERT (EntryIsInList(&ClientObject->PendingAcceptConnection, &ConnectObject->Linkage));
//
// This has to be non NULL so that we can set TcpContext->Connect.UpperConenct to NULL
// below and synchronize with TdiAcceptCompletion
//
ASSERT (TcpContext);
ASSERT (ClientObject->PendingAcceptNumber > 0);
ASSERT (ClientObject == SmbCfg.SmbDeviceObject->SmbServer);
ASSERT (!ConnectObject->Originator);
ClientObject->PendingAcceptNumber--;
SmbDereferenceConnect(ConnectObject, SMB_REF_CONNECT);
} else {
ASSERT (!EntryIsInList(&ClientObject->PendingAcceptConnection, &ConnectObject->Linkage));
}
RemoveEntryList(&ConnectObject->Linkage);
InsertTailList(&ClientObject->AssociatedConnection, &ConnectObject->Linkage);
if (NULL != TcpContext) {
TcpContext->Connect.UpperConnect = NULL;
if (!bWait) {
if (TcpContext->Address.AddressHandle == NULL) {
SmbQueueDisconnectWorkItem (ConnectObject->Device, TcpContext);
} else {
//
// For outbound request, we got to destroy it because
// TCP doesn't support reusing.
//
SmbDelayedDestroyTcpContext(TcpContext);
}
TcpContext = NULL;
}
}
//
// Move all the pending Irps to another linked list
// REVIEW: what to do with the PendingIRPs[SMB_PENDING_RECEIVE]?
//
InitializeListHead (&PendingIrpList);
if (NULL != ConnectObject->ClientIrp && !ConnectObject->PendingIRPs[SMB_PENDING_RECEIVE]) {
InsertTailList (&PendingIrpList, &ConnectObject->ClientIrp->Tail.Overlay.ListEntry);
ConnectObject->ClientIrp = NULL;
}
while (!IsListEmpty(&ConnectObject->RcvList)) {
PLIST_ENTRY entry;
entry = RemoveHeadList(&ConnectObject->RcvList);
PendingIrp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);
InsertTailList (&PendingIrpList, &PendingIrp->Tail.Overlay.ListEntry);
}
InitializeListHead (&ConnectObject->RcvList);
SMB_RELEASE_SPINLOCK_DPC(ConnectObject);
SMB_RELEASE_SPINLOCK_DPC(ClientObject);
SMB_RELEASE_SPINLOCK(DeviceObject, Irql);
//
// We have disassociate all the pending IRPs from the ConnectObject.
// The ConnectObject can be reused from now on. Now we complete all
// the pending IRPs.
//
while (!IsListEmpty(&PendingIrpList)) {
PLIST_ENTRY entry;
entry = RemoveHeadList(&PendingIrpList);
PendingIrp = CONTAINING_RECORD(entry, IRP, Tail.Overlay.ListEntry);
IoAcquireCancelSpinLock(&Irql);
IoSetCancelRoutine(PendingIrp, NULL);
IoReleaseCancelSpinLock(Irql);
PendingIrp->IoStatus.Status = STATUS_CONNECTION_RESET;
PendingIrp->IoStatus.Information = 0;
IoCompleteRequest(PendingIrp, IO_NETWORK_INCREMENT);
}
//
// Handling the synchronous disconnect case
//
if (NULL != TcpContext) {
ASSERT (bWait);
ASSERT (dwFlag == TDI_DISCONNECT_RELEASE || dwFlag == TDI_DISCONNECT_ABORT);
//
// Issue synchronous disconnection
//
status = SmbTcpDisconnect(
TcpContext,
1000, // 1 second time out
dwFlag
);
if (STATUS_SUCCESS != status || TcpContext->Address.AddressHandle != NULL) {
SmbDelayedDestroyTcpContext(TcpContext);
} else {
SmbFreeTcpContext(TcpContext);
}
}
}
NTSTATUS
SmbDoDisconnect(
PSMB_CONNECT ConnectObject
)
{
PSMB_TCP_CONTEXT TcpContext = NULL;
PSMB_CLIENT_ELEMENT ClientObject = NULL;
KIRQL Irql;
NTSTATUS status;
PAGED_CODE();
SMB_ACQUIRE_SPINLOCK(&SmbCfg, Irql);
SMB_ACQUIRE_SPINLOCK_DPC(ConnectObject);
TcpContext = ConnectObject->TcpContext;
ClientObject = ConnectObject->ClientObject;
ConnectObject->TcpContext = NULL;
ConnectObject->State = SMB_IDLE;
SMB_RELEASE_SPINLOCK_DPC(ConnectObject);
SMB_RELEASE_SPINLOCK(&SmbCfg, Irql);
SmbPrint(SMB_TRACE_CALL, ("DoDisconnect: Connect %p\n", ConnectObject));
SaveDisconnectOriginator(ConnectObject, SMB_DISCONNECT_FROM_CLIENT);
if (NULL != TcpContext) {
ASSERT (NULL != ClientObject);
//
// Wait for the tcp-layer disconnect completion since
// the disconnection is generated by our clients.
//
SmbDisconnectCleanup(ConnectObject->Device, ClientObject,
ConnectObject, TcpContext, TDI_DISCONNECT_RELEASE, TRUE);
ASSERT(ConnectObject->State == SMB_IDLE);
}
return STATUS_SUCCESS;
}
NTSTATUS
SmbDisconnect(
PSMB_DEVICE Device,
PIRP Irp
)
/*++
Routine Description:
TDI_DISCONNECT
To be implement.
Arguments:
Return Value:
--*/
{
PIO_STACK_LOCATION IrpSp = NULL;
PSMB_CONNECT ConnectObject = NULL;
PAGED_CODE();
IrpSp = IoGetCurrentIrpStackLocation(Irp);
ConnectObject = SmbVerifyAndReferenceConnect(IrpSp->FileObject, SMB_REF_DISCONNECT);
if (NULL == ConnectObject) {
ASSERT(0);
return STATUS_INVALID_HANDLE;
}
SmbTrace (SMB_TRACE_CONNECT, ("TDI_DISCONNECT: pIrp %p ConnOb %p", Irp, ConnectObject));
SmbDoDisconnect(ConnectObject);
SmbDereferenceConnect(ConnectObject, SMB_REF_DISCONNECT);
return STATUS_SUCCESS;
}
void
SmbSessionCompleteRequest(
PSMB_CONNECT ConnectObject,
NTSTATUS status,
DWORD information
)
/*++
Routine Description:
Complete a pending session request and cleanup the connection
Note: we don't cleanup the Tcp level connection here since we could be
called at DISPATCH_LEVEL. Instead, we leave tcp connections alive.
the cleanup will be done when the client actually disassociate or
close the connection.
We can reuse the tcp connection if the client reattempt to make
another connection.
Arguments:
--*/
{
PIRP Irp = NULL;
KIRQL Irql;
PIO_STACK_LOCATION IrpSp = NULL;
PSMB_CLIENT_ELEMENT ClientObject = NULL;
PSMB_TCP_CONTEXT TcpContext = NULL;
SmbTrace (SMB_TRACE_CONNECT, ("complete TDI_CONNECT: %p %!status!", ConnectObject, status));
SmbPrint (SMB_TRACE_CONNECT, ("complete TDI_CONNECT: %p %08lx\n", ConnectObject, status));
ClientObject = ConnectObject->ClientObject;
ASSERT(NULL != ClientObject);
if (NULL == ClientObject && status == STATUS_SUCCESS) {
status = STATUS_INTERNAL_ERROR;
}
SMB_ACQUIRE_SPINLOCK(&SmbCfg, Irql);
if (ClientObject) {
SMB_ACQUIRE_SPINLOCK_DPC(ClientObject);
}
SMB_ACQUIRE_SPINLOCK_DPC(ConnectObject);
Irp = ConnectObject->PendingIRPs[SMB_PENDING_CONNECT];
ConnectObject->PendingIRPs[SMB_PENDING_CONNECT] = NULL;
if (status == STATUS_SUCCESS) {
ConnectObject->State = SMB_CONNECTED;
RemoveEntryList(&ConnectObject->Linkage);
InsertTailList(&ClientObject->ActiveConnection, &ConnectObject->Linkage);
TcpContext = NULL;
} else {
ConnectObject->State = SMB_IDLE;
TcpContext = ConnectObject->TcpContext;
ConnectObject->TcpContext = NULL;
if (TcpContext) {
TcpContext->Connect.UpperConnect = NULL;
}
}
SMB_RELEASE_SPINLOCK_DPC(ConnectObject);
if (ClientObject) {
SMB_RELEASE_SPINLOCK_DPC(ClientObject);
}
SMB_RELEASE_SPINLOCK(&SmbCfg, Irql);
if (STATUS_CONNECTION_ACTIVE == status) {
//
// RDR could bugcheck if returning STATUS_CONNECTION_ACTIVE
//
ASSERT(0);
status = STATUS_BAD_NETWORK_PATH;
}
//
// Don't call SmbFreeOutbound() because the Tcp endpoint could be
// in some inconsistent state which prohibit from being reused.
// Simply destroy it!!!
//
SmbDelayedDestroyTcpContext(TcpContext);
ASSERT (STATUS_PENDING != status);
IoAcquireCancelSpinLock(&Irql);
IoSetCancelRoutine(Irp, NULL);
IoReleaseCancelSpinLock(Irql);
IrpSp = IoGetCurrentIrpStackLocation(Irp);
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = information;
SmbDereferenceConnect(ConnectObject, SMB_REF_CONNECT);
IoCompleteRequest(Irp, IO_NETWORK_INCREMENT);
SmbTrace (SMB_TRACE_CONNECT, ("complete TDI_CONNECT: pIrp %p ConnOb %p %!status!",
Irp, ConnectObject, status));
}
NTSTATUS
FindTdiAddress(
PVOID TdiAddress,
ULONG TdiAddressLength,
USHORT AddressType,
PVOID *AddressFound,
PULONG AddressLength
)
/*++
Routine Description:
This routine search for a particular type of TDI address in a big compound address.
Arguments:
Return Value:
STATUS_SUCCESS if we find one
Otherwise fail
--*/
{
#define TA_ADDRESS_HEADER_SIZE (FIELD_OFFSET(TA_ADDRESS,Address))
int i;
DWORD RemainingBufferLength;
PTA_ADDRESS pAddress = NULL;
TRANSPORT_ADDRESS UNALIGNED *pTransportAddr = NULL;
PAGED_CODE();
pTransportAddr = (PTRANSPORT_ADDRESS)TdiAddress;
if (TdiAddressLength < sizeof(pTransportAddr->TAAddressCount)) {
return STATUS_UNSUCCESSFUL;
}
RemainingBufferLength = TdiAddressLength - sizeof(pTransportAddr->TAAddressCount);
pAddress = (PTA_ADDRESS)pTransportAddr->Address;
for (i = 0; i < pTransportAddr->TAAddressCount; i++) {
//
// First, make sure we can safely access pAddress->AddressLength
//
if (RemainingBufferLength < TA_ADDRESS_HEADER_SIZE) {
return STATUS_INVALID_ADDRESS_COMPONENT;
}
RemainingBufferLength -= TA_ADDRESS_HEADER_SIZE;
if (RemainingBufferLength < pAddress->AddressLength) {
return STATUS_INVALID_ADDRESS_COMPONENT;
}
if (AddressType == pAddress->AddressType) {
*AddressFound = pAddress->Address;
*AddressLength = pAddress->AddressLength;
return STATUS_SUCCESS;
}
RemainingBufferLength -= pAddress->AddressLength;
pAddress = (PTA_ADDRESS)(((PUCHAR)pAddress) + pAddress->AddressLength + TA_ADDRESS_HEADER_SIZE);
}
return STATUS_UNSUCCESSFUL;
}