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.
3608 lines
92 KiB
3608 lines
92 KiB
/*++
|
|
|
|
Copyright (c) 1998-2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
httpconn.c
|
|
|
|
Abstract:
|
|
|
|
This module implements the HTTP_CONNECTION object.
|
|
|
|
Author:
|
|
|
|
Keith Moore (keithmo) 08-Jul-1998
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
|
|
#include "precomp.h"
|
|
#include "httpconnp.h"
|
|
|
|
|
|
//
|
|
// Private globals.
|
|
//
|
|
|
|
BOOLEAN g_HttpconnInitialized = FALSE;
|
|
|
|
UL_ZOMBIE_HTTP_CONNECTION_LIST g_ZombieConnectionList;
|
|
|
|
//
|
|
// Global connection Limits stuff
|
|
//
|
|
|
|
ULONG g_MaxConnections = HTTP_LIMIT_INFINITE;
|
|
ULONG g_CurrentConnections = 0;
|
|
BOOLEAN g_InitGlobalConnections = FALSE;
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
|
|
#pragma alloc_text(INIT, UlInitializeHttpConnection)
|
|
|
|
#endif // ALLOC_PRAGMA
|
|
|
|
#if 0
|
|
NOT PAGEABLE -- UlBindConnectionToProcess
|
|
NOT PAGEABLE -- UlQueryProcessBinding
|
|
NOT PAGEABLE -- UlpCreateProcBinding
|
|
NOT PAGEABLE -- UlpFreeProcBinding
|
|
NOT PAGEABLE -- UlpFindProcBinding
|
|
|
|
NOT PAGEABLE -- UlCreateHttpConnection
|
|
NOT PAGEABLE -- UlReferenceHttpConnection
|
|
NOT PAGEABLE -- UlDereferenceHttpConnection
|
|
|
|
NOT PAGEABLE -- UlReferenceHttpRequest
|
|
NOT PAGEABLE -- UlDereferenceHttpRequest
|
|
NOT PAGEABLE -- UlpCreateHttpRequest
|
|
NOT PAGEABLE -- UlpFreeHttpRequest
|
|
|
|
NOT PAGEABLE -- UlTerminateHttpConnection
|
|
NOT PAGEABLE -- UlPurgeZombieConnections
|
|
NOT PAGEABLE -- UlpZombifyHttpConnection
|
|
NOT PAGEABLE -- UlZombieTimerDpcRoutine
|
|
NOT PAGEABLE -- UlpZombieTimerWorker
|
|
NOT PAGEABLE -- UlpTerminateZombieList
|
|
NOT PAGEABLE -- UlLogZombieConnection
|
|
|
|
NOT PAGEABLE -- UlDisconnectHttpConnection
|
|
|
|
#endif
|
|
|
|
//
|
|
// Public functions.
|
|
//
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Performs global initialization of this module.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlInitializeHttpConnection(
|
|
VOID
|
|
)
|
|
{
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT(!g_HttpconnInitialized);
|
|
|
|
if (g_HttpconnInitialized)
|
|
{
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
//
|
|
// Initialize global data.
|
|
//
|
|
|
|
UlInitalizeLockedList(
|
|
&g_ZombieConnectionList.LockList,
|
|
"ZombieHttpConnectionList"
|
|
);
|
|
|
|
g_ZombieConnectionList.TimerRunning = 0;
|
|
|
|
#ifdef ENABLE_HTTP_CONN_STATS
|
|
g_ZombieConnectionList.TotalCount = 0;
|
|
g_ZombieConnectionList.TotalZombieCount = 0;
|
|
g_ZombieConnectionList.TotalZombieRefusal = 0;
|
|
g_ZombieConnectionList.MaxZombieCount = 0;
|
|
#endif
|
|
|
|
//
|
|
// Zombie Timer stuff
|
|
//
|
|
|
|
g_ZombieConnectionList.TimerInitialized = TRUE;
|
|
|
|
UlInitializeSpinLock(
|
|
&g_ZombieConnectionList.TimerSpinLock,
|
|
"HttpZombieConnectionTimerSpinLock"
|
|
);
|
|
|
|
KeInitializeDpc(
|
|
&g_ZombieConnectionList.DpcObject, // DPC object
|
|
&UlZombieTimerDpcRoutine, // DPC routine
|
|
NULL // context
|
|
);
|
|
|
|
UlInitializeWorkItem(&g_ZombieConnectionList.WorkItem);
|
|
|
|
KeInitializeTimer(&g_ZombieConnectionList.Timer);
|
|
|
|
UlpSetZombieTimer();
|
|
|
|
//
|
|
// Everything is initialized.
|
|
//
|
|
|
|
g_HttpconnInitialized = TRUE;
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Performs global termination of this module.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlTerminateHttpConnection(
|
|
VOID
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
if (g_HttpconnInitialized)
|
|
{
|
|
// Zombie timer
|
|
|
|
UlAcquireSpinLock(&g_ZombieConnectionList.TimerSpinLock, &OldIrql);
|
|
|
|
g_ZombieConnectionList.TimerInitialized = FALSE;
|
|
|
|
KeCancelTimer(&g_ZombieConnectionList.Timer);
|
|
|
|
UlReleaseSpinLock(&g_ZombieConnectionList.TimerSpinLock, OldIrql);
|
|
|
|
UlpTerminateZombieList();
|
|
|
|
#ifdef ENABLE_HTTP_CONN_STATS
|
|
UlTrace(HTTP_IO,
|
|
("UlTerminateHttpConnection, Stats:\n"
|
|
"\tTotalConnections = %lu\n"
|
|
"\tMaxZombieCount = %lu\n"
|
|
"\tTotalDisconnectedCount = %lu\n"
|
|
"\tTotalRefusedCount =%lu\n",
|
|
g_ZombieConnectionList.TotalCount,
|
|
g_ZombieConnectionList.MaxZombieCount,
|
|
g_ZombieConnectionList.TotalZombieCount,
|
|
g_ZombieConnectionList.TotalZombieRefusal
|
|
));
|
|
#endif
|
|
|
|
UlDestroyLockedList(&g_ZombieConnectionList.LockList);
|
|
|
|
g_HttpconnInitialized = FALSE;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Creates a new HTTP_CONNECTION object.
|
|
|
|
Arguments:
|
|
|
|
ppHttpConnection - Receives a pointer to the new HTTP_CONNECTION
|
|
object if successful.
|
|
|
|
pConnection - Supplies a pointer to the UL_CONNECTION to associate
|
|
with the newly created HTTP_CONNECTION.
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlCreateHttpConnection(
|
|
OUT PUL_HTTP_CONNECTION *ppHttpConnection,
|
|
IN PUL_CONNECTION pConnection
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pHttpConn;
|
|
|
|
ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);
|
|
|
|
pHttpConn = &pConnection->HttpConnection;
|
|
RtlZeroMemory(pHttpConn, sizeof(*pHttpConn));
|
|
|
|
REFERENCE_CONNECTION(pConnection);
|
|
|
|
pHttpConn->Signature = UL_HTTP_CONNECTION_POOL_TAG;
|
|
pHttpConn->RefCount = 1;
|
|
pHttpConn->pConnection = pConnection;
|
|
pHttpConn->SecureConnection = pConnection->FilterInfo.SecureConnection;
|
|
|
|
UlInitializeWorkItem(&pHttpConn->WorkItem);
|
|
UlInitializeWorkItem(&pHttpConn->DisconnectWorkItem);
|
|
UlInitializeWorkItem(&pHttpConn->DisconnectDrainWorkItem);
|
|
UlInitializeWorkItem(&pHttpConn->ReceiveBufferWorkItem);
|
|
|
|
UlInitializeExclusiveLock(&pHttpConn->ExLock);
|
|
|
|
//
|
|
// Initialize the disconnect management fields.
|
|
//
|
|
|
|
UlInitializeNotifyHead(&pHttpConn->WaitForDisconnectHead, NULL);
|
|
|
|
//
|
|
// Init our buffer list
|
|
//
|
|
|
|
InitializeListHead(&pHttpConn->BufferHead);
|
|
|
|
//
|
|
// Init pending receive buffer list
|
|
//
|
|
|
|
InitializeSListHead(&pHttpConn->ReceiveBufferSList);
|
|
|
|
//
|
|
// init app pool process binding list
|
|
//
|
|
|
|
InitializeListHead(&(pHttpConn->BindingHead));
|
|
|
|
UlInitializePushLock(
|
|
&(pHttpConn->PushLock),
|
|
"UL_HTTP_CONNECTION[%p].PushLock",
|
|
pHttpConn,
|
|
UL_HTTP_CONNECTION_PUSHLOCK_TAG
|
|
);
|
|
|
|
//
|
|
// Initialize BufferingInfo structure.
|
|
//
|
|
|
|
UlInitializeSpinLock(
|
|
&pHttpConn->BufferingInfo.BufferingSpinLock,
|
|
"BufferingSpinLock"
|
|
);
|
|
|
|
UlInitializeSpinLock(
|
|
&pHttpConn->RequestIdSpinLock,
|
|
"RequestIdSpinLock"
|
|
);
|
|
|
|
UlTrace(
|
|
REFCOUNT, (
|
|
"http!UlCreateHttpConnection conn=%p refcount=%d\n",
|
|
pHttpConn,
|
|
pHttpConn->RefCount)
|
|
);
|
|
|
|
*ppHttpConnection = pHttpConn;
|
|
RETURN(STATUS_SUCCESS);
|
|
|
|
} // UlCreateHttpConnection
|
|
|
|
|
|
|
|
NTSTATUS
|
|
UlCreateHttpConnectionId(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection
|
|
)
|
|
{
|
|
NTSTATUS Status;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
//
|
|
// Create an opaque id for the connection
|
|
//
|
|
|
|
Status = UlAllocateOpaqueId(
|
|
&pHttpConnection->ConnectionId, // pOpaqueId
|
|
UlOpaqueIdTypeHttpConnection, // OpaqueIdType
|
|
pHttpConnection // pContext
|
|
);
|
|
|
|
if (NT_SUCCESS(Status))
|
|
{
|
|
UL_REFERENCE_HTTP_CONNECTION(pHttpConnection);
|
|
|
|
TRACE_TIME(
|
|
pHttpConnection->ConnectionId,
|
|
0,
|
|
TIME_ACTION_CREATE_CONNECTION
|
|
);
|
|
}
|
|
|
|
RETURN(Status);
|
|
|
|
} // UlCreateHttpConnectionId
|
|
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
In normal case it cleans up the connection and unlink the request.
|
|
(if there is one). But if the last sendresponse hasn't been received on
|
|
the connection yet, it moves the connection to the zombie list w/o
|
|
releasing it's refcount.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - workitem to get the http connection
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlConnectionDestroyedWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
BOOLEAN ZombieConnection = FALSE;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pHttpConnection = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_HTTP_CONNECTION,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConnection));
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"Http!UlConnectionDestroyedWorker: httpconn=%p\n",
|
|
pHttpConnection
|
|
));
|
|
|
|
#ifdef ENABLE_HTTP_CONN_STATS
|
|
InterlockedIncrement(
|
|
(PLONG) &g_ZombieConnectionList.TotalCount);
|
|
#endif
|
|
|
|
//
|
|
// Don't let connection go away while we are working. If it becomes
|
|
// zombie it may get destroyed as soon as we put it on the zombie
|
|
// list and release the connection eresource.
|
|
//
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION(pHttpConnection);
|
|
|
|
UlAcquirePushLockExclusive(&pHttpConnection->PushLock);
|
|
|
|
pRequest = pHttpConnection->pRequest;
|
|
|
|
//
|
|
// Kill the request if there is one. But only if we have done with
|
|
// the logging, otherwise we will keep it in the zombie list until last
|
|
// the last sendresponse with logging data arrives or the request
|
|
// itself get scavenged out from the zombie list later.
|
|
//
|
|
|
|
if (pRequest)
|
|
{
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
//
|
|
// Only if the last send did not happened yet.
|
|
//
|
|
|
|
if (0 == InterlockedCompareExchange(
|
|
(PLONG) &pRequest->ZombieCheck,
|
|
1,
|
|
0
|
|
))
|
|
{
|
|
//
|
|
// Only if request got routed up to the worker process
|
|
// and logging enabled but not happened.
|
|
//
|
|
|
|
if (pRequest->ConfigInfo.pLoggingConfig != NULL &&
|
|
UlCheckAppPoolState(pRequest))
|
|
{
|
|
ZombieConnection = TRUE;
|
|
}
|
|
}
|
|
|
|
if (ZombieConnection)
|
|
{
|
|
ASSERT(pHttpConnection->Zombified == FALSE);
|
|
ASSERT(pHttpConnection->pRequestIdContext != NULL);
|
|
|
|
Status = UlpZombifyHttpConnection(pHttpConnection);
|
|
if (!NT_SUCCESS(Status))
|
|
{
|
|
//
|
|
// We didn't make it to the zombie list. The list
|
|
// is probably full. Lets destroy this connection
|
|
// completely.
|
|
//
|
|
|
|
ZombieConnection = FALSE;
|
|
}
|
|
}
|
|
|
|
if (!ZombieConnection)
|
|
{
|
|
//
|
|
// If we got far enough to deliver the request then unlink it
|
|
// from the app pool. This needs to happen here because the
|
|
// queue'd IRPs keep references to the UL_INTERNAL_REQUEST
|
|
// objects. This kicks their references off and allows them
|
|
// to delete.
|
|
//
|
|
|
|
if (pRequest->ConfigInfo.pAppPool)
|
|
{
|
|
UlUnlinkRequestFromProcess(
|
|
pRequest->ConfigInfo.pAppPool,
|
|
pRequest
|
|
);
|
|
}
|
|
|
|
UlUnlinkHttpRequest(pRequest);
|
|
|
|
//
|
|
// If connection didn't go to the zombie list then
|
|
// null out our request pointer since our refcount
|
|
// has gone already.
|
|
//
|
|
|
|
pHttpConnection->pRequest = NULL;
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Make sure no one adds a new request now that we're done
|
|
//
|
|
|
|
pHttpConnection->UlconnDestroyed = 1;
|
|
|
|
if (!ZombieConnection)
|
|
{
|
|
//
|
|
// If we don't need to keep the connection around
|
|
// then remove its opaque id.
|
|
//
|
|
|
|
if (!HTTP_IS_NULL_ID(&pHttpConnection->ConnectionId))
|
|
{
|
|
UlFreeOpaqueId(
|
|
pHttpConnection->ConnectionId,
|
|
UlOpaqueIdTypeHttpConnection
|
|
);
|
|
|
|
HTTP_SET_NULL_ID(&pHttpConnection->ConnectionId);
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConnection);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Complete all WaitForDisconnect IRPs
|
|
//
|
|
// Prevent new IRPs from getting attached to the connection,
|
|
// by setting the DisconnectFlag.
|
|
//
|
|
|
|
ASSERT(!pHttpConnection->DisconnectFlag);
|
|
pHttpConnection->DisconnectFlag = 1;
|
|
|
|
if (pHttpConnection->WaitForDisconnectFlag)
|
|
{
|
|
UlCompleteAllWaitForDisconnect(pHttpConnection);
|
|
}
|
|
|
|
UlReleasePushLockExclusive(&pHttpConnection->PushLock);
|
|
|
|
//
|
|
// Release the temporary refcount.
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConnection);
|
|
|
|
//
|
|
// Only remove the ULTDI reference from the HTTP_CONNECTION,
|
|
// if connection didn't make it to the zombie list. Otherwise
|
|
// the actual cleanup will happen later. Zombie list takes the
|
|
// ownership of the TDI's original refcount now.
|
|
//
|
|
|
|
if (!ZombieConnection)
|
|
{
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConnection);
|
|
}
|
|
|
|
} // UlConnectionDestroyedWorker
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Tries to establish a binding between a connection and an app pool
|
|
process. You can query these bindings with UlQueryProcessBinding.
|
|
|
|
Arguments:
|
|
|
|
pHttpConnection - the connection to bind
|
|
pAppPool - the app pool that owns the process (key for lookups)
|
|
pProcess - the process to bind to
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlBindConnectionToProcess(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
IN PUL_APP_POOL_OBJECT pAppPool,
|
|
IN PUL_APP_POOL_PROCESS pProcess
|
|
)
|
|
{
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
PUL_APOOL_PROC_BINDING pBinding = NULL;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConnection ) );
|
|
ASSERT( pAppPool->NumberActiveProcesses > 1 || pProcess == NULL );
|
|
ASSERT( UlDbgSpinLockOwned( &pAppPool->SpinLock ) );
|
|
|
|
//
|
|
// see if there's already a binding object
|
|
//
|
|
pBinding = UlpFindProcBinding(pHttpConnection, pAppPool);
|
|
if (pBinding) {
|
|
if (pProcess) {
|
|
//
|
|
// modify the binding
|
|
//
|
|
pBinding->pProcess = pProcess;
|
|
} else {
|
|
//
|
|
// we're supposed to kill this binding
|
|
//
|
|
RemoveEntryList(&pBinding->BindingEntry);
|
|
UlpFreeProcBinding(pBinding);
|
|
}
|
|
} else {
|
|
if (pProcess) {
|
|
//
|
|
// create a new entry
|
|
//
|
|
pBinding = UlpCreateProcBinding(pAppPool, pProcess);
|
|
|
|
if (pBinding) {
|
|
InsertHeadList(
|
|
&pHttpConnection->BindingHead,
|
|
&pBinding->BindingEntry
|
|
);
|
|
} else {
|
|
Status = STATUS_INSUFFICIENT_RESOURCES;
|
|
}
|
|
}
|
|
}
|
|
|
|
UlTrace(ROUTING, (
|
|
"http!UlBindConnectionToProcess(\n"
|
|
" pHttpConnection = %p (%I64x)\n"
|
|
" pAppPool = %p\n"
|
|
" pProcess = %p )\n"
|
|
" Status = 0x%x\n",
|
|
pHttpConnection,
|
|
pHttpConnection->ConnectionId,
|
|
pAppPool,
|
|
pProcess,
|
|
Status
|
|
));
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes an HTTP request from all lists and cleans up it's opaque id.
|
|
|
|
Arguments:
|
|
|
|
pRequest - the request to be unlinked
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlCleanupHttpConnection(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
|
|
ASSERT( UL_IS_VALID_INTERNAL_REQUEST( pHttpConnection->pRequest ) );
|
|
ASSERT( pHttpConnection->WaitingForResponse );
|
|
ASSERT( UlDbgPushLockOwnedExclusive( &pHttpConnection->PushLock ) );
|
|
|
|
pRequest = pHttpConnection->pRequest;
|
|
|
|
if (pRequest->ContentLength > 0 || 1 == pRequest->Chunked)
|
|
{
|
|
//
|
|
// Unlink the request from the process for POST. The time to
|
|
// unlink a GET request is when the last send completes.
|
|
// However, similar logic can't be applied to POST because
|
|
// we may still have to drain the entity body so we are not
|
|
// fully done with the request on send completion. So the
|
|
// arrangement for POST is to 1) defer resuming parsing until send
|
|
// completion, and 2) unlink the request from process when we
|
|
// have drained the whole entity body unless server has issued
|
|
// the disconnect.
|
|
//
|
|
|
|
if (pRequest->ConfigInfo.pAppPool)
|
|
{
|
|
UlUnlinkRequestFromProcess(
|
|
pRequest->ConfigInfo.pAppPool,
|
|
pRequest
|
|
);
|
|
}
|
|
}
|
|
|
|
UlUnlinkHttpRequest(pRequest);
|
|
|
|
pHttpConnection->pRequest = NULL;
|
|
pHttpConnection->WaitingForResponse = 0;
|
|
|
|
UlTrace( PARSER, (
|
|
"***1 pConnection %p->WaitingForResponse = 0\n",
|
|
pHttpConnection
|
|
));
|
|
|
|
//
|
|
// Free the old request's buffers
|
|
//
|
|
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER( pHttpConnection->pCurrentBuffer ) );
|
|
|
|
pEntry = pHttpConnection->BufferHead.Flink;
|
|
while (pEntry != &pHttpConnection->BufferHead)
|
|
{
|
|
PUL_REQUEST_BUFFER pBuffer;
|
|
|
|
//
|
|
// Get the object
|
|
//
|
|
|
|
pBuffer = CONTAINING_RECORD(
|
|
pEntry,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
//
|
|
// did we reach the end?
|
|
//
|
|
|
|
if (pBuffer == pHttpConnection->pCurrentBuffer) {
|
|
break;
|
|
}
|
|
|
|
//
|
|
// remember the next one
|
|
//
|
|
|
|
pEntry = pEntry->Flink;
|
|
|
|
//
|
|
// unlink it
|
|
//
|
|
|
|
UlFreeRequestBuffer(pBuffer);
|
|
}
|
|
} // UlCleanupHttpConnection
|
|
|
|
|
|
VOID
|
|
UlReferenceHttpConnection(
|
|
IN PVOID pObject
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
PUL_HTTP_CONNECTION pHttpConnection = (PUL_HTTP_CONNECTION) pObject;
|
|
|
|
refCount = InterlockedIncrement( &pHttpConnection->RefCount );
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pHttpConnectionTraceLog,
|
|
pHttpConnection->pConnection->pHttpTraceLog,
|
|
REF_ACTION_REFERENCE_HTTP_CONNECTION,
|
|
refCount,
|
|
pHttpConnection,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(
|
|
REFCOUNT, (
|
|
"http!UlReferenceHttpConnection conn=%p refcount=%d\n",
|
|
pHttpConnection,
|
|
refCount)
|
|
);
|
|
|
|
} // UlReferenceHttpConnection
|
|
|
|
|
|
VOID
|
|
UlDereferenceHttpConnection(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
ASSERT( pHttpConnection );
|
|
ASSERT( pHttpConnection->RefCount > 0 );
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pHttpConnectionTraceLog,
|
|
pHttpConnection->pConnection->pHttpTraceLog,
|
|
REF_ACTION_DEREFERENCE_HTTP_CONNECTION,
|
|
pHttpConnection->RefCount - 1,
|
|
pHttpConnection,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
refCount = InterlockedDecrement( &pHttpConnection->RefCount );
|
|
|
|
UlTrace(
|
|
REFCOUNT, (
|
|
"http!UlDereferenceHttpConnection conn=%p refcount=%d\n",
|
|
pHttpConnection,
|
|
refCount)
|
|
);
|
|
|
|
if (refCount == 0)
|
|
{
|
|
//
|
|
// now it's time to free the connection, we need to do
|
|
// this at lower'd irql, let's check it out
|
|
//
|
|
|
|
UL_CALL_PASSIVE(
|
|
&(pHttpConnection->WorkItem),
|
|
&UlDeleteHttpConnection
|
|
);
|
|
}
|
|
|
|
} // UlDereferenceHttpConnection
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Frees all resources associated with the specified HTTP_CONNECTION.
|
|
|
|
Arguments:
|
|
|
|
pWorkItem - Supplies the HTTP_CONNECTION to free.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlDeleteHttpConnection(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
PUL_HTTP_CONNECTION pHttpConnection;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pHttpConnection = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_HTTP_CONNECTION,
|
|
WorkItem
|
|
);
|
|
|
|
ASSERT( pHttpConnection != NULL &&
|
|
pHttpConnection->Signature == UL_HTTP_CONNECTION_POOL_TAG );
|
|
ASSERT(HTTP_IS_NULL_ID(&pHttpConnection->ConnectionId));
|
|
ASSERT(pHttpConnection->TotalSendBytes == 0);
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pHttpConnectionTraceLog,
|
|
pHttpConnection->pConnection->pHttpTraceLog,
|
|
REF_ACTION_DESTROY_HTTP_CONNECTION,
|
|
0,
|
|
pHttpConnection,
|
|
__FILE__,
|
|
__LINE__
|
|
);
|
|
|
|
//
|
|
// The request is gone by now
|
|
//
|
|
|
|
ASSERT(pHttpConnection->pRequest == NULL);
|
|
|
|
//
|
|
// remove from Timeout Timer Wheel(c)
|
|
//
|
|
|
|
UlTimeoutRemoveTimerWheelEntry(
|
|
&pHttpConnection->TimeoutInfo
|
|
);
|
|
|
|
//
|
|
// delete the buffer list
|
|
//
|
|
|
|
pEntry = pHttpConnection->BufferHead.Flink;
|
|
while (pEntry != &pHttpConnection->BufferHead)
|
|
{
|
|
PUL_REQUEST_BUFFER pBuffer;
|
|
|
|
//
|
|
// Get the object
|
|
//
|
|
|
|
pBuffer = CONTAINING_RECORD(
|
|
pEntry,
|
|
UL_REQUEST_BUFFER,
|
|
ListEntry
|
|
);
|
|
|
|
//
|
|
// remember the next one
|
|
//
|
|
|
|
pEntry = pEntry->Flink;
|
|
|
|
//
|
|
// unlink it
|
|
//
|
|
|
|
UlFreeRequestBuffer(pBuffer);
|
|
}
|
|
|
|
ASSERT(IsListEmpty(&pHttpConnection->BufferHead));
|
|
|
|
//
|
|
// get rid of any bindings we're keeping
|
|
//
|
|
while (!IsListEmpty(&pHttpConnection->BindingHead))
|
|
{
|
|
PUL_APOOL_PROC_BINDING pBinding;
|
|
|
|
//
|
|
// Get the object
|
|
//
|
|
pEntry = RemoveHeadList(&pHttpConnection->BindingHead);
|
|
|
|
pBinding = CONTAINING_RECORD(
|
|
pEntry,
|
|
UL_APOOL_PROC_BINDING,
|
|
BindingEntry
|
|
);
|
|
|
|
ASSERT( IS_VALID_PROC_BINDING(pBinding) );
|
|
|
|
//
|
|
// free it
|
|
//
|
|
|
|
UlpFreeProcBinding(pBinding);
|
|
}
|
|
|
|
//
|
|
// get rid of our resource
|
|
//
|
|
UlDeletePushLock(&pHttpConnection->PushLock);
|
|
|
|
//
|
|
// Attempt to remove any QoS filter on this connection,
|
|
// if BWT is enabled.
|
|
//
|
|
|
|
if (pHttpConnection->BandwidthThrottlingEnabled)
|
|
{
|
|
UlTcDeleteFilter(pHttpConnection);
|
|
}
|
|
|
|
//
|
|
// Decrement the global connection limit. Its safe to decrement for
|
|
// global case because http object doesn't even allocated for global
|
|
// rejection which happens as tcp refusal early when acepting the
|
|
// connection request.
|
|
//
|
|
|
|
UlDecrementConnections( &g_CurrentConnections );
|
|
|
|
//
|
|
// Decrement the site connections and let our ref go away. If the
|
|
// pConnectionCountEntry is not null, we have been accepted.
|
|
//
|
|
|
|
if (pHttpConnection->pConnectionCountEntry)
|
|
{
|
|
UlDecrementConnections(
|
|
&pHttpConnection->pConnectionCountEntry->CurConnections );
|
|
|
|
DEREFERENCE_CONNECTION_COUNT_ENTRY(pHttpConnection->pConnectionCountEntry);
|
|
pHttpConnection->pConnectionCountEntry = NULL;
|
|
}
|
|
|
|
//
|
|
// Remove final (previous) site counter entry reference
|
|
// (matches reference in UlSendCachedResponse/UlDeliverHttpRequest)
|
|
//
|
|
if (pHttpConnection->pPrevSiteCounters)
|
|
{
|
|
UlDecSiteCounter(
|
|
pHttpConnection->pPrevSiteCounters,
|
|
HttpSiteCounterCurrentConns
|
|
);
|
|
|
|
DEREFERENCE_SITE_COUNTER_ENTRY(pHttpConnection->pPrevSiteCounters);
|
|
pHttpConnection->pPrevSiteCounters = NULL;
|
|
}
|
|
|
|
|
|
//
|
|
// free the memory
|
|
//
|
|
|
|
pHttpConnection->Signature = MAKE_FREE_SIGNATURE(
|
|
UL_HTTP_CONNECTION_POOL_TAG
|
|
);
|
|
|
|
//
|
|
// let go the UL_CONNECTION
|
|
//
|
|
|
|
DEREFERENCE_CONNECTION( pHttpConnection->pConnection );
|
|
|
|
} // UlDeleteHttpConnection
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
|
|
Arguments:
|
|
|
|
Return Value:
|
|
|
|
NTSTATUS - Completion status.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpCreateHttpRequest(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
OUT PUL_INTERNAL_REQUEST *ppRequest
|
|
)
|
|
{
|
|
PUL_INTERNAL_REQUEST pRequest = NULL;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pRequest = UlPplAllocateInternalRequest();
|
|
|
|
if (pRequest == NULL)
|
|
{
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
ASSERT( pRequest->Signature == UL_INTERNAL_REQUEST_POOL_TAG );
|
|
|
|
//
|
|
// Keep a reference to the connection.
|
|
//
|
|
|
|
UL_REFERENCE_HTTP_CONNECTION( pHttpConnection );
|
|
|
|
pRequest->Signature = UL_INTERNAL_REQUEST_POOL_TAG;
|
|
pRequest->pHttpConn = pHttpConnection;
|
|
pRequest->ConnectionId = pHttpConnection->ConnectionId;
|
|
pRequest->Secure = pHttpConnection->SecureConnection;
|
|
|
|
//
|
|
// Set first request flag, used to decide if we need to complete demand
|
|
// start IRPs.
|
|
//
|
|
|
|
ASSERT( pHttpConnection->pCurrentBuffer );
|
|
|
|
if (0 == pHttpConnection->pCurrentBuffer->BufferNumber &&
|
|
0 == pHttpConnection->pCurrentBuffer->ParsedBytes)
|
|
{
|
|
pRequest->FirstRequest = TRUE;
|
|
}
|
|
else
|
|
{
|
|
pRequest->FirstRequest = FALSE;
|
|
}
|
|
|
|
//
|
|
// Grab the raw connection id off the UL_CONNECTION.
|
|
//
|
|
|
|
pRequest->RawConnectionId = pHttpConnection->pConnection->FilterInfo.ConnectionId;
|
|
|
|
|
|
CREATE_REF_TRACE_LOG( pRequest->pTraceLog,
|
|
32 - REF_TRACE_OVERHEAD,
|
|
0, TRACELOG_LOW_PRIORITY,
|
|
UL_INTERNAL_REQUEST_REF_TRACE_LOG_POOL_TAG );
|
|
|
|
//
|
|
// UL Handle the request received Event. Record the Connection Id
|
|
// and the client IP address.
|
|
//
|
|
|
|
if (ETW_LOG_RESOURCE())
|
|
{
|
|
UlEtwTraceEvent(
|
|
&UlTransGuid,
|
|
ETW_TYPE_START,
|
|
(PVOID) &pRequest,
|
|
sizeof(PVOID),
|
|
(PVOID) &pHttpConnection->pConnection->AddressType,
|
|
sizeof(USHORT),
|
|
(PVOID) &pHttpConnection->pConnection->RemoteAddress,
|
|
pHttpConnection->pConnection->AddressLength,
|
|
NULL,
|
|
0
|
|
);
|
|
}
|
|
|
|
//
|
|
// Increase the number of outstanding requests on this connection.
|
|
//
|
|
|
|
InterlockedIncrement((PLONG) &pHttpConnection->PipelinedRequests);
|
|
|
|
//
|
|
// return it
|
|
//
|
|
|
|
*ppRequest = pRequest;
|
|
|
|
UlTrace(REFCOUNT, (
|
|
"http!UlpCreateHttpRequest req=%p refcount=%d\n",
|
|
pRequest,
|
|
pRequest->RefCount
|
|
));
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlpCreateHttpRequest
|
|
|
|
|
|
VOID
|
|
UlReferenceHttpRequest(
|
|
IN PVOID pObject
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
PUL_INTERNAL_REQUEST pRequest = (PUL_INTERNAL_REQUEST) pObject;
|
|
|
|
refCount = InterlockedIncrement( &pRequest->RefCount );
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pHttpRequestTraceLog,
|
|
pRequest->pTraceLog,
|
|
REF_ACTION_REFERENCE_HTTP_REQUEST,
|
|
refCount,
|
|
pRequest,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(
|
|
REFCOUNT, (
|
|
"http!UlReferenceHttpRequest req=%p refcount=%d\n",
|
|
pRequest,
|
|
refCount)
|
|
);
|
|
|
|
} // UlReferenceHttpRequest
|
|
|
|
VOID
|
|
UlDereferenceHttpRequest(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
WRITE_REF_TRACE_LOG2(
|
|
g_pHttpRequestTraceLog,
|
|
pRequest->pTraceLog,
|
|
REF_ACTION_DEREFERENCE_HTTP_REQUEST,
|
|
pRequest->RefCount - 1,
|
|
pRequest,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
refCount = InterlockedDecrement( &pRequest->RefCount );
|
|
|
|
UlTrace(
|
|
REFCOUNT, (
|
|
"http!UlDereferenceHttpRequest req=%p refcount=%d\n",
|
|
pRequest,
|
|
refCount)
|
|
);
|
|
|
|
if (refCount == 0)
|
|
{
|
|
UL_CALL_PASSIVE(
|
|
&pRequest->WorkItem,
|
|
&UlpFreeHttpRequest
|
|
);
|
|
}
|
|
|
|
} // UlDereferenceHttpRequest
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Cancels all pending http io.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Supplies the HTTP_REQUEST.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlCancelRequestIo(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
PIRP pIrp;
|
|
PUL_CHUNK_TRACKER pTracker;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(UlDbgPushLockOwnedExclusive(&pRequest->pHttpConn->PushLock));
|
|
|
|
//
|
|
// Mark the request as InCleanup, so additional IRPs
|
|
// on the request will not be queued.
|
|
//
|
|
|
|
pRequest->InCleanup = 1;
|
|
|
|
//
|
|
// tank all pending io on this request
|
|
//
|
|
|
|
while (IsListEmpty(&pRequest->ResponseHead) == FALSE)
|
|
{
|
|
//
|
|
// Pop the SendHttpResponse/EntityBody IRP off the list.
|
|
//
|
|
|
|
pEntry = RemoveHeadList(&pRequest->ResponseHead);
|
|
pTracker = CONTAINING_RECORD(pEntry, UL_CHUNK_TRACKER, ListEntry);
|
|
ASSERT(IS_VALID_CHUNK_TRACKER(pTracker));
|
|
|
|
//
|
|
// Complete the send with STATUS_CONNECTION_RESET. This is better
|
|
// than using STATUS_CANCELLED since we always reset the connection
|
|
// upon hitting an error.
|
|
//
|
|
|
|
UlCompleteSendResponse(pTracker, STATUS_CONNECTION_RESET);
|
|
}
|
|
|
|
while (IsListEmpty(&pRequest->IrpHead) == FALSE)
|
|
{
|
|
//
|
|
// Pop the ReceiveEntityBody IRP off the list.
|
|
//
|
|
|
|
pEntry = RemoveHeadList(&pRequest->IrpHead);
|
|
pEntry->Blink = pEntry->Flink = NULL;
|
|
|
|
pIrp = CONTAINING_RECORD(pEntry, IRP, Tail.Overlay.ListEntry);
|
|
ASSERT(IS_VALID_IRP(pIrp));
|
|
|
|
//
|
|
// pop the cancel routine
|
|
//
|
|
|
|
if (IoSetCancelRoutine(pIrp, NULL) == NULL)
|
|
{
|
|
//
|
|
// IoCancelIrp pop'd it first
|
|
//
|
|
// ok to just ignore this irp, it's been pop'd off the queue
|
|
// and will be completed in the cancel routine.
|
|
//
|
|
// keep looping
|
|
//
|
|
|
|
pIrp = NULL;
|
|
}
|
|
else
|
|
{
|
|
PUL_INTERNAL_REQUEST pIrpRequest;
|
|
|
|
//
|
|
// cancel it. even if pIrp->Cancel == TRUE we are supposed to
|
|
// complete it, our cancel routine will never run.
|
|
//
|
|
|
|
pIrpRequest = (PUL_INTERNAL_REQUEST)(
|
|
IoGetCurrentIrpStackLocation(pIrp)->
|
|
Parameters.DeviceIoControl.Type3InputBuffer
|
|
);
|
|
|
|
ASSERT(pIrpRequest == pRequest);
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(pIrpRequest);
|
|
|
|
IoGetCurrentIrpStackLocation(pIrp)->
|
|
Parameters.DeviceIoControl.Type3InputBuffer = NULL;
|
|
|
|
pIrp->IoStatus.Status = STATUS_CANCELLED;
|
|
pIrp->IoStatus.Information = 0;
|
|
|
|
UlCompleteRequest(pIrp, IO_NO_INCREMENT);
|
|
pIrp = NULL;
|
|
}
|
|
|
|
} // while (IsListEmpty(&pRequest->IrpHead) == FALSE)
|
|
|
|
} // UlCancelRequestIo
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Frees all resources associated with the specified UL_INTERNAL_REQUEST.
|
|
|
|
Arguments:
|
|
|
|
pRequest - Supplies the UL_INTERNAL_REQUEST to free.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpFreeHttpRequest(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
PLIST_ENTRY pEntry;
|
|
ULONG i;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
pRequest = CONTAINING_RECORD(
|
|
pWorkItem,
|
|
UL_INTERNAL_REQUEST,
|
|
WorkItem
|
|
);
|
|
|
|
//
|
|
// There should not be a LogData hanging around.
|
|
//
|
|
|
|
ASSERT(!pRequest->pLogData);
|
|
|
|
//
|
|
// our opaque id should already be free'd (UlDeleteOpaqueIds)
|
|
//
|
|
|
|
ASSERT(HTTP_IS_NULL_ID(&pRequest->RequestId));
|
|
|
|
//
|
|
// free any known header buffers allocated
|
|
//
|
|
|
|
if (pRequest->HeadersAppended)
|
|
{
|
|
for (i = 0; i < HttpHeaderRequestMaximum; i++)
|
|
{
|
|
HTTP_HEADER_ID HeaderId = (HTTP_HEADER_ID)pRequest->HeaderIndex[i];
|
|
|
|
if (HeaderId == HttpHeaderRequestMaximum)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ASSERT( pRequest->HeaderValid[HeaderId] );
|
|
|
|
if (pRequest->Headers[HeaderId].OurBuffer == 1)
|
|
{
|
|
UL_FREE_POOL(
|
|
pRequest->Headers[HeaderId].pHeader,
|
|
HEADER_VALUE_POOL_TAG
|
|
);
|
|
}
|
|
}
|
|
|
|
//
|
|
// and any unknown header buffers allocated
|
|
//
|
|
|
|
while (IsListEmpty(&pRequest->UnknownHeaderList) == FALSE)
|
|
{
|
|
PUL_HTTP_UNKNOWN_HEADER pUnknownHeader;
|
|
|
|
pEntry = RemoveHeadList(&pRequest->UnknownHeaderList);
|
|
|
|
pUnknownHeader = CONTAINING_RECORD(
|
|
pEntry,
|
|
UL_HTTP_UNKNOWN_HEADER,
|
|
List
|
|
);
|
|
|
|
if (pUnknownHeader->HeaderValue.OurBuffer == 1)
|
|
{
|
|
UL_FREE_POOL(
|
|
pUnknownHeader->HeaderValue.pHeader,
|
|
HEADER_VALUE_POOL_TAG
|
|
);
|
|
}
|
|
|
|
//
|
|
// Free the header structure
|
|
//
|
|
|
|
if (pUnknownHeader->HeaderValue.ExternalAllocated == 1)
|
|
{
|
|
UL_FREE_POOL(
|
|
pUnknownHeader,
|
|
UL_HTTP_UNKNOWN_HEADER_POOL_TAG
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// there better not be any pending io, it would have been cancelled either
|
|
// in UlDeleteHttpConnection or in UlDetachProcessFromAppPool .
|
|
//
|
|
|
|
ASSERT(IsListEmpty(&pRequest->IrpHead));
|
|
ASSERT(pRequest->SendsPending == 0);
|
|
|
|
//
|
|
// dereferenc request buffers.
|
|
//
|
|
|
|
for (i = 0; i < pRequest->UsedRefBuffers; i++)
|
|
{
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER(pRequest->pRefBuffers[i]) );
|
|
UL_DEREFERENCE_REQUEST_BUFFER(pRequest->pRefBuffers[i]);
|
|
}
|
|
|
|
if (pRequest->AllocRefBuffers > 1)
|
|
{
|
|
UL_FREE_POOL(
|
|
pRequest->pRefBuffers,
|
|
UL_REF_REQUEST_BUFFER_POOL_TAG
|
|
);
|
|
}
|
|
|
|
//
|
|
// free any url that was allocated
|
|
//
|
|
|
|
if (pRequest->CookedUrl.pUrl != NULL)
|
|
{
|
|
if (pRequest->CookedUrl.pUrl != pRequest->pUrlBuffer)
|
|
{
|
|
UL_FREE_POOL(pRequest->CookedUrl.pUrl, URL_POOL_TAG);
|
|
}
|
|
}
|
|
|
|
if (pRequest->CookedUrl.pRoutingToken != NULL)
|
|
{
|
|
if (pRequest->CookedUrl.pRoutingToken != pRequest->pDefaultRoutingTokenBuffer)
|
|
{
|
|
UL_FREE_POOL(pRequest->CookedUrl.pRoutingToken, URL_POOL_TAG);
|
|
}
|
|
}
|
|
|
|
//
|
|
// free any config group info
|
|
//
|
|
|
|
ASSERT( IS_VALID_URL_CONFIG_GROUP_INFO(&pRequest->ConfigInfo) );
|
|
ASSERT( pRequest->pHttpConn );
|
|
|
|
//
|
|
// Perf counters
|
|
// NOTE: Assumes cache & non-cache paths both go through here
|
|
// NOTE: If connection is refused the pConnectionCountEntry will be NULL
|
|
//
|
|
if (pRequest->ConfigInfo.pSiteCounters &&
|
|
pRequest->pHttpConn->pConnectionCountEntry)
|
|
{
|
|
PUL_SITE_COUNTER_ENTRY pCtr = pRequest->ConfigInfo.pSiteCounters;
|
|
|
|
UlAddSiteCounter64(
|
|
pCtr,
|
|
HttpSiteCounterBytesSent,
|
|
pRequest->BytesSent
|
|
);
|
|
|
|
UlAddSiteCounter64(
|
|
pCtr,
|
|
HttpSiteCounterBytesReceived,
|
|
pRequest->BytesReceived
|
|
);
|
|
|
|
UlAddSiteCounter64(
|
|
pCtr,
|
|
HttpSiteCounterBytesTransfered,
|
|
(pRequest->BytesSent + pRequest->BytesReceived)
|
|
);
|
|
|
|
}
|
|
|
|
//
|
|
// Release all the references from the UL_URL_CONFIG_GROUP_INFO.
|
|
//
|
|
if (pRequest->ConfigInfo.UrlInfoSet)
|
|
{
|
|
UlConfigGroupInfoRelease(&pRequest->ConfigInfo);
|
|
}
|
|
|
|
//
|
|
// Decrease the number of outstanding requests on this connection.
|
|
//
|
|
InterlockedDecrement((PLONG) &pRequest->pHttpConn->PipelinedRequests);
|
|
|
|
//
|
|
// release our reference to the connection
|
|
//
|
|
UL_DEREFERENCE_HTTP_CONNECTION( pRequest->pHttpConn );
|
|
pRequest->pHttpConn = NULL;
|
|
|
|
//
|
|
// release our reference to the process
|
|
//
|
|
if (pRequest->AppPool.pProcess)
|
|
{
|
|
DEREFERENCE_APP_POOL_PROCESS( pRequest->AppPool.pProcess );
|
|
pRequest->AppPool.pProcess = NULL;
|
|
}
|
|
|
|
//
|
|
// Free the object buffer
|
|
//
|
|
ASSERT( pRequest->Signature == UL_INTERNAL_REQUEST_POOL_TAG );
|
|
|
|
DESTROY_REF_TRACE_LOG( pRequest->pTraceLog,
|
|
UL_INTERNAL_REQUEST_REF_TRACE_LOG_POOL_TAG );
|
|
|
|
|
|
//
|
|
// Initialize the Request structure before putting it on the free list
|
|
//
|
|
|
|
INIT_HTTP_REQUEST( pRequest );
|
|
|
|
UlPplFreeInternalRequest( pRequest );
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Allocates and initializes a new UL_REQUEST_BUFFER.
|
|
|
|
Arguments:
|
|
|
|
BufferSize - size of the new buffer in bytes
|
|
|
|
--***************************************************************************/
|
|
PUL_REQUEST_BUFFER
|
|
UlCreateRequestBuffer(
|
|
ULONG BufferSize,
|
|
ULONG BufferNumber,
|
|
BOOLEAN UseLookaside
|
|
)
|
|
{
|
|
PUL_REQUEST_BUFFER pBuffer;
|
|
BOOLEAN FromLookaside;
|
|
|
|
if (UseLookaside && BufferSize <= DEFAULT_MAX_REQUEST_BUFFER_SIZE)
|
|
{
|
|
BufferSize = DEFAULT_MAX_REQUEST_BUFFER_SIZE;
|
|
FromLookaside = TRUE;
|
|
|
|
pBuffer = UlPplAllocateRequestBuffer();
|
|
}
|
|
else
|
|
{
|
|
FromLookaside = FALSE;
|
|
|
|
pBuffer = UL_ALLOCATE_STRUCT_WITH_SPACE(
|
|
NonPagedPool,
|
|
UL_REQUEST_BUFFER,
|
|
BufferSize,
|
|
UL_REQUEST_BUFFER_POOL_TAG
|
|
);
|
|
}
|
|
|
|
if (pBuffer == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
RtlZeroMemory((PCHAR)pBuffer, sizeof(UL_REQUEST_BUFFER));
|
|
|
|
pBuffer->Signature = UL_REQUEST_BUFFER_POOL_TAG;
|
|
UlInitializeWorkItem(&pBuffer->WorkItem);
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlCreateRequestBuffer buff=%p num=#%d size=%d\n",
|
|
pBuffer,
|
|
BufferNumber,
|
|
BufferSize
|
|
));
|
|
|
|
pBuffer->RefCount = 1;
|
|
pBuffer->AllocBytes = BufferSize;
|
|
pBuffer->BufferNumber = BufferNumber;
|
|
pBuffer->FromLookaside = FromLookaside;
|
|
|
|
return pBuffer;
|
|
} // UlCreateRequestBuffer
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes a request buffer from the buffer list and destroys it.
|
|
|
|
Arguments:
|
|
|
|
pBuffer - the buffer to be deleted
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlFreeRequestBuffer(
|
|
PUL_REQUEST_BUFFER pBuffer
|
|
)
|
|
{
|
|
ASSERT( UL_IS_VALID_REQUEST_BUFFER( pBuffer ) );
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"http!UlFreeRequestBuffer(pBuffer = %p, Num = #%d)\n",
|
|
pBuffer,
|
|
pBuffer->BufferNumber
|
|
));
|
|
|
|
if (pBuffer->ListEntry.Flink != NULL)
|
|
{
|
|
RemoveEntryList(&pBuffer->ListEntry);
|
|
pBuffer->ListEntry.Blink = pBuffer->ListEntry.Flink = NULL;
|
|
}
|
|
|
|
UL_DEREFERENCE_REQUEST_BUFFER(pBuffer);
|
|
} // UlFreeRequestBuffer
|
|
|
|
|
|
//
|
|
// Private functions.
|
|
//
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Retrieves a binding set with UlBindConnectionToProcess.
|
|
|
|
Arguments:
|
|
|
|
pHttpConnection - the connection to query
|
|
pAppPool - the key to use for the lookup
|
|
|
|
Return Values:
|
|
|
|
A pointer to the bound process if one was found. NULL otherwise.
|
|
|
|
--***************************************************************************/
|
|
PUL_APP_POOL_PROCESS
|
|
UlQueryProcessBinding(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
IN PUL_APP_POOL_OBJECT pAppPool
|
|
)
|
|
{
|
|
PUL_APOOL_PROC_BINDING pBinding;
|
|
PUL_APP_POOL_PROCESS pProcess = NULL;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION( pHttpConnection ) );
|
|
ASSERT( UlDbgSpinLockOwned( &pAppPool->SpinLock ) );
|
|
|
|
pBinding = UlpFindProcBinding(pHttpConnection, pAppPool);
|
|
|
|
if (pBinding) {
|
|
pProcess = pBinding->pProcess;
|
|
}
|
|
|
|
return pProcess;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Allocates and builds a UL_APOOL_PROC_BINDING object.
|
|
|
|
Arguments:
|
|
|
|
pAppPool - the lookup key
|
|
pProcess - the binding
|
|
|
|
Return Values:
|
|
|
|
a pointer to the allocated object, or NULL on failure
|
|
|
|
--***************************************************************************/
|
|
PUL_APOOL_PROC_BINDING
|
|
UlpCreateProcBinding(
|
|
IN PUL_APP_POOL_OBJECT pAppPool,
|
|
IN PUL_APP_POOL_PROCESS pProcess
|
|
)
|
|
{
|
|
PUL_APOOL_PROC_BINDING pBinding;
|
|
|
|
ASSERT( UlDbgSpinLockOwned( &pAppPool->SpinLock ) );
|
|
ASSERT( pAppPool->NumberActiveProcesses > 1 );
|
|
|
|
//
|
|
// CODEWORK: lookaside
|
|
//
|
|
|
|
pBinding = UL_ALLOCATE_STRUCT(
|
|
NonPagedPool,
|
|
UL_APOOL_PROC_BINDING,
|
|
UL_APOOL_PROC_BINDING_POOL_TAG
|
|
);
|
|
|
|
if (pBinding) {
|
|
pBinding->Signature = UL_APOOL_PROC_BINDING_POOL_TAG;
|
|
pBinding->pAppPool = pAppPool;
|
|
pBinding->pProcess = pProcess;
|
|
|
|
UlTrace(ROUTING, (
|
|
"http!UlpCreateProcBinding(\n"
|
|
" pAppPool = %p\n"
|
|
" pProcess = %p )\n"
|
|
" pBinding = %p\n",
|
|
pAppPool,
|
|
pProcess,
|
|
pBinding
|
|
));
|
|
}
|
|
|
|
return pBinding;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Gets rid of a proc binding
|
|
|
|
Arguments:
|
|
|
|
pBinding - the binding to get rid of
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlpFreeProcBinding(
|
|
IN PUL_APOOL_PROC_BINDING pBinding
|
|
)
|
|
{
|
|
UL_FREE_POOL_WITH_SIG(pBinding, UL_APOOL_PROC_BINDING_POOL_TAG);
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Searches a connection's list of bindings for one that has the right
|
|
app pool key
|
|
|
|
Arguments:
|
|
|
|
pHttpConnection - the connection to search
|
|
pAppPool - the key
|
|
|
|
Return Values:
|
|
|
|
The binding if found or NULL if not found.
|
|
|
|
--***************************************************************************/
|
|
PUL_APOOL_PROC_BINDING
|
|
UlpFindProcBinding(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
IN PUL_APP_POOL_OBJECT pAppPool
|
|
)
|
|
{
|
|
PLIST_ENTRY pEntry;
|
|
PUL_APOOL_PROC_BINDING pBinding = NULL;
|
|
|
|
//
|
|
// Sanity check
|
|
//
|
|
ASSERT( UlDbgSpinLockOwned( &pAppPool->SpinLock ) );
|
|
ASSERT( UL_IS_VALID_HTTP_CONNECTION(pHttpConnection) );
|
|
|
|
pEntry = pHttpConnection->BindingHead.Flink;
|
|
while (pEntry != &pHttpConnection->BindingHead) {
|
|
|
|
pBinding = CONTAINING_RECORD(
|
|
pEntry,
|
|
UL_APOOL_PROC_BINDING,
|
|
BindingEntry
|
|
);
|
|
|
|
ASSERT( IS_VALID_PROC_BINDING(pBinding) );
|
|
|
|
if (pBinding->pAppPool == pAppPool) {
|
|
//
|
|
// got it!
|
|
//
|
|
break;
|
|
}
|
|
|
|
pEntry = pEntry->Flink;
|
|
}
|
|
|
|
return pBinding;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes an HTTP request from all lists and cleans up it's opaque id.
|
|
|
|
Arguments:
|
|
|
|
pRequest - the request to be unlinked
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlUnlinkHttpRequest(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
ASSERT(UlDbgPushLockOwnedExclusive(&pRequest->pHttpConn->PushLock));
|
|
|
|
//
|
|
// cancel i/o
|
|
//
|
|
|
|
UlCancelRequestIo(pRequest);
|
|
|
|
//
|
|
// delete its opaque id
|
|
//
|
|
|
|
if (HTTP_IS_NULL_ID(&pRequest->RequestId) == FALSE)
|
|
{
|
|
UlFreeRequestId(pRequest);
|
|
|
|
HTTP_SET_NULL_ID(&pRequest->RequestId);
|
|
|
|
//
|
|
// it is still referenced by this connection
|
|
//
|
|
|
|
ASSERT(pRequest->RefCount > 1);
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(pRequest);
|
|
}
|
|
|
|
//
|
|
// deref it
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(pRequest);
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
If we need to keep the connection around (after disconnect/reset) to be
|
|
able to get the logging data from worker process, we put the connection
|
|
in zombie list until last sendresponse with the logging data arrives or
|
|
until timeout code decides on purging this connection completely.
|
|
|
|
Http Connection lock must be acquired exclusive before calling this
|
|
function.
|
|
|
|
Arguments:
|
|
|
|
pHttpConnection - the http connection to be inserted to the zombie list.
|
|
|
|
Return Value:
|
|
|
|
STATUS_INVALID_DEVICE_STATE - If the zombie list length is reached the
|
|
allowed value.
|
|
|
|
STATUS_SUCCESS - If the connection has been inserted to the zombie list
|
|
and marked as zombie.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlpZombifyHttpConnection(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection
|
|
)
|
|
{
|
|
PUL_INTERNAL_REQUEST pRequest = pHttpConnection->pRequest;
|
|
PLOCKED_LIST_HEAD pListHead = &g_ZombieConnectionList.LockList;
|
|
KIRQL OldIrql;
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(UlDbgPushLockOwnedExclusive(&pHttpConnection->PushLock));
|
|
|
|
//
|
|
// Try to add the new zombie connection by paying attention to the
|
|
// zombie list limit restriction.
|
|
//
|
|
|
|
UlAcquireSpinLock(&pListHead->SpinLock, &OldIrql);
|
|
|
|
ASSERT(NULL == pHttpConnection->ZombieListEntry.Flink);
|
|
|
|
if (HTTP_LIMIT_INFINITE != g_UlMaxZombieHttpConnectionCount &&
|
|
(pListHead->Count + 1) >= g_UlMaxZombieHttpConnectionCount)
|
|
{
|
|
//
|
|
// We are already at the maximum possible zombie list size.
|
|
// Refuse the connection and let it get destroyed.
|
|
//
|
|
|
|
#ifdef ENABLE_HTTP_CONN_STATS
|
|
InterlockedIncrement(
|
|
(PLONG) &g_ZombieConnectionList.TotalZombieRefusal
|
|
);
|
|
#endif
|
|
|
|
UlReleaseSpinLock(&pListHead->SpinLock, OldIrql);
|
|
return STATUS_INVALID_DEVICE_STATE;
|
|
}
|
|
|
|
//
|
|
// Double-check if the AppPool has been detached inside the lock for
|
|
// g_ZombieConnectionList. Because we purge all zombie connections
|
|
// related to the process after it is detached, we guarantee we never
|
|
// have a dangling http connection in the zombie list when a process
|
|
// is detached. Also check if the listening endpoint has been closed.
|
|
//
|
|
|
|
if (!UlCheckAppPoolState(pRequest) ||
|
|
!UlCheckListeningEndpointState(pHttpConnection->pConnection))
|
|
{
|
|
#ifdef ENABLE_HTTP_CONN_STATS
|
|
InterlockedIncrement(
|
|
(PLONG) &g_ZombieConnectionList.TotalZombieRefusal
|
|
);
|
|
#endif
|
|
|
|
UlReleaseSpinLock(&pListHead->SpinLock, OldIrql);
|
|
return STATUS_INVALID_DEVICE_STATE;
|
|
}
|
|
|
|
pListHead->Count += 1;
|
|
InsertTailList(
|
|
&pListHead->ListHead,
|
|
&pHttpConnection->ZombieListEntry
|
|
);
|
|
|
|
//
|
|
// Remember the process that the connection was delivered so we can
|
|
// force-purge later when the process is detached. This needs to be
|
|
// done inside the lock to synchronize with UlPurgeZombieConnections.
|
|
//
|
|
|
|
ASSERT(IS_VALID_AP_PROCESS(pRequest->AppPool.pProcess));
|
|
pHttpConnection->pAppPoolProcess = pRequest->AppPool.pProcess;
|
|
|
|
UlReleaseSpinLock(&pListHead->SpinLock, OldIrql);
|
|
|
|
#ifdef ENABLE_HTTP_CONN_STATS
|
|
{
|
|
LONG ZombieCount;
|
|
|
|
ZombieCount = (LONG) g_ZombieConnectionList.LockList.Count;
|
|
if (ZombieCount > (LONG) g_ZombieConnectionList.MaxZombieCount)
|
|
{
|
|
InterlockedExchange(
|
|
(PLONG) &g_ZombieConnectionList.MaxZombieCount,
|
|
ZombieCount
|
|
);
|
|
}
|
|
|
|
InterlockedIncrement((PLONG) &g_ZombieConnectionList.TotalZombieCount);
|
|
}
|
|
#endif
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"Http!UlZombifyHttpRequest: httpconn=%p & request=%p \n",
|
|
pHttpConnection, pRequest
|
|
));
|
|
|
|
//
|
|
// If we got far enough to deliver the request then unlink it from
|
|
// the app pool. So that queued Irps release their references on
|
|
// the request.
|
|
//
|
|
|
|
if (pRequest->ConfigInfo.pAppPool)
|
|
{
|
|
UlUnlinkRequestFromProcess(
|
|
pRequest->ConfigInfo.pAppPool,
|
|
pRequest
|
|
);
|
|
}
|
|
|
|
//
|
|
// Cancel (Receive) I/O
|
|
//
|
|
|
|
UlCancelRequestIo(pRequest);
|
|
|
|
//
|
|
// Keep its opaque id while its siting on the zombie list. But
|
|
// owner is now the zombie list rather than the http connection.
|
|
//
|
|
|
|
ASSERT(pRequest->RefCount > 1);
|
|
|
|
//
|
|
// Mark that now we are in the zombie list.
|
|
//
|
|
|
|
InterlockedExchange((PLONG) &pHttpConnection->Zombified, 1);
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Removes the http connection from the zombie list.
|
|
|
|
Caller should hold the zombie list spinlock prior to calling this
|
|
function.
|
|
|
|
Arguments:
|
|
|
|
pHttpConnection - the http connection to be removed frm zombie list.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpRemoveZombieHttpConnection(
|
|
IN PUL_HTTP_CONNECTION pHttpConn
|
|
)
|
|
{
|
|
LONG NewCount;
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConn));
|
|
|
|
ASSERT(UlDbgSpinLockOwned(
|
|
&g_ZombieConnectionList.LockList.SpinLock));
|
|
|
|
//
|
|
// This connection should be in the zombie list.
|
|
//
|
|
|
|
ASSERT(pHttpConn->Zombified == TRUE);
|
|
ASSERT(pHttpConn->ZombieListEntry.Flink != NULL);
|
|
|
|
//
|
|
// Remove this from the zombie list of http connections.
|
|
//
|
|
|
|
RemoveEntryList(&pHttpConn->ZombieListEntry);
|
|
pHttpConn->ZombieListEntry.Flink = NULL;
|
|
|
|
NewCount = InterlockedDecrement(
|
|
(PLONG) &g_ZombieConnectionList.LockList.Count);
|
|
ASSERT(NewCount >= 0);
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Once the due SendResponse happens or timeout happens (whichever the
|
|
earliest) then we remove this connection from zombie list and cleanup
|
|
it and it's request as well.
|
|
|
|
You should hold the http connection lock exclusive before calling
|
|
this function.
|
|
|
|
You should also hold the zombie list lock prior to calling this function.
|
|
|
|
Arguments:
|
|
|
|
pHttpConnection - the http connection to be removed frm zombie list.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpCleanupZombieHttpConnection(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection
|
|
)
|
|
{
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
|
|
//
|
|
// Both connection and request should be in the good shape.
|
|
//
|
|
|
|
pRequest = pHttpConnection->pRequest;
|
|
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConnection));
|
|
ASSERT(UlDbgPushLockOwnedExclusive(&pHttpConnection->PushLock));
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"Http!UlCleanupZombieHttpConnection: httpconn=%p & request=%p \n",
|
|
pHttpConnection, pRequest
|
|
));
|
|
|
|
//
|
|
// Release Request's opaque id and it's refcount.
|
|
//
|
|
|
|
if (!HTTP_IS_NULL_ID(&pRequest->RequestId))
|
|
{
|
|
UlFreeRequestId(pRequest);
|
|
|
|
HTTP_SET_NULL_ID(&pRequest->RequestId);
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(pRequest);
|
|
}
|
|
|
|
//
|
|
// Release httpconn's refcount on request.
|
|
//
|
|
|
|
UL_DEREFERENCE_INTERNAL_REQUEST(pRequest);
|
|
pHttpConnection->pRequest = NULL;
|
|
|
|
//
|
|
// Release httpconn's opaque id and its refcount.
|
|
//
|
|
|
|
if (!HTTP_IS_NULL_ID(&pHttpConnection->ConnectionId))
|
|
{
|
|
UlFreeOpaqueId(
|
|
pHttpConnection->ConnectionId,
|
|
UlOpaqueIdTypeHttpConnection
|
|
);
|
|
|
|
HTTP_SET_NULL_ID(&pHttpConnection->ConnectionId);
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConnection);
|
|
}
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
DPC routine get called every 30 seconds. (Default)
|
|
|
|
Arguments:
|
|
|
|
Ignored
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlZombieTimerDpcRoutine(
|
|
PKDPC Dpc,
|
|
PVOID DeferredContext,
|
|
PVOID SystemArgument1,
|
|
PVOID SystemArgument2
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Dpc);
|
|
UNREFERENCED_PARAMETER(DeferredContext);
|
|
UNREFERENCED_PARAMETER(SystemArgument1);
|
|
UNREFERENCED_PARAMETER(SystemArgument2);
|
|
|
|
//
|
|
// Queue a worker for terminating the timed out zombie entries.
|
|
// Worker can acquire the individual http connection eresources.
|
|
// But do not queue a worker if there's already one running.
|
|
//
|
|
|
|
UlAcquireSpinLockAtDpcLevel(&g_ZombieConnectionList.TimerSpinLock);
|
|
|
|
if (g_ZombieConnectionList.TimerInitialized == TRUE)
|
|
{
|
|
if (FALSE == InterlockedExchange(
|
|
&g_ZombieConnectionList.TimerRunning, TRUE))
|
|
{
|
|
|
|
UL_QUEUE_WORK_ITEM(
|
|
&g_ZombieConnectionList.WorkItem,
|
|
&UlpZombieTimerWorker
|
|
);
|
|
}
|
|
}
|
|
|
|
UlReleaseSpinLockFromDpcLevel(&g_ZombieConnectionList.TimerSpinLock);
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Purge the zombie connections according to pPurgeRoutine or
|
|
pHttpConn->ZombieExpired.
|
|
|
|
Arguments:
|
|
|
|
pPurgeRoutine - purge routine to decide which connection to be purged.
|
|
pPurgeContext - context passed to the purge routine.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlPurgeZombieConnections(
|
|
IN PUL_PURGE_ROUTINE pPurgeRoutine,
|
|
IN PVOID pPurgeContext
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY pLink;
|
|
PUL_HTTP_CONNECTION pHttpConn;
|
|
LIST_ENTRY TempZombieList;
|
|
PLOCKED_LIST_HEAD pList;
|
|
BOOLEAN ForceExpire;
|
|
|
|
//
|
|
// Init temp zombie list
|
|
//
|
|
|
|
InitializeListHead(&TempZombieList);
|
|
|
|
#ifdef ENABLE_HTTP_CONN_STATS
|
|
UlTraceVerbose(HTTP_IO,
|
|
("Http!UlPurgeZombieConnections, Stats:\n"
|
|
"\tpPurgeRoutine = %p\n"
|
|
"\tpPurgeContext = %p\n"
|
|
"\tCurrentZombieCount = %lu\n"
|
|
"\tMaxZombieCount = %lu\n"
|
|
"\tTotalConnections = %lu\n"
|
|
"\tTotalZombieCount = %lu\n"
|
|
"\tTotalZombieRefusal = %lu\n",
|
|
pPurgeRoutine,
|
|
pPurgeContext,
|
|
g_ZombieConnectionList.LockList.Count,
|
|
g_ZombieConnectionList.MaxZombieCount,
|
|
g_ZombieConnectionList.TotalCount,
|
|
g_ZombieConnectionList.TotalZombieCount,
|
|
g_ZombieConnectionList.TotalZombieRefusal
|
|
));
|
|
#endif
|
|
|
|
pList = &g_ZombieConnectionList.LockList;
|
|
|
|
UlAcquireSpinLock(&pList->SpinLock, &OldIrql);
|
|
|
|
pLink = pList->ListHead.Flink;
|
|
while (pLink != &pList->ListHead)
|
|
{
|
|
pHttpConn = CONTAINING_RECORD(
|
|
pLink,
|
|
UL_HTTP_CONNECTION,
|
|
ZombieListEntry
|
|
);
|
|
|
|
pLink = pLink->Flink;
|
|
ForceExpire = FALSE;
|
|
|
|
if (pPurgeRoutine)
|
|
{
|
|
ForceExpire = (*pPurgeRoutine)(pHttpConn, pPurgeContext);
|
|
|
|
if (!ForceExpire)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
if (pHttpConn->ZombieExpired)
|
|
{
|
|
ForceExpire = TRUE;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// It will go away next time we wake up.
|
|
//
|
|
|
|
pHttpConn->ZombieExpired = TRUE;
|
|
}
|
|
|
|
if (ForceExpire)
|
|
{
|
|
//
|
|
// Guard against multiple cleanups by looking at the below flag.
|
|
// Final send may already be on the run and it will do the
|
|
// cleanup when it's done.
|
|
//
|
|
|
|
if (0 == InterlockedCompareExchange(
|
|
(PLONG) &pHttpConn->CleanedUp,
|
|
1,
|
|
0
|
|
))
|
|
{
|
|
//
|
|
// Timed out already or we need to purge by force. Move to
|
|
// the temp list and cleanup outside of the spinlock.
|
|
//
|
|
|
|
UlpRemoveZombieHttpConnection(pHttpConn);
|
|
|
|
InsertTailList(&TempZombieList, &pHttpConn->ZombieListEntry);
|
|
}
|
|
}
|
|
}
|
|
|
|
UlReleaseSpinLock(&pList->SpinLock, OldIrql);
|
|
|
|
//
|
|
// Now cleanup the temp list.
|
|
//
|
|
|
|
pLink = TempZombieList.Flink;
|
|
while (pLink != &TempZombieList)
|
|
{
|
|
pHttpConn = CONTAINING_RECORD(
|
|
pLink,
|
|
UL_HTTP_CONNECTION,
|
|
ZombieListEntry
|
|
);
|
|
|
|
pLink = pLink->Flink;
|
|
|
|
UlAcquirePushLockExclusive(&pHttpConn->PushLock);
|
|
|
|
//
|
|
// Log the zombified connection before drop.
|
|
//
|
|
|
|
UlErrorLog(pHttpConn,
|
|
NULL,
|
|
ERROR_LOG_INFO_FOR_ZOMBIE_DROP,
|
|
ERROR_LOG_INFO_FOR_ZOMBIE_DROP_SIZE,
|
|
TRUE
|
|
);
|
|
|
|
UlpCleanupZombieHttpConnection(pHttpConn);
|
|
|
|
pHttpConn->ZombieListEntry.Flink = NULL;
|
|
|
|
UlReleasePushLockExclusive(&pHttpConn->PushLock);
|
|
|
|
//
|
|
// Release the zombie list's refcount on http connection.
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Timer function walks through the zombie list and decides on terminating
|
|
the old zombie connections once and for all.
|
|
|
|
Arguments:
|
|
|
|
WorkItem - Ignored.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpZombieTimerWorker(
|
|
IN PUL_WORK_ITEM pWorkItem
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(pWorkItem);
|
|
|
|
ASSERT(g_ZombieConnectionList.TimerRunning == TRUE);
|
|
|
|
//
|
|
// Purge all zombie connections that have expired.
|
|
//
|
|
|
|
UlPurgeZombieConnections(NULL, NULL);
|
|
|
|
//
|
|
// Finally allow other instances of the timer to run.
|
|
//
|
|
|
|
InterlockedExchange(&g_ZombieConnectionList.TimerRunning, FALSE);
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Purge the zombie list when we are shutting down.
|
|
|
|
Arguments:
|
|
|
|
Ignored
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpTerminateZombieList(
|
|
VOID
|
|
)
|
|
{
|
|
KIRQL OldIrql;
|
|
PLIST_ENTRY pLink;
|
|
PUL_HTTP_CONNECTION pHttpConn;
|
|
LIST_ENTRY TempZombieList;
|
|
PLOCKED_LIST_HEAD pList;
|
|
|
|
//
|
|
// Init temp zombie list
|
|
//
|
|
|
|
InitializeListHead(&TempZombieList);
|
|
|
|
pList = &g_ZombieConnectionList.LockList;
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"Http!UlpTerminateZombieList: Terminating for ZombieCount =%d\n",
|
|
pList->Count
|
|
));
|
|
|
|
UlAcquireSpinLock(&pList->SpinLock, &OldIrql);
|
|
|
|
//
|
|
// Move the entire list to the temp zombie list and cleanup
|
|
// outside of the spinlock.
|
|
//
|
|
|
|
pLink = pList->ListHead.Flink;
|
|
while (pLink != &pList->ListHead)
|
|
{
|
|
|
|
PLIST_ENTRY pNextLink = pLink->Flink;
|
|
|
|
pHttpConn = CONTAINING_RECORD(
|
|
pLink,
|
|
UL_HTTP_CONNECTION,
|
|
ZombieListEntry
|
|
);
|
|
|
|
UlpRemoveZombieHttpConnection(pHttpConn);
|
|
|
|
InsertTailList(&TempZombieList, &pHttpConn->ZombieListEntry);
|
|
|
|
pLink = pNextLink;
|
|
}
|
|
|
|
UlReleaseSpinLock(&pList->SpinLock, OldIrql);
|
|
|
|
//
|
|
// Now cleanup everything.
|
|
//
|
|
|
|
pLink = TempZombieList.Flink;
|
|
while (pLink != &TempZombieList)
|
|
{
|
|
pHttpConn = CONTAINING_RECORD(
|
|
pLink,
|
|
UL_HTTP_CONNECTION,
|
|
ZombieListEntry
|
|
);
|
|
|
|
pLink = pLink->Flink;
|
|
|
|
UlAcquirePushLockExclusive(&pHttpConn->PushLock);
|
|
|
|
//
|
|
// Log the zombified connection before dropping it.
|
|
//
|
|
|
|
UlErrorLog(pHttpConn,
|
|
NULL,
|
|
ERROR_LOG_INFO_FOR_ZOMBIE_DROP,
|
|
ERROR_LOG_INFO_FOR_ZOMBIE_DROP_SIZE,
|
|
TRUE
|
|
);
|
|
|
|
UlpCleanupZombieHttpConnection(pHttpConn);
|
|
|
|
pHttpConn->ZombieListEntry.Flink = NULL;
|
|
|
|
UlReleasePushLockExclusive(&pHttpConn->PushLock);
|
|
|
|
//
|
|
// Release the zombie list's refcount on http connection.
|
|
//
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Timer for the zombie http connection list wakes up every 30 seconds and
|
|
in the max case it terminates the zombie connection no later than 60
|
|
seconds.
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlpSetZombieTimer(
|
|
VOID
|
|
)
|
|
{
|
|
LONGLONG PeriodTime100Ns;
|
|
LONG PeriodTimeMs;
|
|
LARGE_INTEGER PeriodTime;
|
|
|
|
PeriodTimeMs = DEFAULT_ZOMBIE_HTTP_CONNECTION_TIMER_PERIOD_IN_SECONDS * 1000;
|
|
PeriodTime100Ns = (LONGLONG) PeriodTimeMs * 10 * 1000;
|
|
|
|
UlTrace(HTTP_IO,("http!UlpSetZombieTimer: period of %d seconds.\n",
|
|
DEFAULT_ZOMBIE_HTTP_CONNECTION_TIMER_PERIOD_IN_SECONDS
|
|
));
|
|
|
|
PeriodTime.QuadPart = -PeriodTime100Ns; // Relative time
|
|
|
|
KeSetTimerEx(
|
|
&g_ZombieConnectionList.Timer,
|
|
PeriodTime, // In nanosec
|
|
PeriodTimeMs, // In millisec
|
|
&g_ZombieConnectionList.DpcObject
|
|
);
|
|
|
|
}
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Probes and prepares the user provided log buffers and completes the
|
|
logging for this zombie connection. After that it triggers the cleanup
|
|
of the zombie connection.
|
|
|
|
If any error happens, it cleans up the zombie connection But returns
|
|
success regardless.
|
|
|
|
You should hold the http connection lock exclusive before calling this
|
|
function.
|
|
|
|
Arguments:
|
|
|
|
pRequest - The request for the zombie connection.
|
|
pHttpConnection - Supplies the HTTP_CONNECTION to send the response on.
|
|
|
|
Return Value:
|
|
|
|
Always STATUS_CONNECTION_INVALID. Handling the zombie connection is a
|
|
best effort to write a log record for the connection. If something fails
|
|
we terminate the zombie connection and do not allow a second chance.
|
|
|
|
--***************************************************************************/
|
|
NTSTATUS
|
|
UlLogZombieConnection(
|
|
IN PUL_INTERNAL_REQUEST pRequest,
|
|
IN PUL_HTTP_CONNECTION pHttpConn,
|
|
IN PHTTP_LOG_FIELDS_DATA pCapturedUserLogData,
|
|
IN KPROCESSOR_MODE RequestorMode
|
|
)
|
|
{
|
|
PUL_LOG_DATA_BUFFER pLogData = NULL;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
Status = STATUS_SUCCESS;
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConn));
|
|
ASSERT(UL_IS_VALID_INTERNAL_REQUEST(pRequest));
|
|
|
|
ASSERT(pHttpConn->Zombified == TRUE);
|
|
ASSERT(pHttpConn->ZombieListEntry.Flink != NULL);
|
|
|
|
UlTrace(HTTP_IO, (
|
|
"Http!UlLogZombieConnection: Logging for pRequest=%p pHttpConn=%p\n",
|
|
pRequest, pHttpConn
|
|
));
|
|
|
|
//
|
|
// No user logging data, bail out and cleanup the zombie connection.
|
|
//
|
|
|
|
if (!pCapturedUserLogData)
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Probe the log data. If something fails, then cleanup
|
|
// the zombie connection.
|
|
//
|
|
|
|
__try
|
|
{
|
|
//
|
|
// The pCapturedUserLogData is already probed and captured. However
|
|
// we need to probe the individual log fields (pointers) inside the
|
|
// structure.
|
|
//
|
|
|
|
UlProbeLogData(pCapturedUserLogData, RequestorMode);
|
|
|
|
} __except(UL_EXCEPTION_FILTER())
|
|
{
|
|
Status = UL_CONVERT_EXCEPTION_CODE(GetExceptionCode());
|
|
goto cleanup;
|
|
}
|
|
|
|
//
|
|
// Now we will allocate a kernel pLogData and built and format it
|
|
// from the provided user log fields. However if logging is not
|
|
// enabled for the pRequest's cgroup then capture will simply
|
|
// return success w/o setting the pLogData. Therefore we need to
|
|
// make sure we only log if the pLogData is set.
|
|
//
|
|
|
|
Status = UlCaptureUserLogData(
|
|
pCapturedUserLogData,
|
|
pRequest,
|
|
&pLogData
|
|
);
|
|
|
|
if (NT_SUCCESS(Status) && pLogData)
|
|
{
|
|
ASSERT(IS_VALID_LOG_DATA_BUFFER(pLogData));
|
|
|
|
//
|
|
// The actual logging takes place inline.
|
|
//
|
|
|
|
LOG_SET_WIN32STATUS(pLogData, STATUS_CONNECTION_RESET);
|
|
|
|
//
|
|
// Pick the right logging type.
|
|
//
|
|
|
|
if (pLogData->Flags.Binary)
|
|
{
|
|
UlRawLogHttpHit(pLogData);
|
|
}
|
|
else
|
|
{
|
|
UlLogHttpHit(pLogData);
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
|
|
//
|
|
// Cleanup the pLogData & the zombie connection before
|
|
// completing the Ioctl.
|
|
//
|
|
|
|
if (pLogData)
|
|
{
|
|
UlDestroyLogDataBuffer(pLogData);
|
|
}
|
|
|
|
UlAcquireSpinLock(
|
|
&g_ZombieConnectionList.LockList.SpinLock, &OldIrql);
|
|
|
|
UlpRemoveZombieHttpConnection(pHttpConn);
|
|
|
|
UlReleaseSpinLock(
|
|
&g_ZombieConnectionList.LockList.SpinLock, OldIrql);
|
|
|
|
UlpCleanupZombieHttpConnection(pHttpConn);
|
|
|
|
//
|
|
// Release Zombie List's refcount on the http connection.
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pHttpConn);
|
|
|
|
return STATUS_CONNECTION_INVALID;
|
|
}
|
|
|
|
|
|
//
|
|
// Following code has been implemented for Global & Site specific connection
|
|
// limits feature. If enforced, incoming connections get refused when they
|
|
// exceed the existing limit. Control channel & config group (re)sets this
|
|
// values whereas httprcv and sendresponse updates the limits for incoming
|
|
// requests & connections. A seperate connection_count_entry structure keeps
|
|
// track of the limits per site. Global limits are tracked by the global
|
|
// variables g_MaxConnections & g_CurrentConnections same API has been used
|
|
// for both purposes.
|
|
//
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Allocates a connection count entry which will hold the site specific
|
|
connection count info. Get called by cgroup when Config group is
|
|
attempting to allocate a connection count entry.
|
|
|
|
Arguments:
|
|
|
|
pConfigGroup - To receive the newly allocated count entry
|
|
MaxConnections - The maximum allowed connections
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlCreateConnectionCountEntry(
|
|
IN OUT PUL_CONFIG_GROUP_OBJECT pConfigGroup,
|
|
IN ULONG MaxConnections
|
|
)
|
|
{
|
|
PUL_CONNECTION_COUNT_ENTRY pEntry;
|
|
|
|
// Sanity check.
|
|
|
|
PAGED_CODE();
|
|
ASSERT(IS_VALID_CONFIG_GROUP(pConfigGroup));
|
|
|
|
// Alloc new struct from Paged Pool
|
|
|
|
pEntry = UL_ALLOCATE_STRUCT(
|
|
PagedPool,
|
|
UL_CONNECTION_COUNT_ENTRY,
|
|
UL_CONNECTION_COUNT_ENTRY_POOL_TAG
|
|
);
|
|
if (!pEntry)
|
|
{
|
|
UlTrace(LIMITS,
|
|
("Http!UlCreateConnectionCountEntry: OutOfMemory pConfigGroup %p\n",
|
|
pConfigGroup
|
|
));
|
|
|
|
return STATUS_NO_MEMORY;
|
|
}
|
|
|
|
pEntry->Signature = UL_CONNECTION_COUNT_ENTRY_POOL_TAG;
|
|
pEntry->RefCount = 1;
|
|
pEntry->MaxConnections = MaxConnections;
|
|
pEntry->CurConnections = 0;
|
|
|
|
// Update cgroup pointer
|
|
|
|
ASSERT( pConfigGroup->pConnectionCountEntry == NULL );
|
|
pConfigGroup->pConnectionCountEntry = pEntry;
|
|
|
|
UlTrace(LIMITS,
|
|
("Http!UlCreateConnectionCountEntry: "
|
|
"pNewEntry %p, pConfigGroup %p, Max %d\n",
|
|
pEntry,
|
|
pConfigGroup,
|
|
MaxConnections
|
|
));
|
|
|
|
return STATUS_SUCCESS;
|
|
|
|
} // UlCreateConnectionCountEntry
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
increments the refcount for ConnectionCountEntry.
|
|
|
|
Arguments:
|
|
|
|
pEntry - the object to increment.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlReferenceConnectionCountEntry(
|
|
IN PUL_CONNECTION_COUNT_ENTRY pEntry
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( IS_VALID_CONNECTION_COUNT_ENTRY(pEntry) );
|
|
|
|
refCount = InterlockedIncrement( &pEntry->RefCount );
|
|
|
|
//
|
|
// Tracing.
|
|
//
|
|
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pConnectionCountTraceLog,
|
|
REF_ACTION_REFERENCE_CONNECTION_COUNT_ENTRY,
|
|
refCount,
|
|
pEntry,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(
|
|
REFCOUNT,
|
|
("Http!UlReferenceConnectionCountEntry pEntry=%p refcount=%d\n",
|
|
pEntry,
|
|
refCount
|
|
));
|
|
|
|
} // UlReferenceConnectionCountEntry
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
decrements the refcount. if it hits 0, destruct's ConnectionCountEntry
|
|
|
|
Arguments:
|
|
|
|
pEntry - the object to decrement.
|
|
|
|
--***************************************************************************/
|
|
VOID
|
|
UlDereferenceConnectionCountEntry(
|
|
IN PUL_CONNECTION_COUNT_ENTRY pEntry
|
|
REFERENCE_DEBUG_FORMAL_PARAMS
|
|
)
|
|
{
|
|
LONG refCount;
|
|
|
|
//
|
|
// Sanity check.
|
|
//
|
|
|
|
PAGED_CODE();
|
|
|
|
ASSERT( IS_VALID_CONNECTION_COUNT_ENTRY(pEntry) );
|
|
|
|
refCount = InterlockedDecrement( &pEntry->RefCount );
|
|
|
|
//
|
|
// Tracing.
|
|
//
|
|
WRITE_REF_TRACE_LOG(
|
|
g_pConnectionCountTraceLog,
|
|
REF_ACTION_DEREFERENCE_CONNECTION_COUNT_ENTRY,
|
|
refCount,
|
|
pEntry,
|
|
pFileName,
|
|
LineNumber
|
|
);
|
|
|
|
UlTrace(
|
|
REFCOUNT,
|
|
("Http!UlDereferenceConnectionCountEntry pEntry=%p refcount=%d\n",
|
|
pEntry,
|
|
refCount
|
|
));
|
|
|
|
//
|
|
// Cleanup the memory and do few checks !
|
|
//
|
|
|
|
if ( refCount == 0 )
|
|
{
|
|
// Make sure no connection on the site
|
|
ASSERT( 0 == pEntry->CurConnections );
|
|
|
|
UlTrace(
|
|
LIMITS,
|
|
("Http!UlDereferenceConnectionCountEntry: Removing pEntry=%p\n",
|
|
pEntry
|
|
));
|
|
|
|
// Release memory
|
|
UL_FREE_POOL_WITH_SIG(pEntry,UL_CONNECTION_COUNT_ENTRY_POOL_TAG);
|
|
}
|
|
|
|
} // UlDereferenceConnectionCountEntry
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This
|
|
function set the maximum limit. Maximum allowed number of connections
|
|
at any time by the active control channel.
|
|
|
|
Arguments:
|
|
|
|
MaxConnections - Maximum allowed number of connections
|
|
|
|
Return Value:
|
|
|
|
Old Max Connection count
|
|
|
|
--***************************************************************************/
|
|
|
|
ULONG
|
|
UlSetMaxConnections(
|
|
IN OUT PULONG pCurMaxConnection,
|
|
IN ULONG NewMaxConnection
|
|
)
|
|
{
|
|
ULONG OldMaxConnection;
|
|
|
|
UlTrace(LIMITS,
|
|
("Http!UlSetMaxConnections pCurMaxConnection=%p NewMaxConnection=%d\n",
|
|
pCurMaxConnection,
|
|
NewMaxConnection
|
|
));
|
|
|
|
//
|
|
// By setting this we are not forcing the existing connections to
|
|
// termination but this number will be effective for all newcoming
|
|
// connections, as soon as the atomic operation completed.
|
|
//
|
|
|
|
OldMaxConnection = (ULONG) InterlockedExchange((LONG *) pCurMaxConnection,
|
|
(LONG) NewMaxConnection
|
|
);
|
|
return OldMaxConnection;
|
|
|
|
} // UlSetMaxConnections
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Control channel uses this function to set or reset the global connection
|
|
limits.
|
|
|
|
--***************************************************************************/
|
|
|
|
ULONG
|
|
UlGetGlobalConnectionLimit(
|
|
VOID
|
|
)
|
|
{
|
|
return g_MaxConnections;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Control channel uses this function to initialize the global connection
|
|
limits. Assuming the existince of one and only one active control channel
|
|
this globals get set only once during init. But could be set again later
|
|
because of a reconfig.
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlInitGlobalConnectionLimits(
|
|
VOID
|
|
)
|
|
{
|
|
ULONG PerConnectionEstimate;
|
|
SIZE_T HttpMaxConnections;
|
|
SIZE_T AvailablePages;
|
|
NTSTATUS Status = STATUS_SUCCESS;
|
|
|
|
ASSERT(!g_InitGlobalConnections);
|
|
|
|
if (!g_InitGlobalConnections)
|
|
{
|
|
g_CurrentConnections = 0;
|
|
|
|
//
|
|
// Set global max connection limit
|
|
//
|
|
|
|
// Heuristic Calculation to estmiate the size
|
|
// needed per connection
|
|
|
|
// First, there's the UL_CONNECTION
|
|
PerConnectionEstimate = sizeof(UL_CONNECTION);
|
|
|
|
// Second, assume one UL_INTERNAL_REQUEST+Full Tracker per Connection
|
|
PerConnectionEstimate += (sizeof(UL_INTERNAL_REQUEST) +
|
|
MAX(g_UlFullTrackerSize, g_UlChunkTrackerSize) +
|
|
g_UlMaxInternalUrlLength +
|
|
DEFAULT_MAX_ROUTING_TOKEN_LENGTH +
|
|
sizeof(WCHAR) // for the null on the InternalUrl
|
|
);
|
|
|
|
// Third, add one UL_REQUEST_BUFFER
|
|
PerConnectionEstimate += (sizeof(UL_REQUEST_BUFFER) +
|
|
DEFAULT_MAX_REQUEST_BUFFER_SIZE);
|
|
|
|
// Fourth, add the size of a response
|
|
PerConnectionEstimate += g_UlResponseBufferSize;
|
|
|
|
// Finally, round up to the next page size & convert to pages
|
|
PerConnectionEstimate =
|
|
(ULONG)ROUND_TO_PAGES(PerConnectionEstimate);
|
|
|
|
PerConnectionEstimate /= PAGE_SIZE;
|
|
|
|
ASSERT(0 != PerConnectionEstimate);
|
|
|
|
AvailablePages = BYTES_TO_PAGES(g_UlTotalNonPagedPoolBytes);
|
|
|
|
// Assume only 50% of NPP available to HTTP.SYS
|
|
AvailablePages /= 2;
|
|
|
|
// Assume 20% set aside for HttpDataChunkFromFileHandle send buffers.
|
|
// (this works out to roughly 3% to 5% of the connections)
|
|
AvailablePages -= (AvailablePages/5);
|
|
|
|
if ( AvailablePages < PerConnectionEstimate )
|
|
{
|
|
// this machine has so little NPP, that it can't even support one
|
|
// measely little connection!
|
|
AvailablePages = PerConnectionEstimate;
|
|
}
|
|
|
|
// Okay, how many fit?
|
|
HttpMaxConnections = (AvailablePages / PerConnectionEstimate);
|
|
|
|
ASSERT(0 != HttpMaxConnections);
|
|
|
|
// And set it (if it wasn't overridden in UlpReadRegistry)
|
|
if (HTTP_LIMIT_INFINITE == g_MaxConnections)
|
|
{
|
|
g_MaxConnections = (ULONG)MIN(HttpMaxConnections, LONG_MAX);
|
|
}
|
|
else
|
|
{
|
|
UlTrace(LIMITS,
|
|
("Http!UlInitGlobalConnectionLimits: User has overridden "
|
|
"g_MaxConnections: %d\n",
|
|
g_MaxConnections
|
|
));
|
|
}
|
|
|
|
g_InitGlobalConnections = TRUE;
|
|
|
|
UlTrace2Either(LIMITS, TDI_STATS,
|
|
("Http!UlInitGlobalConnectionLimits: Init g_MaxConnections %d,"
|
|
"g_CurrentConnections %d, HttpMaxConnections %d\n",
|
|
g_MaxConnections,
|
|
g_CurrentConnections,
|
|
HttpMaxConnections
|
|
));
|
|
|
|
if (DEFAULT_MAX_REQUESTS_QUEUED == g_UlMaxRequestsQueued)
|
|
{
|
|
//
|
|
// If it's been left at the default, set it equal to
|
|
// the number of Max Connections; this assumes a 1:1 ratio
|
|
// of requests per connection.
|
|
//
|
|
|
|
g_UlMaxRequestsQueued = (ULONG) g_MaxConnections;
|
|
}
|
|
|
|
UlTrace2Either(LIMITS, TDI_STATS,
|
|
("Http!UlInitGlobalConnectionLimits: Init g_UlMaxRequestsQueued %d\n",
|
|
g_UlMaxRequestsQueued
|
|
));
|
|
}
|
|
|
|
return Status;
|
|
|
|
} // UlInitGlobalConnectionLimits
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Wrapper around the Accept Connection for global connections
|
|
|
|
--***************************************************************************/
|
|
BOOLEAN
|
|
UlAcceptGlobalConnection(
|
|
VOID
|
|
)
|
|
{
|
|
return UlAcceptConnection( &g_MaxConnections, &g_CurrentConnections );
|
|
|
|
} // UlAcceptGlobalConnection
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
This function checks if we are allowed to accept the incoming connection
|
|
based on the number enforced by the control channel.
|
|
|
|
Return value:
|
|
|
|
Decision for the newcoming connection either ACCEPT or REJECT as boolean
|
|
|
|
--***************************************************************************/
|
|
|
|
BOOLEAN
|
|
UlAcceptConnection(
|
|
IN PULONG pMaxConnection,
|
|
IN OUT PULONG pCurConnection
|
|
)
|
|
{
|
|
ULONG LocalCur;
|
|
ULONG LocalCurPlusOne;
|
|
ULONG LocalMax;
|
|
|
|
do
|
|
{
|
|
//
|
|
// Capture the Max & Cur. Do the comparison. If in the limit
|
|
// attempt to increment the connection count by ensuring nobody
|
|
// else did it before us.
|
|
//
|
|
|
|
LocalMax = *((volatile ULONG *) pMaxConnection);
|
|
LocalCur = *((volatile ULONG *) pCurConnection);
|
|
|
|
//
|
|
// Its greater than or equal because Max may get updated to
|
|
// a smaller number on-the-fly and we end up having current
|
|
// connections greater than the maximum allowed.
|
|
// NOTE: HTTP_LIMIT_INFINITE has been picked as (ULONG)-1 so
|
|
// following comparison won't reject for the infinite case.
|
|
//
|
|
|
|
if ( LocalCur >= LocalMax )
|
|
{
|
|
//
|
|
// We are already at the limit refuse it.
|
|
//
|
|
|
|
UlTrace(LIMITS,
|
|
("Http!UlAcceptConnection REFUSE pCurConnection=%p[%d]"
|
|
"pMaxConnection=%p[%d]\n",
|
|
pCurConnection, LocalCur,
|
|
pMaxConnection, LocalMax
|
|
));
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Either the limit was infinite or conn count was not exceeding
|
|
// the limit. Lets attempt to increment the count and accept the
|
|
// connection in the while statement.
|
|
//
|
|
|
|
LocalCurPlusOne = LocalCur + 1;
|
|
|
|
}
|
|
while ( LocalCur != (ULONG) InterlockedCompareExchange(
|
|
(LONG *) pCurConnection,
|
|
(LONG) LocalCurPlusOne,
|
|
(LONG) LocalCur
|
|
) );
|
|
|
|
//
|
|
// Successfully incremented the counter. Let it go with success.
|
|
//
|
|
|
|
UlTrace(LIMITS,
|
|
("Http!UlAcceptConnection ACCEPT pCurConnection=%p[%d]"
|
|
"pMaxConnection=%p[%d]\n",
|
|
pCurConnection, LocalCur,
|
|
pMaxConnection, LocalMax
|
|
));
|
|
|
|
return TRUE;
|
|
|
|
} // UlAcceptConnection
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Everytime a disconnection happens we will decrement the count here.
|
|
|
|
Return Value:
|
|
|
|
The newly decremented value
|
|
|
|
--***************************************************************************/
|
|
|
|
LONG
|
|
UlDecrementConnections(
|
|
IN OUT PULONG pCurConnection
|
|
)
|
|
{
|
|
LONG NewConnectionCount;
|
|
|
|
NewConnectionCount = InterlockedDecrement( (LONG *) pCurConnection );
|
|
|
|
ASSERT( NewConnectionCount >= 0 );
|
|
|
|
return NewConnectionCount;
|
|
|
|
} // UlDecrementConnections
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
For cache & non-cache hits this function get called. Connection resource
|
|
has assumed to be acquired at this time. The function decide to accept or
|
|
reject the request by looking at the corresponding count entries.
|
|
|
|
Arguments:
|
|
|
|
pConnection - For getting the previous site's connection count entry
|
|
pConfigInfo - Holds a pointer to newly received request's site's
|
|
connection count entry.
|
|
Return Value:
|
|
|
|
Shows reject or accept
|
|
|
|
--***************************************************************************/
|
|
|
|
BOOLEAN
|
|
UlCheckSiteConnectionLimit(
|
|
IN OUT PUL_HTTP_CONNECTION pConnection,
|
|
IN OUT PUL_URL_CONFIG_GROUP_INFO pConfigInfo
|
|
)
|
|
{
|
|
BOOLEAN Accept;
|
|
PUL_CONNECTION_COUNT_ENTRY pConEntry;
|
|
PUL_CONNECTION_COUNT_ENTRY pCIEntry;
|
|
|
|
if (pConfigInfo->pMaxConnections == NULL || pConfigInfo->pConnectionCountEntry == NULL)
|
|
{
|
|
//
|
|
// No connection count entry for the new request perhaps WAS never
|
|
// set this before otherwise its a problem.
|
|
//
|
|
|
|
UlTrace(LIMITS,
|
|
("Http!UlCheckSiteConnectionLimit: NO LIMIT pConnection=%p pConfigInfo=%p\n",
|
|
pConnection,
|
|
pConfigInfo
|
|
));
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
ASSERT(IS_VALID_CONNECTION_COUNT_ENTRY(pConfigInfo->pConnectionCountEntry));
|
|
|
|
pCIEntry = pConfigInfo->pConnectionCountEntry;
|
|
pConEntry = pConnection->pConnectionCountEntry;
|
|
Accept = FALSE;
|
|
|
|
//
|
|
// Make a check on the connection limit of the site. Refuse the request
|
|
// if the limit is exceded.
|
|
//
|
|
if (pConEntry)
|
|
{
|
|
ASSERT(IS_VALID_CONNECTION_COUNT_ENTRY(pConEntry));
|
|
|
|
//
|
|
// For consecutive requests we decrement the previous site's connection count
|
|
// before proceeding to the decision on the newcoming request,if the two sides
|
|
// are not same.That means we assume this connection on site x until (if ever)
|
|
// a request changes this to site y. Naturally until the first request arrives
|
|
// and successfully parsed, the connection does not count to any specific site
|
|
//
|
|
|
|
if (pConEntry != pCIEntry)
|
|
{
|
|
UlDecrementConnections(&pConEntry->CurConnections);
|
|
DEREFERENCE_CONNECTION_COUNT_ENTRY(pConEntry);
|
|
|
|
//
|
|
// We do not increment the connection here yet, since the AcceptConnection
|
|
// will decide and do that.
|
|
//
|
|
|
|
REFERENCE_CONNECTION_COUNT_ENTRY(pCIEntry);
|
|
pConnection->pConnectionCountEntry = pCIEntry;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// There was an old entry, that means this connection already got through.
|
|
// And the entry hasn't been changed with this new request.
|
|
// No need to check again, our design is not forcing existing connections
|
|
// to disconnect.
|
|
//
|
|
|
|
return TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
REFERENCE_CONNECTION_COUNT_ENTRY(pCIEntry);
|
|
pConnection->pConnectionCountEntry = pCIEntry;
|
|
}
|
|
|
|
Accept = UlAcceptConnection(
|
|
&pConnection->pConnectionCountEntry->MaxConnections,
|
|
&pConnection->pConnectionCountEntry->CurConnections
|
|
);
|
|
|
|
if (Accept == FALSE)
|
|
{
|
|
// We are going to refuse. Let our ref & refcount go away
|
|
// on the connection entry to prevent the possible incorrect
|
|
// decrement in the UlConnectionDestroyedWorker. If refused
|
|
// current connection hasn't been incremented in the accept
|
|
// connection. Perf counters also depends on the fact that
|
|
// pConnectionCountEntry will be Null when con got refused
|
|
|
|
DEREFERENCE_CONNECTION_COUNT_ENTRY(pConnection->pConnectionCountEntry);
|
|
pConnection->pConnectionCountEntry = NULL;
|
|
}
|
|
|
|
return Accept;
|
|
|
|
} // UlCheckSiteConnectionLimit
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Allocate a request opaque ID.
|
|
|
|
Return Value:
|
|
|
|
NT_SUCCESS
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlAllocateRequestId(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
KIRQL OldIrql;
|
|
|
|
PAGED_CODE();
|
|
|
|
pConnection = pRequest->pHttpConn;
|
|
|
|
UlAcquireSpinLock(&pConnection->RequestIdSpinLock, &OldIrql);
|
|
pConnection->pRequestIdContext = pRequest;
|
|
UlReleaseSpinLock(&pConnection->RequestIdSpinLock, OldIrql);
|
|
|
|
pRequest->RequestId = pConnection->ConnectionId;
|
|
|
|
UL_REFERENCE_INTERNAL_REQUEST(pRequest);
|
|
pRequest->RequestIdCopy = pRequest->RequestId;
|
|
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Free a request opaque ID.
|
|
|
|
Return Value:
|
|
|
|
VOID
|
|
|
|
--***************************************************************************/
|
|
|
|
VOID
|
|
UlFreeRequestId(
|
|
IN PUL_INTERNAL_REQUEST pRequest
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
KIRQL OldIrql;
|
|
|
|
pConnection = pRequest->pHttpConn;
|
|
|
|
UlAcquireSpinLock(&pConnection->RequestIdSpinLock, &OldIrql);
|
|
pConnection->pRequestIdContext = NULL;
|
|
UlReleaseSpinLock(&pConnection->RequestIdSpinLock, OldIrql);
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Get a request from the connection opaque ID.
|
|
|
|
Return Value:
|
|
|
|
PUL_INTERNAL_REQUEST
|
|
|
|
--***************************************************************************/
|
|
|
|
PUL_INTERNAL_REQUEST
|
|
UlGetRequestFromId(
|
|
IN HTTP_REQUEST_ID RequestId,
|
|
IN PUL_APP_POOL_PROCESS pProcess
|
|
)
|
|
{
|
|
PUL_HTTP_CONNECTION pConnection;
|
|
PUL_INTERNAL_REQUEST pRequest;
|
|
KIRQL OldIrql;
|
|
|
|
pConnection = UlGetConnectionFromId(RequestId);
|
|
|
|
if (pConnection != NULL)
|
|
{
|
|
UlAcquireSpinLock(&pConnection->RequestIdSpinLock, &OldIrql);
|
|
|
|
pRequest = pConnection->pRequestIdContext;
|
|
|
|
if (pRequest != NULL)
|
|
{
|
|
//
|
|
// Check to make sure the user that is asking for the request
|
|
// comes from the same process we have delivered the request.
|
|
//
|
|
|
|
if (pRequest->AppPool.pProcess == pProcess)
|
|
{
|
|
UL_REFERENCE_INTERNAL_REQUEST(pRequest);
|
|
}
|
|
else
|
|
{
|
|
pRequest = NULL;
|
|
}
|
|
}
|
|
|
|
UlReleaseSpinLock(&pConnection->RequestIdSpinLock, OldIrql);
|
|
|
|
//
|
|
// Release the reference added by UlGetConnectionFromId.
|
|
//
|
|
|
|
UL_DEREFERENCE_HTTP_CONNECTION(pConnection);
|
|
|
|
return pRequest;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Check if the pHttpConnection is associated with pListeningContext.
|
|
|
|
Return value:
|
|
|
|
TRUE if match or FALSE
|
|
|
|
--***************************************************************************/
|
|
|
|
BOOLEAN
|
|
UlPurgeListeningEndpoint(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
IN PVOID pListeningContext
|
|
)
|
|
{
|
|
ASSERT(pHttpConnection->pConnection->pListeningContext);
|
|
|
|
if (pListeningContext == pHttpConnection->pConnection->pListeningContext)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Check if the pHttpConnection is associated with pProcessContext.
|
|
|
|
Return value:
|
|
|
|
TRUE if match or FALSE
|
|
|
|
--***************************************************************************/
|
|
|
|
BOOLEAN
|
|
UlPurgeAppPoolProcess(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
IN PVOID pProcessContext
|
|
)
|
|
{
|
|
ASSERT(pHttpConnection->pAppPoolProcess);
|
|
|
|
if (pProcessContext == pHttpConnection->pAppPoolProcess)
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************++
|
|
|
|
Routine Description:
|
|
|
|
Gracefully close a connection.
|
|
|
|
Return value:
|
|
|
|
None
|
|
|
|
--***************************************************************************/
|
|
|
|
NTSTATUS
|
|
UlDisconnectHttpConnection(
|
|
IN PUL_HTTP_CONNECTION pHttpConnection,
|
|
IN PUL_COMPLETION_ROUTINE pCompletionRoutine,
|
|
IN PVOID pCompletionContext
|
|
)
|
|
{
|
|
PUL_TIMEOUT_INFO_ENTRY pTimeoutInfo;
|
|
KIRQL OldIrql;
|
|
|
|
//
|
|
// Start the Idle timer (do not wait forever for FIN).
|
|
//
|
|
|
|
ASSERT(UL_IS_VALID_HTTP_CONNECTION(pHttpConnection));
|
|
|
|
pTimeoutInfo = &pHttpConnection->TimeoutInfo;
|
|
|
|
UlAcquireSpinLock(&pTimeoutInfo->Lock, &OldIrql);
|
|
|
|
if (UlIsConnectionTimerOff(pTimeoutInfo, TimerConnectionIdle))
|
|
{
|
|
UlSetConnectionTimer(pTimeoutInfo, TimerConnectionIdle);
|
|
}
|
|
|
|
UlReleaseSpinLock(&pTimeoutInfo->Lock, OldIrql);
|
|
|
|
UlEvaluateTimerState(pTimeoutInfo);
|
|
|
|
return UlCloseConnection(
|
|
pHttpConnection->pConnection,
|
|
FALSE,
|
|
pCompletionRoutine,
|
|
pCompletionContext
|
|
);
|
|
}
|
|
|