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.
 
 
 
 
 
 

4737 lines
142 KiB

/*++
Copyright (c) 2000-2002 Microsoft Corporation
Module Name:
clientconn.c
Abstract:
Contains the code for the client-side HTTP connection stuff.
Author:
Henry Sanders (henrysa) 14-Aug-2000
Rajesh Sundaram (rajeshsu) 01-Oct-2000
Revision History:
--*/
#include "precomp.h"
#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, UcInitializeClientConnections )
#pragma alloc_text( PAGE, UcTerminateClientConnections )
#pragma alloc_text( PAGE, UcpTerminateClientConnectionsHelper )
#pragma alloc_text( PAGE, UcOpenClientConnection )
#pragma alloc_text( PAGEUC, UcSendRequestOnConnection )
#pragma alloc_text( PAGEUC, UcCancelSentRequest )
#pragma alloc_text( PAGEUC, UcRestartMdlSend )
#pragma alloc_text( PAGEUC, UcpRestartEntityMdlSend )
#pragma alloc_text( PAGEUC, UcIssueRequests )
#pragma alloc_text( PAGEUC, UcIssueEntities )
#pragma alloc_text( PAGEUC, UcpCleanupConnection )
#pragma alloc_text( PAGEUC, UcRestartClientConnect )
#pragma alloc_text( PAGEUC, UcpCancelPendingRequest )
#pragma alloc_text( PAGEUC, UcSendEntityBody )
#pragma alloc_text( PAGEUC, UcReferenceClientConnection )
#pragma alloc_text( PAGEUC, UcDereferenceClientConnection )
#pragma alloc_text( PAGEUC, UcpConnectionStateMachineWorker )
#pragma alloc_text( PAGEUC, UcKickOffConnectionStateMachine )
#pragma alloc_text( PAGEUC, UcComputeHttpRawConnectionLength )
#pragma alloc_text( PAGEUC, UcGenerateHttpRawConnectionInfo )
#pragma alloc_text( PAGEUC, UcServerCertificateInstalled )
#pragma alloc_text( PAGEUC, UcConnectionStateMachine )
#pragma alloc_text( PAGEUC, UcpInitializeConnection )
#pragma alloc_text( PAGEUC, UcpOpenTdiObjects )
#pragma alloc_text( PAGEUC, UcpFreeTdiObject )
#pragma alloc_text( PAGEUC, UcpAllocateTdiObject )
#pragma alloc_text( PAGEUC, UcpPopTdiObject )
#pragma alloc_text( PAGEUC, UcpPushTdiObject )
#pragma alloc_text( PAGEUC, UcpCheckForPipelining )
#pragma alloc_text( PAGEUC, UcClearConnectionBusyFlag )
#pragma alloc_text( PAGEUC, UcAddServerCertInfoToConnection )
#pragma alloc_text( PAGEUC, UcpCompareServerCert )
#pragma alloc_text( PAGEUC, UcpFindRequestToFail )
#endif
//
// Cache of UC_CLIENT_CONNECTIONS
//
#define NUM_ADDRESS_TYPES 2
#define ADDRESS_TYPE_TO_INDEX(addrtype) ((addrtype) == TDI_ADDRESS_TYPE_IP6)
LIST_ENTRY g_ClientTdiConnectionSListHead[NUM_ADDRESS_TYPES];
#define G_CLIENT_TDI_CONNECTION_SLIST_HEAD(addrtype) \
(g_ClientTdiConnectionSListHead[ADDRESS_TYPE_TO_INDEX(addrtype)])
UL_SPIN_LOCK g_ClientConnSpinLock[NUM_ADDRESS_TYPES];
#define G_CLIENT_CONN_SPIN_LOCK(addrtype) \
(g_ClientConnSpinLock[ADDRESS_TYPE_TO_INDEX(addrtype)])
USHORT g_ClientConnListCount[NUM_ADDRESS_TYPES];
#define G_CLIENT_CONN_LIST_COUNT(addrtype) \
(&g_ClientConnListCount[ADDRESS_TYPE_TO_INDEX(addrtype)])
TA_IP_ADDRESS g_LocalAddressIP;
TA_IP6_ADDRESS g_LocalAddressIP6;
PTRANSPORT_ADDRESS g_LocalAddresses[NUM_ADDRESS_TYPES];
#define G_LOCAL_ADDRESS(addrtype) \
(g_LocalAddresses[ADDRESS_TYPE_TO_INDEX(addrtype)])
ULONG g_LocalAddressLengths[NUM_ADDRESS_TYPES];
#define G_LOCAL_ADDRESS_LENGTH(addrtype) \
(g_LocalAddressLengths[ADDRESS_TYPE_TO_INDEX(addrtype)])
NPAGED_LOOKASIDE_LIST g_ClientConnectionLookaside;
BOOLEAN g_ClientConnectionInitialized;
/***************************************************************************++
Routine Description:
Performs global initialization of this module.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UcInitializeClientConnections(
VOID
)
{
//
// Sanity check.
//
PAGED_CODE();
//
// Initialize our various free lists etc.
//
InitializeListHead(
&G_CLIENT_TDI_CONNECTION_SLIST_HEAD(TDI_ADDRESS_TYPE_IP)
);
InitializeListHead(
&G_CLIENT_TDI_CONNECTION_SLIST_HEAD(TDI_ADDRESS_TYPE_IP6)
);
UlInitializeSpinLock(
&G_CLIENT_CONN_SPIN_LOCK(TDI_ADDRESS_TYPE_IP),
"g_ClientConnSpinLock"
);
UlInitializeSpinLock(
&G_CLIENT_CONN_SPIN_LOCK(TDI_ADDRESS_TYPE_IP6),
"g_ClientConnSpinLock"
);
//
// Initialize the client lookaside list.
//
ExInitializeNPagedLookasideList(
&g_ClientConnectionLookaside,
NULL,
NULL,
0,
sizeof(UC_CLIENT_CONNECTION),
UC_CLIENT_CONNECTION_POOL_TAG,
0
);
g_ClientConnectionInitialized = TRUE;
//
// Initialize our local address object. This is an addrss object with
// a wildcard address (0 IP, 0 port) that we use for outgoing requests.
//
g_LocalAddressIP.TAAddressCount = 1;
g_LocalAddressIP.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP;
g_LocalAddressIP.Address[0].AddressType = TDI_ADDRESS_TYPE_IP;
g_LocalAddressIP.Address[0].Address[0].sin_port = 0;
g_LocalAddressIP.Address[0].Address[0].in_addr = 0;
g_LocalAddressIP6.TAAddressCount = 1;
g_LocalAddressIP6.Address[0].AddressLength = TDI_ADDRESS_LENGTH_IP6;
g_LocalAddressIP6.Address[0].AddressType = TDI_ADDRESS_TYPE_IP6;
RtlZeroMemory(&g_LocalAddressIP6.Address[0].Address[0],
sizeof(TDI_ADDRESS_IP6));
G_LOCAL_ADDRESS(TDI_ADDRESS_TYPE_IP) =
(PTRANSPORT_ADDRESS)&g_LocalAddressIP;
G_LOCAL_ADDRESS(TDI_ADDRESS_TYPE_IP6) =
(PTRANSPORT_ADDRESS)&g_LocalAddressIP6;
G_LOCAL_ADDRESS_LENGTH(TDI_ADDRESS_TYPE_IP) = sizeof(g_LocalAddressIP);
G_LOCAL_ADDRESS_LENGTH(TDI_ADDRESS_TYPE_IP6) = sizeof(g_LocalAddressIP6);
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Performs termination of a Client TDI connections slist.
Return Value:
None.
--***************************************************************************/
VOID
UcpTerminateClientConnectionsHelper(
IN USHORT AddressType
)
{
PLIST_ENTRY pListHead;
PUC_TDI_OBJECTS pTdiObject;
PLIST_ENTRY pListEntry;
pListHead = &G_CLIENT_TDI_CONNECTION_SLIST_HEAD(AddressType);
//
// Sanity check.
//
PAGED_CODE();
//
// Since this is called from the unload thread, there can't be any other
// thread, so we don't have to take the spin lock.
//
while(!IsListEmpty(pListHead))
{
pListEntry = RemoveHeadList(pListHead);
pTdiObject = CONTAINING_RECORD(
pListEntry,
UC_TDI_OBJECTS,
Linkage
);
(*G_CLIENT_CONN_LIST_COUNT(AddressType))--;
UcpFreeTdiObject(pTdiObject);
}
ASSERT(*G_CLIENT_CONN_LIST_COUNT(AddressType) == 0);
}
/***************************************************************************++
Routine Description:
Performs global termination of this module.
Return Value:
None.
--***************************************************************************/
VOID
UcTerminateClientConnections(
VOID
)
{
//
// Sanity check.
//
PAGED_CODE();
if(g_ClientConnectionInitialized)
{
UcpTerminateClientConnectionsHelper(
TDI_ADDRESS_TYPE_IP
);
UcpTerminateClientConnectionsHelper(
TDI_ADDRESS_TYPE_IP6
);
ExDeleteNPagedLookasideList(&g_ClientConnectionLookaside);
}
}
/***************************************************************************++
Routine Description:
Opens an HTTP connection. The HTTP connection will have a TDI connection
object associated with it and that object will itself be associated
with our address object.
Arguments:
pHttpConnection - Receives a pointer to the HTTP connection object.
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UcOpenClientConnection(
IN PUC_PROCESS_SERVER_INFORMATION pInfo,
OUT PUC_CLIENT_CONNECTION *pUcConnection
)
{
NTSTATUS Status;
PUC_CLIENT_CONNECTION pConnection;
//
// Sanity check.
//
PAGED_CODE();
*pUcConnection = NULL;
//
// Try to snag a connection from the global pool.
//
pConnection = (PUC_CLIENT_CONNECTION)
ExAllocateFromNPagedLookasideList(
&g_ClientConnectionLookaside
);
if(pConnection)
{
//
// One time connection initialization.
//
pConnection->Signature = UC_CLIENT_CONNECTION_SIGNATURE;
UlInitializeSpinLock(&pConnection->SpinLock, "Uc Connection Spinlock");
InitializeListHead(&pConnection->PendingRequestList);
InitializeListHead(&pConnection->SentRequestList);
InitializeListHead(&pConnection->ProcessedRequestList);
UlInitializeWorkItem(&pConnection->WorkItem);
pConnection->bWorkItemQueued = 0;
pConnection->RefCount = 0;
pConnection->Flags = 0;
pConnection->pEvent = NULL;
// Server Cert Info initialization
RtlZeroMemory(&pConnection->ServerCertInfo,
sizeof(pConnection->ServerCertInfo));
CREATE_REF_TRACE_LOG( pConnection->pTraceLog,
96 - REF_TRACE_OVERHEAD,
0,
TRACELOG_LOW_PRIORITY,
UL_REF_TRACE_LOG_POOL_TAG );
//
// Initialize this connection.
//
Status = UcpInitializeConnection(pConnection, pInfo);
if(!NT_SUCCESS(Status))
{
DESTROY_REF_TRACE_LOG( pConnection->pTraceLog,
UL_REF_TRACE_LOG_POOL_TAG);
ExFreeToNPagedLookasideList(
&g_ClientConnectionLookaside,
pConnection
);
return Status;
}
pConnection->ConnectionState = UcConnectStateConnectIdle;
pConnection->SslState = UcSslStateNoSslState;
pConnection->pTdiObjects = NULL;
REFERENCE_CLIENT_CONNECTION(pConnection);
}
else
{
Status = STATUS_INSUFFICIENT_RESOURCES;
}
*pUcConnection = pConnection;
return Status;
}
/***************************************************************************++
Routine Description:
Checks if we can pipeline.
Arguments:
pConnection - Receives a pointer to the HTTP connection object.
Return Value:
TRUE - Yes, we can send the next request.
FALSE - No, cannot.
--***************************************************************************/
BOOLEAN
UcpCheckForPipelining(
IN PUC_CLIENT_CONNECTION pConnection
)
{
PUC_HTTP_REQUEST pPendingRequest, pSentRequest;
PLIST_ENTRY pSentEntry, pPendingEntry;
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
ASSERT( UlDbgSpinLockOwned(&pConnection->SpinLock) );
ASSERT( !IsListEmpty(&pConnection->PendingRequestList) );
// If the remote server supports pipeling and this request does also
// or the sent request list is empty, go ahead and send it.
if( IsListEmpty(&pConnection->SentRequestList) )
{
// Sent List is empty, we can send.
return TRUE;
}
else
{
pPendingEntry = pConnection->PendingRequestList.Flink;
pPendingRequest = CONTAINING_RECORD(pPendingEntry,
UC_HTTP_REQUEST,
Linkage
);
pSentEntry = pConnection->SentRequestList.Flink;
pSentRequest = CONTAINING_RECORD(pSentEntry,
UC_HTTP_REQUEST,
Linkage
);
ASSERT(UC_IS_VALID_HTTP_REQUEST(pPendingRequest));
ASSERT(UC_IS_VALID_HTTP_REQUEST(pSentRequest));
if(pConnection->pServerInfo->pNextHopInfo->Version11 &&
pPendingRequest->RequestFlags.PipeliningAllowed &&
pSentRequest->RequestFlags.PipeliningAllowed
)
{
ASSERT(pSentRequest->RequestFlags.NoRequestEntityBodies);
ASSERT(pPendingRequest->RequestFlags.NoRequestEntityBodies);
ASSERT(pSentRequest->DontFreeMdls == 0);
return TRUE;
}
}
return FALSE;
}
/***************************************************************************++
Routine Description:
Send a request on a client connection. We've given a connection (which
must be referenced when we're called) and a request. The connection may
or may not be established. We'll queue the request to the connection, then
figure out the state of the connection. If it's not connected we'll get
a connection going. If it is connected then we determine if it's ok to
send the request now or not.
Arguments:
pConnection - Pointer to the connection structure on which we're
sending.
pRequest - Pointer to the request we're sending.
Return Value:
NTSTATUS - Status of attempt to send the request.
--***************************************************************************/
NTSTATUS
UcSendRequestOnConnection(
PUC_CLIENT_CONNECTION pConnection,
PUC_HTTP_REQUEST pRequest,
KIRQL OldIrql)
{
BOOLEAN RequestCancelled;
PUC_HTTP_REQUEST pHeadRequest;
PLIST_ENTRY pEntry;
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
pEntry = pConnection->PendingRequestList.Flink;
pHeadRequest = CONTAINING_RECORD(pEntry,
UC_HTTP_REQUEST,
Linkage);
ASSERT(UC_IS_VALID_HTTP_REQUEST(pHeadRequest));
//
// We will call UcSendRequestOnConnection only if
// a. The request is not buffered OR
// b. The request is buffered & we've seen the last entity.
//
// For case a, we might not have seen all the entity body.
// but we still want to send since we know the content-length.
ASSERT(!pRequest->RequestFlags.RequestBuffered ||
(pRequest->RequestFlags.RequestBuffered &&
pRequest->RequestFlags.LastEntitySeen));
// Check the state. If it's connected, we may be able to send the request
// right now. We can send only if we are the "head" request on the list.
if (
// Connection is still active
pConnection->ConnectionState == UcConnectStateConnectReady
&&
// No one else is sending
!(pConnection->Flags & CLIENT_CONN_FLAG_SEND_BUSY)
&&
// We are the head request on this list
(pRequest == pHeadRequest)
&&
// pipelining is OK
UcpCheckForPipelining(pConnection)
)
{
// It's OK to send now.
IoMarkIrpPending(pRequest->RequestIRP);
UcIssueRequests(pConnection, OldIrql);
return STATUS_PENDING;
}
//
// We can't send now, so leave it queued. Since we're leaving this request
// queued set it up for cancelling now.
//
RequestCancelled = UcSetRequestCancelRoutine(
pRequest,
UcpCancelPendingRequest
);
if (RequestCancelled)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
IoMarkIrpPending(pRequest->RequestIRP);
pRequest->RequestIRP = NULL;
//
// Remove this request from the pending list, so that other threads
// don't land up sending it.
//
RemoveEntryList(&pRequest->Linkage);
InitializeListHead(&pRequest->Linkage);
//
// Make sure that any new API calls for this request ID are failed.
//
UcSetFlag(&pRequest->RequestFlags.Value, UcMakeRequestCancelledFlag());
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
return STATUS_PENDING;
}
IoMarkIrpPending(pRequest->RequestIRP);
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_QUEUED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_PENDING)
);
//
// If connection is not ready & if we are the "head" request, then
// we kick off the connection state machine.
//
if (pConnection->ConnectionState != UcConnectStateConnectReady &&
pRequest == pHeadRequest
)
{
UcConnectionStateMachine(pConnection, OldIrql);
}
else
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
return STATUS_PENDING;
}
/***************************************************************************++
Routine Description:
Cancel a pending request that caused a connection. This routine is called
when we're canceling a request that's on the pending list and we've got a
connect IRP outstanding.
Arguments:
pDeviceObject - Pointer to device object.
Irp - Pointer to IRP being canceled.
Return Value:
--***************************************************************************/
VOID
UcCancelSentRequest(
PDEVICE_OBJECT pDeviceObject,
PIRP Irp
)
{
PUC_HTTP_REQUEST pRequest;
PUC_CLIENT_CONNECTION pConnection;
KIRQL OldIrql;
LONG OldReceiveBusy;
UNREFERENCED_PARAMETER(pDeviceObject);
// Release the cancel spin lock, since we're not using it.
IoReleaseCancelSpinLock(Irp->CancelIrql);
// Retrieve the pointers we need. The request pointer is stored inthe
// driver context array, and a back pointer to the connection is stored
// in the request. Whoever set the cancel routine is responsible for
// referencing the connection for us.
pRequest = (PUC_HTTP_REQUEST) Irp->Tail.Overlay.DriverContext[0];
pConnection = pRequest->pConnection;
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
OldReceiveBusy = InterlockedExchange(
&pRequest->ReceiveBusy,
UC_REQUEST_RECEIVE_CANCELLED
);
if(OldReceiveBusy == UC_REQUEST_RECEIVE_BUSY ||
OldReceiveBusy == UC_REQUEST_RECEIVE_CANCELLED)
{
// The cancel routine fired when the request was being parsed. We'll
// just postpone the cancel - The receive thread will pick it up
// later.
pRequest->RequestIRP = Irp;
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CLEAN_PENDED,
pConnection,
pRequest,
Irp,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
return;
}
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
Irp,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
//
// IF we are here, then we are guaranteed that our send has completed.
// But, the app could have asked us not to free the MDL chain (because of
// SSPI auth). In such cases, we have to free it here.
//
// If the SSPI worker thread kicks in, then we'll just fail it.
//
UcFreeSendMdls(pRequest->pMdlHead);
pRequest->pMdlHead = NULL;
pRequest->RequestIRP = NULL;
//
// Note: We cannot just call UcFailRequest from here. UcFailRequest
// is supposed to be called when a request is failed (e.g. parseer
// error) or canceled (HttpCancelRequest API) & hence has code to
// not double complete the IRP if the cancel routine kicked in.
//
// Since we are the IRP cancel routine, we have to manually
// complete the IRP. An IRP in this state has not hit the wire.
// so, we just free send MDLs & cancel it. Note that we call
// UcFailRequest to handle common IRP cleanup.
//
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->RequestorMode = pRequest->AppRequestorMode;
Irp->MdlAddress = pRequest->AppMdl;
UcSetFlag(&pRequest->RequestFlags.Value, UcMakeRequestCancelledFlag());
UcFailRequest(pRequest, STATUS_CANCELLED, OldIrql);
// For the IRP
UC_DEREFERENCE_REQUEST(pRequest);
UlCompleteRequest(Irp, IO_NO_INCREMENT);
}
/*********************************************************************++
Routine Description:
This is our send request complete routine.
Arguments:
pDeviceObject - The device object we called.
pIrp - The IRP that is completing.
Context - Our context value, a pointer to a request
structure.
Return Value:
NTSTATUS - MORE_PROCESSING_REQUIRED if this isn't to be completed now,
SUCCESS otherwise.
--*********************************************************************/
VOID
UcRestartMdlSend(
IN PVOID pCompletionContext,
IN NTSTATUS Status,
IN ULONG_PTR Information
)
{
PUC_HTTP_REQUEST pRequest;
PUC_CLIENT_CONNECTION pConnection;
KIRQL OldIrql;
BOOLEAN RequestCancelled;
PIRP pIrp;
UNREFERENCED_PARAMETER(Information);
pRequest = (PUC_HTTP_REQUEST) pCompletionContext;
pConnection = pRequest->pConnection;
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_SEND_COMPLETE,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr(Status)
);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
//
// If the send complete failed, or if we had pended a request cleanup
// we'll pick them up now.
//
if(!NT_SUCCESS(Status) || pRequest->RequestFlags.CleanPended)
{
if(!NT_SUCCESS(Status))
{
pRequest->RequestStatus = Status;
pRequest->RequestState = UcRequestStateDone;
}
else
{
switch(pRequest->RequestState)
{
case UcRequestStateSent:
pRequest->RequestState = UcRequestStateSendCompleteNoData;
break;
case UcRequestStateNoSendCompletePartialData:
pRequest->RequestState =
UcRequestStateSendCompletePartialData;
break;
case UcRequestStateNoSendCompleteFullData:
pRequest->RequestState = UcRequestStateResponseParsed;
break;
}
Status = pRequest->RequestStatus;
}
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CLEAN_RESUMED,
pConnection,
pRequest,
UlongToPtr(pRequest->RequestState),
UlongToPtr(pConnection->ConnectionState)
);
UcCompleteParsedRequest(pRequest,
Status,
TRUE,
OldIrql
);
return;
}
switch(pRequest->RequestState)
{
case UcRequestStateSent:
pRequest->RequestState = UcRequestStateSendCompleteNoData;
if(!pRequest->RequestFlags.ReceiveBufferSpecified)
{
if(!pRequest->DontFreeMdls)
{
// The app has not supplied any receive buffers. If
// we are not doing SSPI auth (that requires a re-negotiate)
// we can complete the IRP.
pIrp = UcPrepareRequestIrp(pRequest, Status);
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
if(pIrp)
{
UlCompleteRequest(pIrp, IO_NETWORK_INCREMENT);
}
}
else
{
// The app has not passed any receive buffers, but we are
// doing SSPI auth. We cannot free the MDL chain or complete
// the IRP, because we might have to re-negotiate.
//
// We'll insert a cancel routine in the IRP.
if(pRequest->RequestIRP != NULL)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_SET_CANCEL_ROUTINE,
pConnection,
pRequest,
pRequest->RequestIRP,
0
);
RequestCancelled = UcSetRequestCancelRoutine(
pRequest,
UcCancelSentRequest
);
if(RequestCancelled)
{
// Make sure that any new API calls for this
// request ID are failed.
UcSetFlag(&pRequest->RequestFlags.Value,
UcMakeRequestCancelledFlag());
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
pRequest->RequestIRP = NULL;
}
}
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
}
else
{
// The app has specified a receive buffer. We will not complete
// the IRP from here, as we have to wait till the receive
// buffer gets filled up.
if(!pRequest->DontFreeMdls)
{
// If we are not doing SSPI auth, we can free the MDL
// chain.
UcFreeSendMdls(pRequest->pMdlHead);
pRequest->pMdlHead = NULL;
}
if(pRequest->RequestIRP != NULL)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_SET_CANCEL_ROUTINE,
pConnection,
pRequest,
pRequest->RequestIRP,
0
);
RequestCancelled = UcSetRequestCancelRoutine(
pRequest,
UcCancelSentRequest
);
if(RequestCancelled)
{
// Make sure that any new API calls for this
// request ID are failed.
UcSetFlag(&pRequest->RequestFlags.Value,
UcMakeRequestCancelledFlag());
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
pRequest->RequestIRP = NULL;
}
}
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
break;
case UcRequestStateNoSendCompletePartialData:
//
// We got a send complete after receiving some response.
//
pRequest->RequestState = UcRequestStateSendCompletePartialData;
if(!pRequest->RequestFlags.ReceiveBufferSpecified)
{
if(!pRequest->DontFreeMdls)
{
// The app has not supplied any receive buffers. If
// we are not doing SSPI auth (that requires a re-negotiate)
// we can complete the IRP.
pIrp = UcPrepareRequestIrp(pRequest, Status);
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
if(pIrp)
{
UlCompleteRequest(pIrp, IO_NETWORK_INCREMENT);
}
}
else
{
// The app has not passed any receive buffers, but we are
// doing SSPI auth. We cannot free the MDL chain or complete
// the IRP, because we might have to re-negotiate.
//
// We'll insert a cancel routine in the IRP.
if(pRequest->RequestIRP != NULL)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_SET_CANCEL_ROUTINE,
pConnection,
pRequest,
pRequest->RequestIRP,
0
);
RequestCancelled = UcSetRequestCancelRoutine(
pRequest,
UcCancelSentRequest
);
if(RequestCancelled)
{
// Make sure that any new API calls for this
// request ID are failed.
UcSetFlag(&pRequest->RequestFlags.Value,
UcMakeRequestCancelledFlag());
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
pRequest->RequestIRP = NULL;
}
}
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
}
else
{
// The app has specified a receive buffer. If it's been
// fully written, we can complete the IRP.
if(pRequest->RequestIRPBytesWritten)
{
pIrp = UcPrepareRequestIrp(pRequest, Status);
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
if(pIrp)
{
UlCompleteRequest(pIrp, IO_NETWORK_INCREMENT);
}
break;
}
if(!pRequest->DontFreeMdls)
{
// If we are not doing SSPI auth, we can free the MDL
// chain.
UcFreeSendMdls(pRequest->pMdlHead);
pRequest->pMdlHead = NULL;
}
if(pRequest->RequestIRP != NULL)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_SET_CANCEL_ROUTINE,
pConnection,
pRequest,
pRequest->RequestIRP,
0
);
RequestCancelled = UcSetRequestCancelRoutine(
pRequest,
UcCancelSentRequest
);
if(RequestCancelled)
{
// Make sure that any new API calls for this
// request ID are failed.
UcSetFlag(&pRequest->RequestFlags.Value,
UcMakeRequestCancelledFlag());
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
pRequest->RequestIRP = NULL;
}
}
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
break;
case UcRequestStateNoSendCompleteFullData:
// The send complete happened after the response was parsed.
// We don't have to free the MDLs here or complete the IRP,
// as these will be handled by UcCompleteParsedRequest.
//
pRequest->RequestState = UcRequestStateResponseParsed;
UcCompleteParsedRequest(pRequest, Status, TRUE, OldIrql);
break;
default:
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
ASSERT(0);
break;
}
}
/*********************************************************************++
Routine Description:
This is our send request complete routine for entity bodies.
Arguments:
pDeviceObject - The device object we called.
pIrp - The IRP that is completing.
Context - Our context value, a pointer to a request
structure.
Return Value:
NTSTATUS - MORE_PROCESSING_REQUIRED if this isn't to be completed now,
SUCCESS otherwise.
--*********************************************************************/
VOID
UcpRestartEntityMdlSend(
IN PVOID pCompletionContext,
IN NTSTATUS Status,
IN ULONG_PTR Information
)
{
PUC_HTTP_SEND_ENTITY_BODY pEntity;
PUC_HTTP_REQUEST pRequest;
PUC_CLIENT_CONNECTION pConnection;
KIRQL OldIrql;
BOOLEAN bCancelRoutineCalled;
PIRP pIrp;
UNREFERENCED_PARAMETER(Information);
pEntity = (PUC_HTTP_SEND_ENTITY_BODY) pCompletionContext;
pRequest = pEntity->pRequest;
pConnection = pRequest->pConnection;
pIrp = pEntity->pIrp;
ASSERT(UC_IS_VALID_HTTP_REQUEST(pRequest));
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
//
// Free the send MDLs. We want to do this as soon as we can, so we
// do it when the send-completes.
//
ASSERT(pRequest->DontFreeMdls == 0);
ASSERT(pRequest->RequestFlags.RequestBuffered == 0);
UcFreeSendMdls(pEntity->pMdlHead);
//
// If the send complete failed, we have to fail this send, even though
// it might have succeeded
//
if(!NT_SUCCESS(pRequest->RequestStatus))
{
Status = pRequest->RequestStatus;
}
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
RemoveEntryList(&pEntity->Linkage);
//
// try to remove the cancel routine in the IRP
//
bCancelRoutineCalled = UcRemoveEntityCancelRoutine(pEntity);
//
// If we are pending a cleanup, now's the time to complete it.
//
if(pEntity->pRequest->RequestFlags.CleanPended &&
IsListEmpty(&pRequest->SentEntityList))
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CLEAN_RESUMED,
pConnection,
pRequest,
UlongToPtr(pRequest->RequestState),
UlongToPtr(pConnection->ConnectionState)
);
UcCompleteParsedRequest(pRequest,
pRequest->RequestStatus,
TRUE,
OldIrql
);
}
else
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
if(!bCancelRoutineCalled)
{
pIrp->RequestorMode = pEntity->AppRequestorMode;
pIrp->MdlAddress = pEntity->AppMdl;
pIrp->IoStatus.Status = Status;
pIrp->IoStatus.Information = 0;
UlCompleteRequest(pIrp, IO_NETWORK_INCREMENT);
}
UL_FREE_POOL_WITH_QUOTA(
pEntity,
UC_ENTITY_POOL_TAG,
NonPagedPool,
pEntity->BytesAllocated,
pRequest->pServerInfo->pProcess
);
UC_DEREFERENCE_REQUEST(pRequest);
}
/***************************************************************************++
Routine Description:
Issue requests on a connection. This routine is called when another routine
determines that requests need to be issued on the connection. We'll loop,
issuing requests as long as we can. The connection we're given must be both
referenced and have the spin lock held when we're called. Also, the send
busy flag must be set.
Arguments:
pConnection - Pointer to the connection structure on which requests
are to be issued.
OldIrql - The IRQL to be restored when the lock is freed.
Return Value:
--***************************************************************************/
VOID
UcIssueRequests(
PUC_CLIENT_CONNECTION pConnection,
KIRQL OldIrql
)
{
PLIST_ENTRY pEntry;
PUC_HTTP_REQUEST pRequest;
NTSTATUS Status;
BOOLEAN bCloseConnection = FALSE;
ASSERT( UlDbgSpinLockOwned(&pConnection->SpinLock) );
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
//
// We cannot cleanup the connection when we are still sending requests.
// So, we pend connection cleanup when our Send thread is active.
//
// Such pended cleanups will get picked up at the end of this routine.
//
ASSERT(!(pConnection->Flags & CLIENT_CONN_FLAG_SEND_BUSY));
pConnection->Flags |= CLIENT_CONN_FLAG_SEND_BUSY;
// We know it's OK to send when we're first called or we wouldn't have
// been called. Get a pointer to the first entry on the pending list
// and send it. Then we'll keep looping while there's still stuff
// on the pending list that can be sent and the connection is still
// alive.
//
ASSERT(!IsListEmpty(&pConnection->PendingRequestList));
pEntry = pConnection->PendingRequestList.Flink;
for (;;)
{
BOOLEAN bCancelled;
// Remove the current entry from the list, and get a pointer to the
// containing request. We know we're going to send this one if we're
// here, so remove a cancel routine if there is one. If the request
// is already cancelled, skip it, otherwise move it to the sent
// list.
pRequest = CONTAINING_RECORD(
pEntry,
UC_HTTP_REQUEST,
Linkage);
ASSERT( UC_IS_VALID_HTTP_REQUEST(pRequest) );
//
// Can't send something that is still buffered.
//
if(pRequest->RequestFlags.RequestBuffered &&
!pRequest->RequestFlags.LastEntitySeen)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_BUFFERED,
pConnection,
pRequest,
pRequest->RequestIRP,
0
);
break;
}
RemoveEntryList(pEntry);
// See if there was a cancel routine set on this request, and if there
// was remove it. If it's already gone, the request is cancelled.
bCancelled = UcRemoveRequestCancelRoutine(pRequest);
if (bCancelled)
{
ASSERT(pRequest->RequestState == UcRequestStateCaptured);
// If the cancel routine was already null, this request is in the
// process of being cancelled. In that case just initialize the
// linkage on the request (so the cancel routine can't pull it
// from the list again), and continue on.
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
InitializeListHead(pEntry);
}
else
{
UC_REFERENCE_REQUEST(pRequest);
// Either there wasn't a cancel routine or it was removed
// successfully. In either case put this request on the sent
// list and send it.
InsertTailList(&pConnection->SentRequestList, pEntry);
pRequest->RequestState = UcRequestStateSent;
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_SENT,
pConnection,
pRequest,
pRequest->RequestIRP,
0
);
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
// Send it, saving the status for later return.
Status = UcSendData(pConnection,
pRequest->pMdlHead,
pRequest->BytesBuffered,
&UcRestartMdlSend,
(PVOID)pRequest,
pRequest->RequestIRP,
FALSE
);
if(STATUS_PENDING != Status)
{
UcRestartMdlSend(pRequest, Status, 0);
}
// Acquire the spinlock so we can check again.
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
if(pRequest->RequestStatus == STATUS_SUCCESS &&
pConnection->ConnectionState == UcConnectStateConnectReady)
{
if(UcIssueEntities(pRequest, pConnection, &OldIrql) == FALSE)
{
// We have not sent all of the data for this request.
// Additional requests will be blocked from being sent out
// because there will be a request on the SentRequestList
// that still hasn't sent out all of its entities.
//
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_MORE_ENTITY_NEEDED,
pConnection,
pRequest,
pRequest->RequestIRP,
0
);
UC_DEREFERENCE_REQUEST(pRequest);
break;
}
//
// If we have sent out all the entity for this request, see
// if we have to close the connection.
//
if(pRequest->RequestConnectionClose)
{
bCloseConnection = TRUE;
UC_DEREFERENCE_REQUEST(pRequest);
break;
}
}
else
{
//
// The send failed or the connection is torn down.
// If the send failed, we don't necessarily have to tear the
// connection down. If the connection is torn down, we'll
// exit (see below).
}
UC_DEREFERENCE_REQUEST(pRequest);
}
//
// If the pending list is empty or the connection is not active, we
// can't send.
//
if (IsListEmpty(&pConnection->PendingRequestList) ||
pConnection->ConnectionState != UcConnectStateConnectReady
)
{
break;
}
//
// We have at least one request to send out. See if we can pipeline.
//
if(UcpCheckForPipelining(pConnection) == FALSE)
{
break;
}
// We still have something on the list and we might be able to send it.
// Look at it to see if it's OK.
pEntry = pConnection->PendingRequestList.Flink;
}
UcClearConnectionBusyFlag(
pConnection,
CLIENT_CONN_FLAG_SEND_BUSY,
OldIrql,
bCloseConnection
);
}
/***************************************************************************++
Routine Description:
Issue entities on a connection. This routine is called after we send the
original request, or from the context of the send-entity IOCTL handler.
Arguments:
pRequest - pointer to the request that got sent out.
pConnection - Pointer to the connection structure
OldIrql - The IRQL to be restored when the lock is freed.
Return Value:
TRUE - We are done with this request & all of it's entities.
FALSE - More entities to come.
--***************************************************************************/
BOOLEAN
UcIssueEntities(
PUC_HTTP_REQUEST pRequest,
PUC_CLIENT_CONNECTION pConnection,
PKIRQL OldIrql
)
{
PLIST_ENTRY pEntry;
PUC_HTTP_SEND_ENTITY_BODY pEntity;
NTSTATUS Status;
BOOLEAN bLast;
bLast = (0 == pRequest->RequestFlags.LastEntitySeen) ? FALSE : TRUE;
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
ASSERT(pConnection->Flags & CLIENT_CONN_FLAG_SEND_BUSY);
// We know it's OK to send when we're first called or we wouldn't have
// been called. Get a pointer to the first entry on the pending list
// and send it. Then we'll keep looping while there's still stuff
// on the pending list that can be sent and the connection is still
// alive.
//
while(!IsListEmpty(&pRequest->PendingEntityList))
{
BOOLEAN bCancelled;
//
// We don't add buffered entities to the PendingEntityList
//
ASSERT(!pRequest->RequestFlags.RequestBuffered);
// Remove the current entry from the list, and get a pointer to the
// containing request. We know we're going to send this one if we're
// here, so remove a cancel routine if there is one. If the request
// is already cancelled, skip it, otherwise move it to the sent
// list.
pEntry = RemoveHeadList(&pRequest->PendingEntityList);
pEntity = CONTAINING_RECORD(pEntry,
UC_HTTP_SEND_ENTITY_BODY,
Linkage);
bLast = pEntity->Last;
if(pEntity->pIrp)
{
// See if there was a cancel routine set on this request, and if
// there was remove it. If it's already gone, the request is
// cancelled.
bCancelled = UcRemoveEntityCancelRoutine(pEntity);
if (bCancelled)
{
// If the cancel routine was already null, this request is in
// the process of being cancelled. In that case just
// initialize the linkage on the request (so the cancel routine
// can't pull it from the list again), and continue on.
InitializeListHead(pEntry);
}
else
{
// Either there wasn't a cancel routine or it was removed
// successfully. In either case put this request on the sent
// list and send it.
InsertTailList(&pRequest->SentEntityList, &pEntity->Linkage);
// Send it, saving the status for later return.
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_ENTITY_SENT,
pConnection,
pRequest,
pEntity,
pEntity->pIrp
);
UlReleaseSpinLock(&pConnection->SpinLock, *OldIrql);
Status = UcSendData(pConnection,
pEntity->pMdlHead,
pEntity->BytesBuffered,
&UcpRestartEntityMdlSend,
(PVOID)pEntity,
pEntity->pIrp,
FALSE);
if(STATUS_PENDING != Status)
{
UcpRestartEntityMdlSend(pRequest, Status, 0);
}
// Acquire the spinlock so we can check again.
UlAcquireSpinLock(&pConnection->SpinLock, OldIrql);
}
}
}
return bLast;
}
/***************************************************************************++
Routine Description:
Free a client connection structure after the reference count has gone to
zero. Freeing the structure means putting it back onto our free list.
Arguments:
pConnection - Pointer to the connection structure to be freed.
Return Value:
--***************************************************************************/
NTSTATUS
UcpCleanupConnection(
IN PUC_CLIENT_CONNECTION pConnection,
IN KIRQL OldIrql,
IN BOOLEAN Final
)
{
PLIST_ENTRY pList;
PUC_HTTP_REQUEST pRequest;
USHORT FreeAddressType;
PUC_TDI_OBJECTS pTdiObject;
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
//
// First, we flag this connection to make sure that no new requests
// The connect state is used to do this. We don't have to explicitly
// do it here, but we'll just make sure that this is the case.
//
ASSERT(!(pConnection->Flags & CLIENT_CONN_FLAG_CLEANUP_PENDED));
//
// If there's a thread that is issuing requests, we'll resume cleanup
// when it's done.
//
if(pConnection->Flags & CLIENT_CONN_FLAG_SEND_BUSY ||
pConnection->Flags & CLIENT_CONN_FLAG_RECV_BUSY)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_CLEAN_PENDED,
pConnection,
NULL,
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
pConnection->Flags |= CLIENT_CONN_FLAG_CLEANUP_PENDED;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
return STATUS_PENDING;
}
//
// Walk the sent-request list to pick of the requests that got submitted.
//
while(!IsListEmpty(&pConnection->SentRequestList))
{
pList = pConnection->SentRequestList.Flink;
pRequest = CONTAINING_RECORD(pList,
UC_HTTP_REQUEST,
Linkage);
ASSERT( UC_IS_VALID_HTTP_REQUEST(pRequest) );
pRequest->RequestStatus = pConnection->ConnectionStatus;
if(UcCompleteParsedRequest(pRequest,
pRequest->RequestStatus,
FALSE,
OldIrql
) == STATUS_PENDING)
{
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
//
// There could be a window where the request gets cleaned up
// after the lock gets released & before we acquire it again.
// So, before we actually pend connection cleanup, we'll make
// sure that the request is still on the list.
//
if(pList == pConnection->SentRequestList.Flink)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_CLEAN_PENDED,
pConnection,
pRequest,
UlongToPtr(pRequest->RequestState),
UlongToPtr(pConnection->Flags)
);
pConnection->Flags |= CLIENT_CONN_FLAG_CLEANUP_PENDED;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
return STATUS_PENDING;
}
else
{
// This request really got cleaned, so we'll move on to the
// next.
}
}
else
{
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
}
}
if(Final ||
(!IsListEmpty(&pConnection->PendingRequestList) &&
!(pConnection->Flags & CLIENT_CONN_FLAG_CONNECT_READY)))
{
//
// If we are doing final cleanup, then ideally these lists should
// be empty, since we would have kicked these guys off in the
// Cleanup Handler. However, it does not hurt us to check these
// here & clean if there are some entries.
//
// A pended request initiates a connection setup, which can go through
// various phases (e.g. connect failures, wait for server cert,
// proxy SSL, etc), before it's actually ready to be used.
//
// If we get called in the cleanup handler before we move to the
// ready state, then our connection setup has failed and we are
// required to fail all pended requests. If we don't fail the pended
// requests, we'll get into a infinite loop, where we'll constantly
// try to set up a connection (which could fail again).
//
if(Final)
{
while(!IsListEmpty(&pConnection->ProcessedRequestList))
{
pList = RemoveHeadList(&pConnection->ProcessedRequestList);
pRequest = CONTAINING_RECORD(pList,
UC_HTTP_REQUEST,
Linkage);
InitializeListHead(pList);
pRequest->RequestStatus = STATUS_CANCELLED;
ASSERT(pRequest->RequestState == UcRequestStateResponseParsed);
if(UcRemoveRequestCancelRoutine(pRequest))
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
}
else
{
pRequest->RequestState = UcRequestStateDone;
UcCompleteParsedRequest(pRequest,
pRequest->RequestStatus,
FALSE,
OldIrql
);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
}
}
}
while(!IsListEmpty(&pConnection->PendingRequestList))
{
pList = RemoveHeadList(&pConnection->PendingRequestList);
pRequest = CONTAINING_RECORD(pList,
UC_HTTP_REQUEST,
Linkage);
InitializeListHead(pList);
pRequest->RequestStatus = pConnection->ConnectionStatus;
ASSERT(pRequest->RequestState == UcRequestStateCaptured);
if(UcRemoveRequestCancelRoutine(pRequest))
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
pRequest->RequestIRP,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
}
else
{
pRequest->RequestState = UcRequestStateDone;
UcCompleteParsedRequest(pRequest,
pRequest->RequestStatus,
FALSE,
OldIrql
);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
}
}
}
//
// Reset the flags.
//
pConnection->Flags = 0;
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_CLEANED,
pConnection,
UlongToPtr(pConnection->ConnectionStatus),
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
pTdiObject = pConnection->pTdiObjects;
pConnection->pTdiObjects = NULL;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
//
// Let's assume that we did a active close & push this object back
// on the list.
//
if(pTdiObject)
{
FreeAddressType = pTdiObject->ConnectionType;
pTdiObject->pConnection = NULL;
UcpPushTdiObject(pTdiObject, FreeAddressType);
}
//
// Get rid of our opaque id if we're a filtered connection.
// Also make sure we stop delivering AppWrite data to the parser.
//
if (pConnection->FilterInfo.pFilterChannel)
{
HTTP_RAW_CONNECTION_ID ConnectionId;
UlUnbindConnectionFromFilter(&pConnection->FilterInfo);
ConnectionId = pConnection->FilterInfo.ConnectionId;
HTTP_SET_NULL_ID( &pConnection->FilterInfo.ConnectionId );
if (!HTTP_IS_NULL_ID( &ConnectionId ))
{
UlFreeOpaqueId(ConnectionId, UlOpaqueIdTypeRawConnection);
DEREFERENCE_CLIENT_CONNECTION(pConnection);
}
UlDestroyFilterConnection(&pConnection->FilterInfo);
DEREFERENCE_FILTER_CHANNEL(pConnection->FilterInfo.pFilterChannel);
pConnection->FilterInfo.pFilterChannel = NULL;
}
//
// Get rid of any buffers we allocated for
// certificate information.
//
if (pConnection->FilterInfo.SslInfo.pServerCertData)
{
UL_FREE_POOL(
pConnection->FilterInfo.SslInfo.pServerCertData,
UL_SSL_CERT_DATA_POOL_TAG
);
pConnection->FilterInfo.SslInfo.pServerCertData = NULL;
}
if (pConnection->FilterInfo.SslInfo.pCertEncoded)
{
UL_FREE_POOL(
pConnection->FilterInfo.SslInfo.pCertEncoded,
UL_SSL_CERT_DATA_POOL_TAG
);
pConnection->FilterInfo.SslInfo.pCertEncoded = NULL;
}
if (pConnection->FilterInfo.SslInfo.Token)
{
HANDLE Token;
Token = (HANDLE) pConnection->FilterInfo.SslInfo.Token;
//
// If we are not running under the system process. And if the
// thread we are running under has some APCs queued currently
// KeAttachProcess won't allow us to attach to another process
// and will bugcheck 5. We have to be queued as a work item and
// should be running on the passive IRQL.
//
ASSERT( PsGetCurrentProcess() == (PEPROCESS) g_pUlSystemProcess );
ZwClose(Token);
}
//
// Free any allocated memory
//
if(pConnection->MergeIndication.pBuffer)
{
UL_FREE_POOL_WITH_QUOTA(
pConnection->MergeIndication.pBuffer,
UC_RESPONSE_TDI_BUFFER_POOL_TAG,
NonPagedPool,
pConnection->MergeIndication.BytesAllocated,
pConnection->pServerInfo->pProcess
);
pConnection->MergeIndication.pBuffer = NULL;
}
// Free serialized server certificate, if any
UC_FREE_SERIALIZED_CERT(&pConnection->ServerCertInfo,
pConnection->pServerInfo->pProcess);
// Free certificate issuer list, if any
UC_FREE_CERT_ISSUER_LIST(&pConnection->ServerCertInfo,
pConnection->pServerInfo->pProcess);
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
The common connect complete routine. Called from the TDI UcpConnectComplete
handler.
Arguments:
pConnection - Pointer to the connection structure to be freed.
Status - Connection Status.
Return Value:
--***************************************************************************/
VOID
UcRestartClientConnect(
IN PUC_CLIENT_CONNECTION pConnection,
IN NTSTATUS Status
)
{
KIRQL OldIrql;
BOOLEAN bCloseConnection;
bCloseConnection = FALSE;
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
pConnection->ConnectionStatus = Status;
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_RESTART_CONNECT,
pConnection,
UlongToPtr(pConnection->ConnectionStatus),
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
//
// First, we check to see if we have received a Cleanup/Close IRP.
// If that's the case, we proceed directly to cleanup, regardless of
// the status.
//
// Now, if the connection was successfully established, we have to tear
// it down. This will eventually cleanup the connection & complete the
// pended Cleanup/Close IRP.
//
if(pConnection->pEvent)
{
pConnection->Flags &= ~CLIENT_CONN_FLAG_TDI_ALLOCATE;
if(Status == STATUS_SUCCESS)
{
pConnection->ConnectionState = UcConnectStateConnectComplete;
bCloseConnection = TRUE;
}
else
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_CLEANUP,
pConnection,
UlongToPtr(pConnection->ConnectionStatus),
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
pConnection->ConnectionState = UcConnectStateConnectCleanup;
pConnection->ConnectionStatus = STATUS_CANCELLED;
}
}
else if (Status == STATUS_SUCCESS)
{
// It did, we connected. Now make sure we're still in the connecting
// state, and get pending requests going.
ASSERT(pConnection->ConnectionState == UcConnectStateConnectPending);
pConnection->Flags &= ~CLIENT_CONN_FLAG_TDI_ALLOCATE;
pConnection->ConnectionState = UcConnectStateConnectComplete;
}
else if (Status == STATUS_ADDRESS_ALREADY_EXISTS)
{
// If a connection attempt fails with STATUS_ADDRESS_ALREADY_EXISTS
// it means that the TCB that is represented by the AO+CO object
// is in TIME_WAIT. We'll put this AO+CO back on our list &
// proceed to allocate a new one from TCP.
//
// If a newly allocated AO+CO also fails with TIME_WAIT, then we'll
// just give up & show the error to the application.
//
if(pConnection->Flags & CLIENT_CONN_FLAG_TDI_ALLOCATE)
{
// Darn. An newly allocated TDI object also failed with TIME_WAIT.
// we'll have to fail the connection attempt. This is our
// recursion breaking condition.
pConnection->Flags &= ~CLIENT_CONN_FLAG_TDI_ALLOCATE;
goto ConnectFailure;
}
else
{
// The actual free of the AO+CO will happen in the
// Connection State Machine.
pConnection->ConnectionState = UcConnectStateConnectIdle;
}
}
else
{
// This connect attempt failed. See if there are any more addresses.
// getaddrinfo can pass back a list of addresses & we'll try all those
// addresses before giving up & bouncing the error to the app.
// Some of these maybe IPv4 address & some others maybe IPv6 addresses.
//
// We use pConnection->NextAddressCount to make sure that we don't
// overflow the address-list that's stored in the ServInfo structure.
//
pConnection->NextAddressCount =
(pConnection->NextAddressCount + 1) %
pConnection->pServerInfo->pTransportAddress->TAAddressCount;
//
// pConnection->pNextAddress points to a TA_ADDRESS structure in the
// TRANSPORT_ADDRESS list that is stored off the ServInfo structure.
// This will be used for the "next" connect attempt.
//
pConnection->pNextAddress = (PTA_ADDRESS)
((PCHAR) pConnection->pNextAddress +
FIELD_OFFSET(TA_ADDRESS, Address) +
pConnection->pNextAddress->AddressLength);
if(pConnection->NextAddressCount == 0)
{
// We've rolled back (i.e we've cycled through all the IP addresses
// Let's treat this as a real failure & propogate the error to the
// application.
//
ConnectFailure:
// The connect failed. We need to fail any pending requests.
pConnection->Flags &= ~CLIENT_CONN_FLAG_TDI_ALLOCATE;
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_CLEANUP,
pConnection,
UlongToPtr(Status),
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
pConnection->ConnectionState = UcConnectStateConnectCleanup;
}
else
{
// Set the state to IDLE, so that we use the next address to
// connect.
pConnection->Flags &= ~CLIENT_CONN_FLAG_TDI_ALLOCATE;
pConnection->ConnectionState = UcConnectStateConnectIdle;
}
}
//
// We can't be sending any reqeusts when a connection attempt is in
// progress.
//
ASSERT(IsListEmpty(&pConnection->SentRequestList));
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
if(bCloseConnection)
{
UC_CLOSE_CONNECTION(pConnection, TRUE, STATUS_CANCELLED);
}
}
/***************************************************************************++
Routine Description:
Cancel a pending request. This routine is called when we're canceling
a request that's on the pending list, hasn't been sent and hasn't caused
a connect request.
Arguments:
pDeviceObject - Pointer to device object.
Irp - Pointer to IRP being canceled.
Return Value:
--***************************************************************************/
VOID
UcpCancelPendingRequest(
PDEVICE_OBJECT pDeviceObject,
PIRP Irp
)
{
PUC_HTTP_REQUEST pRequest;
PUC_CLIENT_CONNECTION pConnection;
KIRQL OldIrql;
UNREFERENCED_PARAMETER(pDeviceObject);
// Release the cancel spin lock, since we're not using it.
IoReleaseCancelSpinLock(Irp->CancelIrql);
// Retrieve the pointers we need. The request pointer is stored inthe
// driver context array, and a back pointer to the connection is stored
// in the request. Whoever set the cancel routine is responsible for
// referencing the connection for us.
pRequest = (PUC_HTTP_REQUEST)Irp->Tail.Overlay.DriverContext[0];
pConnection = pRequest->pConnection;
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_REQUEST_CANCELLED,
pConnection,
pRequest,
Irp,
UlongToPtr((ULONG)STATUS_CANCELLED)
);
//
// Note: We cannot just call UcFailRequest from here. UcFailRequest
// is supposed to be called when a request is failed (e.g. parseer
// error) or canceled (HttpCancelRequest API) & hence has code to
// not double complete the IRP if the cancel routine kicked in.
//
// Since we are the IRP cancel routine, we have to manually
// complete the IRP. An IRP in this state has not hit the wire.
// so, we just free send MDLs & cancel it. Note that we call
// UcFailRequest to handle common IRP cleanup.
//
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
UcFreeSendMdls(pRequest->pMdlHead);
pRequest->pMdlHead = NULL;
pRequest->RequestIRP = NULL;
Irp->IoStatus.Status = STATUS_CANCELLED;
Irp->RequestorMode = pRequest->AppRequestorMode;
Irp->MdlAddress = pRequest->AppMdl;
UcSetFlag(&pRequest->RequestFlags.Value, UcMakeRequestCancelledFlag());
UcFailRequest(pRequest, STATUS_CANCELLED, OldIrql);
// For the IRP
UC_DEREFERENCE_REQUEST(pRequest);
UlCompleteRequest(Irp, IO_NO_INCREMENT);
}
/***************************************************************************++
Routine Description:
Send an entity body on a connection.
Arguments:
Return Value:
--***************************************************************************/
NTSTATUS
UcSendEntityBody(
IN PUC_HTTP_REQUEST pRequest,
IN PUC_HTTP_SEND_ENTITY_BODY pEntity,
IN PIRP pIrp,
IN PIO_STACK_LOCATION pIrpSp,
OUT PBOOLEAN bDontFail,
IN BOOLEAN bLast
)
{
NTSTATUS Status;
PUC_CLIENT_CONNECTION pConnection;
KIRQL OldIrql;
BOOLEAN RequestCancelled;
BOOLEAN bCloseConnection = FALSE;
pConnection = pRequest->pConnection;
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
ASSERT(UC_IS_VALID_HTTP_REQUEST(pRequest));
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
if(pRequest->RequestState == UcRequestStateDone ||
pRequest->RequestFlags.Cancelled == TRUE ||
pRequest->RequestFlags.LastEntitySeen
)
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
return STATUS_INVALID_PARAMETER;
}
if(bLast)
{
UcSetFlag(&pRequest->RequestFlags.Value,
UcMakeRequestLastEntitySeenFlag());
}
if(pRequest->RequestFlags.RequestBuffered)
{
if(pRequest->RequestFlags.LastEntitySeen)
{
//
// We have seen the last entity for this request. We had already
// pinned the request on a connection (by inserting it in the
// pending list), we just need to issue the request.
//
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_ENTITY_LAST,
pConnection,
pRequest,
pEntity,
pIrp
);
InsertTailList(&pRequest->SentEntityList, &pEntity->Linkage);
//
// If the request IRP is around, we'll just use it. Otherwise,
// we'll use the entity IRP.
//
if(pRequest->RequestIRP)
{
// If the request IRP is around, it means that the app has
// passed a receive buffer. In such cases, we will use the
// request IRP to call TDI.
//
// We can complete the entity IRP with status_success since
// we don't need it. If this thread return something that is
// not STATUS_PENDING, then the IOCTL handler will complete
// the IRP.
ASSERT(pRequest->RequestFlags.ReceiveBufferSpecified == TRUE);
pEntity->pIrp = 0;
Status = UcSendRequestOnConnection(pConnection,
pRequest,
OldIrql);
// If we get STATUS_PENDING, we want the IOCTL handler to
// complete the entity IRP with success, since we used the
// request IRP. However, if we get any other status, then
// we want to propogate it to the IOCTL handler. This will
// fail the request, which will complete the request IRP.
return ((Status == STATUS_PENDING)?STATUS_SUCCESS:Status);
}
else
{
pEntity->pIrp = 0;
pRequest->AppRequestorMode = pIrp->RequestorMode;
pRequest->AppMdl = pIrp->MdlAddress;
pRequest->RequestIRP = pIrp;
pRequest->RequestIRPSp = pIrpSp;
ASSERT(pRequest->pFileObject == pIrpSp->FileObject);
// Take a ref for the IRP.
UC_REFERENCE_REQUEST(pRequest);
return UcSendRequestOnConnection(pConnection,
pRequest,
OldIrql);
}
}
else
{
//
// We have buffered the request & hence we have completed it early.
// Let's do the same with the entity body also.
//
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_ENTITY_BUFFERED,
pConnection,
pRequest,
pEntity,
pIrp
);
InsertTailList(&pRequest->SentEntityList, &pEntity->Linkage);
pEntity->pIrp->IoStatus.Status = STATUS_SUCCESS;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
return STATUS_SUCCESS;
}
}
//
// If the request has not been buffered earlier, we can send right
// away.
//
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_ENTITY_READY_TO_SEND,
pConnection,
pRequest,
pEntity,
pIrp
);
if(pConnection->Flags & CLIENT_CONN_FLAG_SEND_BUSY ||
(pRequest->RequestState == UcRequestStateCaptured) ||
(!IsListEmpty(&pRequest->PendingEntityList))
)
{
//
// We can't send this request now. Either
// a. This request itself has not been sent to TDI.
// b. Other send-entities are ahead of us.
// c. This request has not seen all of it's entity bodies.
//
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_ENTITY_QUEUED,
pConnection,
pRequest,
pEntity,
pIrp
);
InsertTailList(&pRequest->PendingEntityList, &pEntity->Linkage);
IoMarkIrpPending(pEntity->pIrp);
RequestCancelled = UcSetEntityCancelRoutine(
pEntity,
UcpCancelSendEntity
);
if(RequestCancelled)
{
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_ENTITY_CANCELLED,
pConnection,
pRequest,
pEntity,
pIrp
);
pEntity->pIrp = NULL;
}
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
return STATUS_PENDING;
}
if(pConnection->ConnectionState == UcConnectStateConnectReady &&
pRequest->RequestState != UcRequestStateResponseParsed &&
pRequest->RequestState != UcRequestStateNoSendCompleteFullData)
{
// We can send now as we are doing chunked sends. Rather than
// calling UcSendData directly, we'll call UcIssueEntities.
// We could get multiple send-entities, and we don't want them
// to go out of order.
pConnection->Flags |= CLIENT_CONN_FLAG_SEND_BUSY;
Status = STATUS_PENDING;
InsertTailList(&pRequest->PendingEntityList, &pEntity->Linkage);
IoMarkIrpPending(pEntity->pIrp);
UC_REFERENCE_REQUEST(pRequest);
if(UcIssueEntities(pRequest, pConnection, &OldIrql))
{
// We have sent the last entity. Now, we can see if we want to
// send the next request or clear the flag.
if(pRequest->RequestConnectionClose)
{
//
// Remember that we've to close the connection. We'll do this
// after we release the spin lock.
//
bCloseConnection = TRUE;
}
else if(
pConnection->ConnectionState == UcConnectStateConnectReady &&
!IsListEmpty(&pConnection->PendingRequestList) &&
IsListEmpty(&pConnection->SentRequestList)
)
{
UC_DEREFERENCE_REQUEST(pRequest);
ASSERT(pConnection->Flags & CLIENT_CONN_FLAG_SEND_BUSY);
pConnection->Flags &= ~CLIENT_CONN_FLAG_SEND_BUSY;
//
// Connection is still ready, see if we can send any more
// requests. Note that we have to check the state again, because
// we are releasing the lock above.
//
UcIssueRequests(pConnection, OldIrql);
return STATUS_PENDING;
}
}
UC_DEREFERENCE_REQUEST(pRequest);
UcClearConnectionBusyFlag(
pConnection,
CLIENT_CONN_FLAG_SEND_BUSY,
OldIrql,
bCloseConnection
);
}
else
{
// It appears as if the connection was torn down for some reason.
// let's propogate this error to the app.
//
// We don't want to fail the request because of this error code.
// The connection could be torn down because of a 401, and we want to
// give the app a chance to read the response buffer. If we fail the
// request, we are preventing the app from reading the response.
//
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
*bDontFail = TRUE;
Status = STATUS_CONNECTION_DISCONNECTED;
}
return Status;
}
/***************************************************************************++
Routine Description:
Reference a client connection structure.
Arguments:
pConnection - Pointer to the connection structure to be referenced.
Return Value:
--***************************************************************************/
VOID
UcReferenceClientConnection(
PVOID pObject
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
LONG RefCount;
PUC_CLIENT_CONNECTION pConnection;
pConnection = (PUC_CLIENT_CONNECTION) pObject;
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
RefCount = InterlockedIncrement(&pConnection->RefCount);
WRITE_REF_TRACE_LOG(
g_pTdiTraceLog,
REF_ACTION_REFERENCE_UL_CONNECTION,
RefCount,
pConnection,
pFileName,
LineNumber
);
ASSERT( RefCount > 0 );
}
/***************************************************************************++
Routine Description:
Dereference a client connection structure. If the reference count goes
to 0, we'll free the structure.
Arguments:
pConnection - Pointer to the connection structure to be
dereferenced.
Return Value:
--***************************************************************************/
VOID
UcDereferenceClientConnection(
PVOID pObject
REFERENCE_DEBUG_FORMAL_PARAMS
)
{
LONG RefCount;
PUC_CLIENT_CONNECTION pConnection;
pConnection = (PUC_CLIENT_CONNECTION) pObject;
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
RefCount = InterlockedDecrement(&pConnection->RefCount);
WRITE_REF_TRACE_LOG(
g_pTdiTraceLog,
REF_ACTION_DEREFERENCE_UL_CONNECTION,
RefCount,
pConnection,
pFileName,
LineNumber
);
ASSERT(RefCount >= 0);
if (RefCount == 0)
{
ASSERT(pConnection->pTdiObjects == NULL);
DESTROY_REF_TRACE_LOG(pConnection->pTraceLog,
UL_REF_TRACE_LOG_POOL_TAG);
if(pConnection->pEvent)
{
KeSetEvent(pConnection->pEvent, 0, FALSE);
pConnection->pEvent = NULL;
}
ExFreeToNPagedLookasideList(
&g_ClientConnectionLookaside,
pConnection
);
}
}
/***************************************************************************++
Routine Description:
The worker thread that calls the connection state machine.
Arguments:
pWorkItem - Pointer to the work-item
Return Value:
None
--***************************************************************************/
VOID
UcpConnectionStateMachineWorker(
IN PUL_WORK_ITEM pWorkItem
)
{
KIRQL OldIrql;
PUC_CLIENT_CONNECTION pConnection;
//
// Sanity check.
//
ASSERT( KeGetCurrentIrql() == PASSIVE_LEVEL );
//
// Grab the connection.
//
pConnection = CONTAINING_RECORD(
pWorkItem,
UC_CLIENT_CONNECTION,
WorkItem
);
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
ASSERT(pConnection->bWorkItemQueued);
pConnection->bWorkItemQueued = FALSE;
UcConnectionStateMachine(pConnection, OldIrql);
DEREFERENCE_CLIENT_CONNECTION(pConnection);
}
/***************************************************************************++
Routine Description:
This routine kicks off the worker thread that fires the connection state
machine. This is always called with the connection spin lock held. If
the worker has already been issued but not fired, we don't do anything.
Arguments:
pConnection - Pointer to the connection structure
OldIrql - The IRQL that we have to use when calling UlReleaseSpinLock.
Return Value:
None
--***************************************************************************/
VOID
UcKickOffConnectionStateMachine(
IN PUC_CLIENT_CONNECTION pConnection,
IN KIRQL OldIrql,
IN UC_CONNECTION_WORKER_TYPE WorkerType
)
{
if(!pConnection->bWorkItemQueued)
{
pConnection->bWorkItemQueued = TRUE;
REFERENCE_CLIENT_CONNECTION(pConnection);
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
if(UcConnectionPassive == WorkerType)
{
// If we are already at PASSIVE, UL_CALL_PASSIVE calls the callback
// in the context of the same thread.
UL_CALL_PASSIVE(&pConnection->WorkItem,
&UcpConnectionStateMachineWorker);
}
else
{
ASSERT(UcConnectionWorkItem == WorkerType);
UL_QUEUE_WORK_ITEM(&pConnection->WorkItem,
&UcpConnectionStateMachineWorker);
}
}
else
{
//
// Someone else has already done this.
//
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
}
/***************************************************************************++
Routine Description:
This routine computes the size required to store HTTP_RAW_CONNECTION_INFO
structure. The computation takes into account the space needed to store
embedded pointers to structures. See the diagram below.
Arguments:
pConnectionContext - Pointer to UC_CLIENT_CONNECTION.
Return Value:
Length (in bytes) needed to generate raw connection info structure.
--***************************************************************************/
ULONG
UcComputeHttpRawConnectionLength(
IN PVOID pConnectionContext
)
{
PUC_CLIENT_CONNECTION pConnection;
ULONG ReturnLength;
pConnection = (PUC_CLIENT_CONNECTION) pConnectionContext;
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
//
// Memory layout: (must be in sync with UcGenerateHttpRawConnectionInfo)
//
// +---------------------------------------------------------------+
// | H_R_C_I |\\\| T_A_L_I |\\| T_A_L_I |\\| H_C_S_C | Server Name |
// +---------------------------------------------------------------+
//
ReturnLength = ALIGN_UP(sizeof(HTTP_RAW_CONNECTION_INFO), PVOID);
ReturnLength += 2 * ALIGN_UP(TDI_ADDRESS_LENGTH_IP6, PVOID);
ReturnLength += sizeof(HTTP_CLIENT_SSL_CONTEXT);
ReturnLength += pConnection->pServerInfo->pServerInfo->ServerNameLength;
return ReturnLength;
}
/***************************************************************************++
Routine Description:
Builds the HTTP_RAW_CONNECTION structure
Arguments:
pContext - Pointer to the UL_CONNECTION
pKernelBuffer - Pointer to kernel buffer
pUserBuffer - Pointer to user buffer
OutputBufferLength - Length of output buffer
pBuffer - Buffer for holding any data
InitialLength - Size of input data.
--***************************************************************************/
ULONG
UcGenerateHttpRawConnectionInfo(
IN PVOID pContext,
IN PUCHAR pKernelBuffer,
IN PVOID pUserBuffer,
IN ULONG OutputBufferLength,
IN PUCHAR pBuffer,
IN ULONG InitialLength
)
{
PHTTP_RAW_CONNECTION_INFO pConnInfo;
PTDI_ADDRESS_IP6 pLocalAddress;
PTDI_ADDRESS_IP6 pRemoteAddress;
PHTTP_TRANSPORT_ADDRESS pAddress;
PUC_CLIENT_CONNECTION pConnection;
ULONG BytesCopied = 0;
PUCHAR pInitialData;
PUCHAR pCurr;
PWSTR pServerName;
USHORT ServerNameLength = 0;
PHTTP_CLIENT_SSL_CONTEXT pClientSSLContext;
pConnection = (PUC_CLIENT_CONNECTION) pContext;
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
//
// We'll assume that the kernel buffer is PVOID aligned. Based on this,
// pointer, other pointers must be aligned.
//
ASSERT(pKernelBuffer == ALIGN_UP_POINTER(pKernelBuffer, PVOID));
//
// N.B. pCurr must always be PVOID aligned.
//
pCurr = pKernelBuffer;
//
// Create HTTP_RAW_CONNECTION_INFO structure.
//
pConnInfo = (PHTTP_RAW_CONNECTION_INFO)pCurr;
pCurr += ALIGN_UP(sizeof(HTTP_RAW_CONNECTION_INFO), PVOID);
//
// Create Local TDI_ADDRESS_IP6 structure.
//
pLocalAddress = (PTDI_ADDRESS_IP6)pCurr;
pCurr += ALIGN_UP(sizeof(TDI_ADDRESS_IP6), PVOID);
//
// Create Remote TDI_ADDRESS_IP6 structure.
//
pRemoteAddress = (PTDI_ADDRESS_IP6)pCurr;
pCurr += ALIGN_UP(sizeof(TDI_ADDRESS_IP6), PVOID);
//
// Create HTTP_CLIENT_SSL_CONTEXT structure.
//
pClientSSLContext = (PHTTP_CLIENT_SSL_CONTEXT)pCurr;
//
// The rest of the space is used to store the server name followed by
// initialize data.
//
pServerName = &pClientSSLContext->ServerName[0];
ServerNameLength = pConnection->pServerInfo->pServerInfo->ServerNameLength;
pInitialData = (PUCHAR) ((PUCHAR)pServerName + ServerNameLength);
pConnInfo->pClientSSLContext = (PHTTP_CLIENT_SSL_CONTEXT)
FIXUP_PTR(
PVOID,
pUserBuffer,
pKernelBuffer,
pClientSSLContext,
OutputBufferLength
);
//
// The last element of HTTP_CLIENT_SSL_CONTEXT is an array WCHAR[1].
// The following calculation takes that WCHAR into account.
//
pConnInfo->ClientSSLContextLength = sizeof(HTTP_CLIENT_SSL_CONTEXT)
- sizeof(WCHAR)
+ ServerNameLength;
// Ssl protocol version to be used for this connection
pClientSSLContext->SslProtocolVersion =
pConnection->pServerInfo->SslProtocolVersion;
// Client certificate to be used for this connection
pClientSSLContext->pClientCertContext =
pConnection->pServerInfo->pClientCert;
// Copy the server certificate validation mode.
pClientSSLContext->ServerCertValidation =
pConnection->pServerInfo->ServerCertValidation;
pClientSSLContext->ServerNameLength = ServerNameLength;
RtlCopyMemory(
pServerName,
pConnection->pServerInfo->pServerInfo->pServerName,
ServerNameLength
);
//
// Now fill in the raw connection data structure.
//
pConnInfo->ConnectionId = pConnection->FilterInfo.ConnectionId;
pAddress = &pConnInfo->Address;
pAddress->pRemoteAddress = FIXUP_PTR(
PVOID,
pUserBuffer,
pKernelBuffer,
pRemoteAddress,
OutputBufferLength
);
pAddress->pLocalAddress = FIXUP_PTR(
PVOID,
pUserBuffer,
pKernelBuffer,
pLocalAddress,
OutputBufferLength
);
RtlZeroMemory(pRemoteAddress, sizeof(TDI_ADDRESS_IP6));
RtlZeroMemory(pLocalAddress, sizeof(TDI_ADDRESS_IP6));
//
// Copy any initial data.
//
if (InitialLength)
{
ASSERT(pBuffer);
pConnInfo->InitialDataSize = InitialLength;
pConnInfo->pInitialData = FIXUP_PTR(
PVOID, // Type
pUserBuffer, // pUserPtr
pKernelBuffer, // pKernelPtr
pInitialData, // pOffsetPtr
OutputBufferLength // BufferLength
);
RtlCopyMemory(
pInitialData,
pBuffer,
InitialLength
);
BytesCopied += InitialLength;
}
return BytesCopied;
}
/****************************************************************************++
Routine Description:
Triggers the connection state machine
Called when a server certificate is received from the filter.
Arguments:
None.
Return Value:
None.
--****************************************************************************/
VOID
UcServerCertificateInstalled(
IN PVOID pConnectionContext,
IN NTSTATUS Status
)
{
PUC_CLIENT_CONNECTION pConnection;
KIRQL OldIrql;
pConnection = (PUC_CLIENT_CONNECTION) pConnectionContext;
ASSERT( UC_IS_VALID_CLIENT_CONNECTION(pConnection) );
if (!NT_SUCCESS(Status))
{
UC_CLOSE_CONNECTION(pConnection, TRUE, Status);
}
else
{
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
ASSERT(pConnection->ConnectionState ==
UcConnectStatePerformingSslHandshake);
ASSERT(pConnection->SslState == UcSslStateServerCertReceived);
UcKickOffConnectionStateMachine(
pConnection,
OldIrql,
UcConnectionWorkItem
);
}
}
/***************************************************************************++
Routine Description:
This routine is the connection state machine
Arguments:
pConnection - Pointer to the connection structure
OldIrql - The IRQL that we have to use when calling UlReleaseSpinLock.
Return Value:
None
--***************************************************************************/
VOID
UcConnectionStateMachine(
IN PUC_CLIENT_CONNECTION pConnection,
IN KIRQL OldIrql
)
{
ULONG TakenLength;
NTSTATUS Status;
PUC_HTTP_REQUEST pHeadRequest;
PLIST_ENTRY pEntry;
PUC_PROCESS_SERVER_INFORMATION pServInfo;
USHORT AddressType, FreeAddressType;
PUC_TDI_OBJECTS pTdiObjects;
// Sanity check.
ASSERT(UlDbgSpinLockOwned(&pConnection->SpinLock));
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_STATE_ENTER,
pConnection,
UlongToPtr(pConnection->Flags),
UlongToPtr(pConnection->ConnectionState),
0
);
pServInfo = pConnection->pServerInfo;
Begin:
ASSERT( UlDbgSpinLockOwned(&pConnection->SpinLock) );
switch(pConnection->ConnectionState)
{
case UcConnectStateConnectCleanup:
Cleanup:
ASSERT(pConnection->ConnectionState == UcConnectStateConnectCleanup);
pConnection->ConnectionState = UcConnectStateConnectCleanupBegin;
if(pConnection->pEvent)
{
//
// We have been called in the cleanup handler, so we just clean
// the connection & not initialize it again. This connection will
// make its' way into the SLIST & will be there for re-use.
//
UcpCleanupConnection(pConnection, OldIrql, TRUE);
break;
}
else
{
//
// We have come here from a Disconnect handler or a Abort handler.
// We have to clean up the connection & then re-initialize it. In
// the process of cleaning up the connection, we will release the
// lock. So we move to an interim state, so that we dont' cleanup
// twice.
//
Status = UcpCleanupConnection(pConnection, OldIrql, FALSE);
if(Status == STATUS_PENDING)
{
// Our cleanup is pended because we are waiting for sends
// to complete.
break;
}
Status = UcpInitializeConnection(pConnection,
pServInfo);
if(!NT_SUCCESS(Status))
{
LIST_ENTRY TempList;
PUC_HTTP_REQUEST pRequest;
InitializeListHead(&TempList);
//
// What do we do here ? This connection is not usable but
// we have not been called in the cleanup handler. We'll remove
// this connection from the active list, which will prevent it
// from being used by any new requests.
//
UlAcquirePushLockExclusive(&pServInfo->PushLock);
// Make the connection unaccessible from ServInfo
ASSERT(pConnection->ConnectionIndex <
pServInfo->MaxConnectionCount);
ASSERT(pServInfo->Connections[pConnection->ConnectionIndex]
== pConnection);
pServInfo->Connections[pConnection->ConnectionIndex] = NULL;
// Invalidate connection index
pConnection->ConnectionIndex = HTTP_REQUEST_ON_CONNECTION_ANY;
pServInfo->CurrentConnectionCount--;
UlReleasePushLock(&pServInfo->PushLock);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
//
// Get rid of all requests from the processed & pended lists.
//
while(!IsListEmpty(&pConnection->ProcessedRequestList))
{
pEntry = RemoveHeadList(&pConnection->ProcessedRequestList);
pRequest = CONTAINING_RECORD(
pEntry,
UC_HTTP_REQUEST,
Linkage
);
ASSERT( UC_IS_VALID_HTTP_REQUEST(pRequest) );
UC_REFERENCE_REQUEST(pRequest);
InsertHeadList(&TempList, &pRequest->Linkage);
}
while(!IsListEmpty(&pConnection->PendingRequestList))
{
pEntry = RemoveHeadList(&pConnection->PendingRequestList);
InsertHeadList(&TempList, pEntry);
}
ASSERT(IsListEmpty(&pConnection->SentRequestList));
//
// Dereference for the ServInfo.
//
DEREFERENCE_CLIENT_CONNECTION(pConnection);
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_CLEANUP,
pConnection,
UlongToPtr(pConnection->ConnectionStatus),
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
pConnection->ConnectionState = UcConnectStateConnectCleanup;
while(!IsListEmpty(&TempList))
{
pEntry = TempList.Flink;
pRequest = CONTAINING_RECORD(pEntry,
UC_HTTP_REQUEST,
Linkage);
UcFailRequest(pRequest, Status, OldIrql);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
}
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
break;
}
else
{
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
if(pConnection->pEvent)
{
pConnection->ConnectionState = UcConnectStateConnectCleanup;
UcpCleanupConnection(pConnection, OldIrql, TRUE);
break;
}
else
{
pConnection->ConnectionState = UcConnectStateConnectIdle;
//
// FALL Through!
//
}
}
}
case UcConnectStateConnectIdle:
ASSERT(pConnection->ConnectionState == UcConnectStateConnectIdle);
if(!IsListEmpty(&pConnection->PendingRequestList))
{
pConnection->ConnectionState = UcConnectStateConnectPending;
AddressType = pConnection->pNextAddress->AddressType;
if(NULL == pConnection->pTdiObjects)
{
//
// If there is no TDI object, then grab one for this connect
// attempt
//
pTdiObjects = UcpPopTdiObject(AddressType);
}
else
{
//
// If we are here, we have a old TDI object that failed
// a connection attempt. This could be because the old one
// was in TIME_WAIT (Failed with STATUS_ADDRESS_ALREADY_EXISTS)
//
// We have to free the old one & allocate a new one. Since
// both free and allocate have to happen at Passive IRQL,
// we'll do it after we release the lock (below).
pTdiObjects = NULL;
}
if(NULL == pTdiObjects)
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
//
// If there is an old one, free it.
//
if(NULL != pConnection->pTdiObjects)
{
FreeAddressType = pConnection->pTdiObjects->ConnectionType;
pConnection->pTdiObjects->pConnection = NULL;
UcpPushTdiObject(pConnection->pTdiObjects, FreeAddressType);
pConnection->pTdiObjects = NULL;
}
//
// Allocate a new one.
//
Status = UcpAllocateTdiObject(
&pTdiObjects,
AddressType
);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
ASSERT(pConnection->ConnectionState ==
UcConnectStateConnectPending);
if(!NT_SUCCESS(Status))
{
ASSERT(Status != STATUS_ADDRESS_ALREADY_EXISTS);
pConnection->ConnectionState = UcConnectStateConnectCleanup;
goto Begin;
}
pConnection->Flags |= CLIENT_CONN_FLAG_TDI_ALLOCATE;
}
pConnection->pTdiObjects = pTdiObjects;
pTdiObjects->pConnection = pConnection;
//
// The address information in the connection has been filled out.
// Reference the connection, and call the appropriate connect
// routine.
//
REFERENCE_CLIENT_CONNECTION(pConnection);
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_BEGIN_CONNECT,
pConnection,
UlongToPtr(pConnection->ConnectionStatus),
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
//
// Fill out the current TDI address & go to the next one.
//
ASSERT(pConnection->NextAddressCount <
pServInfo->pTransportAddress->TAAddressCount);
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
Status = UcClientConnect(
pConnection,
pConnection->pTdiObjects->pIrp
);
if (STATUS_PENDING != Status)
{
UcRestartClientConnect(pConnection, Status);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
//
// In the normal case, UcpConnectComplete calls
// UcRestartClientConnect, does the de-ref & kicks off
// the connection state machine.
//
// Since we are calling UcRestartClientConnect directly,
// we should deref.
DEREFERENCE_CLIENT_CONNECTION(pConnection);
goto Begin;
}
}
else
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
break;
case UcConnectStateConnectPending:
case UcConnectStateProxySslConnect:
case UcConnectStateConnectCleanupBegin:
case UcConnectStateDisconnectIndicatedPending:
case UcConnectStateDisconnectPending:
case UcConnectStateDisconnectComplete:
case UcConnectStateAbortPending:
//
// We have already issued a connect, we don't have to do anything
// here.
//
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
break;
case UcConnectStateConnectComplete:
//
// The TCP has connected.
//
if(!IsListEmpty(&pConnection->PendingRequestList))
{
if(pConnection->Flags & CLIENT_CONN_FLAG_PROXY_SSL_CONNECTION)
{
PUC_HTTP_REQUEST pConnectRequest;
//
// We are going through a proxy. We need to send
// a CONNECT verb.
//
pEntry = pConnection->PendingRequestList.Flink;
pHeadRequest = CONTAINING_RECORD(pEntry,
UC_HTTP_REQUEST,
Linkage);
pConnection->ConnectionState = UcConnectStateProxySslConnect;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
pConnectRequest =
UcBuildConnectVerbRequest(pConnection, pHeadRequest);
if(pConnectRequest == NULL)
{
//
// The CONNECT verb failed, we can't do much so we'll
// acquire the lock & fail it.
//
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
pConnection->ConnectionState = UcConnectStateConnectCleanup;
goto Cleanup;
}
else
{
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
REFERENCE_CLIENT_CONNECTION(pConnection);
InsertHeadList(&pConnection->SentRequestList,
&pConnectRequest->Linkage);
ASSERT(pConnection->ConnectionState ==
UcConnectStateProxySslConnect);
pConnectRequest->RequestState = UcRequestStateSent;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
//
// go ahead & issue the request.
//
ASSERT(pConnectRequest->RequestConnectionClose == FALSE);
Status = UcSendData(pConnection,
pConnectRequest->pMdlHead,
pConnectRequest->BytesBuffered,
&UcRestartMdlSend,
(PVOID) pConnectRequest,
pConnectRequest->RequestIRP,
TRUE);
if(STATUS_PENDING != Status)
{
UcRestartMdlSend(pConnectRequest, Status, 0);
}
}
}
else if(pConnection->FilterInfo.pFilterChannel)
{
pConnection->ConnectionState =
UcConnectStatePerformingSslHandshake;
pConnection->SslState = UcSslStateConnectionDelivered;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
UlDeliverConnectionToFilter(
&pConnection->FilterInfo,
NULL,
0,
&TakenLength
);
ASSERT(TakenLength == 0);
}
else
{
pConnection->ConnectionState = UcConnectStateConnectReady;
goto IssueRequests;
}
}
else
{
//
// There are no requests that need to be sent out, let's
// remain in this state.
//
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
break;
case UcConnectStateProxySslConnectComplete:
if(!IsListEmpty(&pConnection->PendingRequestList))
{
pConnection->ConnectionState =
UcConnectStatePerformingSslHandshake;
pConnection->SslState = UcSslStateConnectionDelivered;
ASSERT(pConnection->FilterInfo.pFilterChannel);
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
UlDeliverConnectionToFilter(
&pConnection->FilterInfo,
NULL,
0,
&TakenLength
);
ASSERT(TakenLength == 0);
}
else
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
break;
case UcConnectStateConnectReady:
IssueRequests:
ASSERT(pConnection->ConnectionState == UcConnectStateConnectReady);
// It's connected. If no one is using the connection to write right
// now, and either the remote server supports pipeling and this
// request does also or the sent request list is empty, go ahead and
// send it.
pConnection->Flags |= CLIENT_CONN_FLAG_CONNECT_READY;
if ( !IsListEmpty(&pConnection->PendingRequestList) &&
!(pConnection->Flags & CLIENT_CONN_FLAG_SEND_BUSY) &&
UcpCheckForPipelining(pConnection)
)
{
// It's OK to send now.
UcIssueRequests(pConnection, OldIrql);
}
else
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
break;
case UcConnectStateIssueFilterClose:
pConnection->ConnectionState = UcConnectStateDisconnectPending;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
UlFilterCloseHandler(
&pConnection->FilterInfo,
NULL,
NULL
);
break;
case UcConnectStateIssueFilterDisconnect:
pConnection->ConnectionState = UcConnectStateDisconnectIndicatedPending;
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
UlFilterDisconnectHandler(&pConnection->FilterInfo);
break;
case UcConnectStatePerformingSslHandshake:
//
// Perform Ssl handshake.
//
if (pConnection->SslState == UcSslStateServerCertReceived)
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
if(UcpCompareServerCert(pConnection))
{
// Okay to move the connection state machine forward.
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
goto Begin;
}
}
else
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
}
break;
default:
ASSERT(!"Invalid Connection state");
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
break;
}
ASSERT(!UlDbgSpinLockOwned(&pConnection->SpinLock));
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_STATE_LEAVE,
pConnection,
UlongToPtr(pConnection->Flags),
UlongToPtr(pConnection->ConnectionState),
0
);
}
/***************************************************************************++
Routine Description:
Initializes a UC_CLIENT_CONNECTION for use.
Arguments:
pConnection - Pointer to the UL_CONNECTION to initialize.
SecureConnection - TRUE if this connection is for a secure endpoint.
--***************************************************************************/
NTSTATUS
UcpInitializeConnection(
IN PUC_CLIENT_CONNECTION pConnection,
IN PUC_PROCESS_SERVER_INFORMATION pInfo
)
{
NTSTATUS Status;
PUL_FILTER_CHANNEL pChannel;
//
// Initialization.
//
pConnection->MergeIndication.pBuffer = NULL;
pConnection->MergeIndication.BytesWritten = 0;
pConnection->MergeIndication.BytesAvailable = 0;
pConnection->MergeIndication.BytesAllocated = 0;
pConnection->NextAddressCount = 0;
pConnection->pNextAddress = pInfo->pTransportAddress->Address;
if(pInfo->bSecure)
{
pChannel = UxRetrieveClientFilterChannel(pInfo->pProcess);
if(!pChannel)
{
return STATUS_NO_TRACKING_SERVICE;
}
if(pInfo->bProxy)
{
pConnection->Flags |= CLIENT_CONN_FLAG_PROXY_SSL_CONNECTION;
}
else
{
pConnection->Flags &= ~CLIENT_CONN_FLAG_PROXY_SSL_CONNECTION;
}
}
else
{
pChannel = NULL;
pConnection->Flags &= ~CLIENT_CONN_FLAG_PROXY_SSL_CONNECTION;
}
Status = UxInitializeFilterConnection(
&pConnection->FilterInfo,
pChannel,
pInfo->bSecure,
&UcReferenceClientConnection,
&UcDereferenceClientConnection,
&UcCloseRawFilterConnection,
&UcpSendRawData,
&UcpReceiveRawData,
&UcHandleResponse,
&UcComputeHttpRawConnectionLength,
&UcGenerateHttpRawConnectionInfo,
&UcServerCertificateInstalled,
&UcDisconnectRawFilterConnection,
NULL, // Listen Context
pConnection
);
if(Status != STATUS_SUCCESS)
{
if(pChannel)
{
//
// Undo the Retrieve
//
DEREFERENCE_FILTER_CHANNEL(pChannel);
}
}
return Status;
}
/***************************************************************************++
Routine Description:
Opens the TDI connection & address objects, called from connection init
code.
Arguments:
pTdi - a pointer to TDI Objects
Return Value:
NTSTATUS - STATUS_SUCCESS if IO should continue processing this
IRP, STATUS_MORE_PROCESSING_REQUIRED if IO should stop processing
this IRP.
--***************************************************************************/
NTSTATUS
UcpOpenTdiObjects(
IN PUC_TDI_OBJECTS pTdi
)
{
USHORT AddressType;
NTSTATUS status;
AddressType = pTdi->ConnectionType;
//
// First, open the TDI connection object for this connection.
//
status = UxOpenTdiConnectionObject(
AddressType,
(CONNECTION_CONTEXT)pTdi,
&pTdi->ConnectionObject
);
if (!NT_SUCCESS(status))
{
return status;
}
//
// Now open an address object for this connection.
//
status = UxOpenTdiAddressObject(
G_LOCAL_ADDRESS(AddressType),
G_LOCAL_ADDRESS_LENGTH(AddressType),
&pTdi->AddressObject
);
if (!NT_SUCCESS(status))
{
UxCloseTdiObject(&pTdi->ConnectionObject);
return status;
}
else
{
//
// Hook up a receive handler.
//
status = UxSetEventHandler(
&pTdi->AddressObject,
TDI_EVENT_RECEIVE,
(ULONG_PTR) &UcpTdiReceiveHandler,
pTdi
);
}
if(!NT_SUCCESS(status))
{
UxCloseTdiObject(&pTdi->ConnectionObject);
UxCloseTdiObject(&pTdi->AddressObject);
return status;
}
else
{
//
// Hook up a Disconnect handler.
//
status = UxSetEventHandler(
&pTdi->AddressObject,
TDI_EVENT_DISCONNECT,
(ULONG_PTR) &UcpTdiDisconnectHandler,
pTdi
);
}
if(!NT_SUCCESS(status))
{
UxCloseTdiObject(&pTdi->ConnectionObject);
UxCloseTdiObject(&pTdi->AddressObject);
return status;
}
return status;
}
/***************************************************************************++
Routine Description:
Allocates a TDI object, which contains a AO & CO. This routine is called
when we don't have any TDI objects in the pool. associate it with a
local address.
Arguments:
ppTdiObjects - A pointer to the TDI object.
AddressType - IPv4 or IPv6
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
NTSTATUS
UcpAllocateTdiObject(
OUT PUC_TDI_OBJECTS *ppTdiObjects,
IN USHORT AddressType
)
{
PUC_TDI_OBJECTS pTdiObjects;
NTSTATUS status;
PUX_TDI_OBJECT pTdiObject;
KEVENT Event;
PIRP pIrp;
IO_STATUS_BLOCK ioStatusBlock;
PAGED_CODE();
*ppTdiObjects = NULL;
//
// Allocate the pool for the connection structure.
//
pTdiObjects = UL_ALLOCATE_STRUCT(
NonPagedPool,
UC_TDI_OBJECTS,
UC_TDI_OBJECTS_POOL_TAG
);
if (pTdiObjects == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
pTdiObjects->ConnectionType = AddressType;
pTdiObjects->TdiInfo.UserDataLength = 0;
pTdiObjects->TdiInfo.UserData = NULL;
pTdiObjects->TdiInfo.OptionsLength = 0;
pTdiObjects->TdiInfo.Options = NULL;
pTdiObjects->pConnection = NULL;
//
// Open the TDI address & connection objects. We need one AO per connection
// as we will have to open multiple TCP connections to the same server.
//
if((status = UcpOpenTdiObjects(pTdiObjects)) != STATUS_SUCCESS)
{
UL_FREE_POOL(pTdiObjects, UC_TDI_OBJECTS_POOL_TAG);
return status;
}
//
// Allocate an IRP for calling into TDI (e.g. Disconnects, Connects, etc)
//
pTdiObject = &pTdiObjects->ConnectionObject;
pTdiObjects->pIrp = UlAllocateIrp(
pTdiObject->pDeviceObject->StackSize,
FALSE
);
if(!pTdiObjects->pIrp)
{
UcpFreeTdiObject(pTdiObjects);
return STATUS_INSUFFICIENT_RESOURCES;
}
//
// Init the IrpContext.
//
pTdiObjects->IrpContext.Signature = UL_IRP_CONTEXT_SIGNATURE;
//
// Now, associate the Address Object with the Connection Object.
//
KeInitializeEvent(&Event, SynchronizationEvent, FALSE);
pIrp = TdiBuildInternalDeviceControlIrp(
TDI_ASSOCIATE_ADDRESS,
pTdiObjects->ConnectionObject.pDeviceObject,
pTdiObjects->ConnectionObject.pFileObject,
&Event,
&ioStatusBlock
);
if (pIrp != NULL)
{
TdiBuildAssociateAddress(
pIrp, // IRP
pTdiObjects->ConnectionObject.pDeviceObject, // Conn. device object.
pTdiObjects->ConnectionObject.pFileObject, // Conn. File object.
NULL, // Completion routine
NULL, // Context
pTdiObjects->AddressObject.Handle // Address obj handle.
);
//
// We don't want to call UlCallDriver, since we did not allocate this
// IRP using UL.
//
status = IoCallDriver(
pTdiObjects->ConnectionObject.pDeviceObject,
pIrp
);
// If it didn't complete, wait for it.
if (status == STATUS_PENDING)
{
status = KeWaitForSingleObject(
&Event,
Executive,
KernelMode,
FALSE,
NULL
);
ASSERT( status == STATUS_SUCCESS);
status = ioStatusBlock.Status;
}
}
else
{
status = STATUS_INSUFFICIENT_RESOURCES;
}
if(!NT_SUCCESS(status))
{
UcpFreeTdiObject(pTdiObjects);
return status;
}
*ppTdiObjects = pTdiObjects;
return STATUS_SUCCESS;
}
/***************************************************************************++
Routine Description:
Free's the TDI object to the list.
Arguments:
pTdiObjects - A pointer to the TDI object.
AddressType - IPv4 or IPv6
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
VOID
UcpFreeTdiObject(
IN PUC_TDI_OBJECTS pTdiObjects
)
{
PAGED_CODE();
if(pTdiObjects->pIrp)
{
UlFreeIrp(pTdiObjects->pIrp);
}
UxCloseTdiObject(&pTdiObjects->ConnectionObject);
UxCloseTdiObject(&pTdiObjects->AddressObject);
UL_FREE_POOL(pTdiObjects, UC_TDI_OBJECTS_POOL_TAG);
}
/***************************************************************************++
Routine Description:
Retrieves a TDI object from a list. IF not found, allocates a new one.
Arguments:
ppTdiObjects - A pointer to the TDI object.
AddressType - IPv4 or IPv6
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
PUC_TDI_OBJECTS
UcpPopTdiObject(
IN USHORT AddressType
)
{
PLIST_ENTRY pListEntry;
PUC_TDI_OBJECTS pTdiObjects;
KIRQL OldIrql;
//
// Get a AO/CO pair from the address object list.
//
UlAcquireSpinLock(&G_CLIENT_CONN_SPIN_LOCK(AddressType), &OldIrql);
if(IsListEmpty(&G_CLIENT_TDI_CONNECTION_SLIST_HEAD(AddressType)))
{
pTdiObjects = NULL;
}
else
{
(*G_CLIENT_CONN_LIST_COUNT(AddressType)) --;
pListEntry = RemoveHeadList(
&G_CLIENT_TDI_CONNECTION_SLIST_HEAD(AddressType)
);
pTdiObjects = CONTAINING_RECORD(
pListEntry,
UC_TDI_OBJECTS,
Linkage
);
ASSERT(pTdiObjects->pConnection == NULL);
}
UlReleaseSpinLock(&G_CLIENT_CONN_SPIN_LOCK(AddressType), OldIrql);
return pTdiObjects;
}
/***************************************************************************++
Routine Description:
Free's the TDI object to the list.
Arguments:
pTdiObjects - A pointer to the TDI object.
AddressType - IPv4 or IPv6
Return Value:
NTSTATUS - Completion status.
--***************************************************************************/
VOID
UcpPushTdiObject(
IN PUC_TDI_OBJECTS pTdiObjects,
IN USHORT AddressType
)
{
KIRQL OldIrql;
ASSERT(pTdiObjects->pConnection == NULL);
PAGED_CODE();
UlAcquireSpinLock(&G_CLIENT_CONN_SPIN_LOCK(AddressType), &OldIrql);
if((*G_CLIENT_CONN_LIST_COUNT(AddressType)) < CLIENT_CONN_TDI_LIST_MAX)
{
(*G_CLIENT_CONN_LIST_COUNT(AddressType))++;
InsertTailList(
&G_CLIENT_TDI_CONNECTION_SLIST_HEAD(AddressType),
&pTdiObjects->Linkage
);
UlReleaseSpinLock(&G_CLIENT_CONN_SPIN_LOCK(AddressType), OldIrql);
}
else
{
UlReleaseSpinLock(&G_CLIENT_CONN_SPIN_LOCK(AddressType), OldIrql);
UcpFreeTdiObject(pTdiObjects);
}
}
/***************************************************************************++
Routine Description:
Clears the send or receive busy flag & re-kicks the connection state machine
Arguments:
pConnection - The UC_CLIENT_CONNECTION structure.
Flag - CLIENT_CONN_FLAG_SEND_BUSY or CLIENT_CONN_FLAG_RECV_BUSY
OldIrql - Irql at which lock was acquired.
bCloseConnection - Whether we shoudl close the connection after releasing
lock.
Return Value:
None
--***************************************************************************/
VOID
UcClearConnectionBusyFlag(
IN PUC_CLIENT_CONNECTION pConnection,
IN ULONG Flags,
IN KIRQL OldIrql,
IN BOOLEAN bCloseConnection
)
{
ASSERT( UlDbgSpinLockOwned(&pConnection->SpinLock) );
ASSERT((Flags & CLIENT_CONN_FLAG_SEND_BUSY) ||
(Flags & CLIENT_CONN_FLAG_RECV_BUSY));
ASSERT(pConnection->Flags & Flags);
pConnection->Flags &= ~Flags;
if(pConnection->Flags & CLIENT_CONN_FLAG_CLEANUP_PENDED)
{
//
// The connection got torn down in between. We've pended the
// cleanup let's resume it now.
//
UC_WRITE_TRACE_LOG(
g_pUcTraceLog,
UC_ACTION_CONNECTION_CLEAN_RESUMED,
pConnection,
UlongToPtr(pConnection->ConnectionStatus),
UlongToPtr(pConnection->ConnectionState),
UlongToPtr(pConnection->Flags)
);
ASSERT(pConnection->ConnectionState ==
UcConnectStateConnectCleanupBegin);
pConnection->ConnectionState = UcConnectStateConnectCleanup;
pConnection->Flags &= ~CLIENT_CONN_FLAG_CLEANUP_PENDED;
UcKickOffConnectionStateMachine(
pConnection,
OldIrql,
UcConnectionWorkItem
);
}
else
{
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
if(bCloseConnection)
{
UC_CLOSE_CONNECTION(pConnection,
FALSE,
STATUS_CONNECTION_DISCONNECTED);
}
}
return;
}
/***************************************************************************++
Routine Description:
Attaches captured SSL server certificate to a connection.
Called with the pConnection->FilterConnLock held. The connection is
assumed to be in the connected state.
Arguments:
pConnection - the connection that gets the info
pServerCertInfo - input server cert info
--***************************************************************************/
NTSTATUS
UcAddServerCertInfoToConnection(
IN PUX_FILTER_CONNECTION pConnection,
IN PHTTP_SSL_SERVER_CERT_INFO pServerCertInfo
)
{
NTSTATUS Status;
PUC_CLIENT_CONNECTION pClientConn;
//
// Sanity check.
//
ASSERT(IS_VALID_FILTER_CONNECTION(pConnection));
ASSERT(pServerCertInfo);
ASSERT(UlDbgSpinLockOwned(&pConnection->FilterConnLock));
ASSERT(pConnection->ConnState == UlFilterConnStateConnected);
//
// Initialize local variables
//
Status = STATUS_INVALID_PARAMETER;
//
// Get client connection
//
pClientConn = (PUC_CLIENT_CONNECTION)pConnection->pConnectionContext;
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pClientConn));
//
// We are already at DPC, so aquire spin lock at DPC
// BUGBUG: deadlock? (acquiring filter lock followed by connection lock)
// (Is there a place we acquire connection lock before filter lock?
//
UlAcquireSpinLockAtDpcLevel(&pClientConn->SpinLock);
//
// The server certinfo can be passed only during initial handshake or
// after receiving ssl renegotiate from the server - in which case
// the connection must be ready (for a request to be sent out on it)
//
if (pServerCertInfo->Status == SEC_E_OK)
{
// Did a renegotiation happen?
if (pClientConn->ConnectionState == UcConnectStateConnectReady)
{
// Renegotiation must yield the same server certificate
if (!UC_COMPARE_CERT_HASH(pServerCertInfo,
&pClientConn->ServerCertInfo))
{
goto quit;
}
}
else if (pClientConn->ConnectionState !=
UcConnectStatePerformingSslHandshake ||
pClientConn->SslState != UcSslStateConnectionDelivered)
{
goto quit;
}
}
else
{
// BUGBUG: handle the error case more gracefully!
goto quit;
}
// Go back to ssl handshake state
pClientConn->ConnectionState = UcConnectStatePerformingSslHandshake;
pClientConn->SslState = UcSslStateServerCertReceived;
//
// Before overwriting ServerCertInfo, make sure it does not
// contain any serialized blobs or Issuer List
//
ASSERT(pClientConn->ServerCertInfo.Cert.pSerializedCert == NULL);
ASSERT(pClientConn->ServerCertInfo.Cert.pSerializedCertStore == NULL);
ASSERT(pClientConn->ServerCertInfo.IssuerInfo.pIssuerList == NULL);
RtlCopyMemory(&pClientConn->ServerCertInfo,
pServerCertInfo,
sizeof(pClientConn->ServerCertInfo));
Status = STATUS_SUCCESS;
quit:
UlReleaseSpinLockFromDpcLevel(&pClientConn->SpinLock);
if (!NT_SUCCESS(Status))
{
// An error occured.
// Free serialized server certificate if any
UC_FREE_SERIALIZED_CERT(pServerCertInfo,
pClientConn->pServerInfo->pProcess);
// Free issuer list if any
UC_FREE_CERT_ISSUER_LIST(pServerCertInfo,
pClientConn->pServerInfo->pProcess);
}
return Status;
}
/**************************************************************************++
Routine Description:
This routine fails
Arguments:
pConnection - Pointer to client connection.
Return Value:
TRUE - A request was failed.
FALSE - Could not fail arequest.
--**************************************************************************/
PUC_HTTP_REQUEST
UcpFindRequestToFail(
PUC_CLIENT_CONNECTION pConnection
)
{
PUC_HTTP_REQUEST pRequest = NULL;
PLIST_ENTRY pListEntry;
PIRP pIrp;
//
// Sanity checks.
//
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
ASSERT(UlDbgSpinLockOwned(&pConnection->SpinLock));
//
// Try to fail a pending request. Start searching from the head of the
// pending request list.
//
for (pListEntry = pConnection->PendingRequestList.Flink;
pListEntry != &pConnection->PendingRequestList;
pListEntry = pListEntry->Flink)
{
pRequest = CONTAINING_RECORD(pConnection->PendingRequestList.Flink,
UC_HTTP_REQUEST,
Linkage);
ASSERT(UC_IS_VALID_HTTP_REQUEST(pRequest));
ASSERT(pRequest->RequestState == UcRequestStateCaptured);
pIrp = UcPrepareRequestIrp(pRequest, STATUS_RETRY);
if (pIrp)
{
// Prepared the request IRP for completion. Now complete it.
UlCompleteRequest(pIrp, 0);
break;
}
// Set to NULL so that it not returned.
pRequest = NULL;
}
return pRequest;
}
/***************************************************************************++
Routine Description:
Compares a server certificate present on a connection to a server
certificate on a server context.
Arguments:
pConnection - Client connection
Return Value:
TRUE - Continue sending requests.
FALSE - Do not send requests.
--***************************************************************************/
BOOLEAN
UcpCompareServerCert(
IN PUC_CLIENT_CONNECTION pConnection
)
{
BOOLEAN action = FALSE;
KIRQL OldIrql;
PUC_PROCESS_SERVER_INFORMATION pServInfo;
PUC_HTTP_REQUEST pRequest = NULL;
// Sanity check.
ASSERT(UC_IS_VALID_CLIENT_CONNECTION(pConnection));
//
// Retrieve server information from the connection.
//
pServInfo = pConnection->pServerInfo;
ASSERT(IS_VALID_SERVER_INFORMATION(pServInfo));
//
// Acquire the server information push lock followed by the
// connection spinlock.
//
UlAcquirePushLockExclusive(&pServInfo->PushLock);
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
//
// Make sure the connection is still in ssl handshake state
// (This check is needed since we released the connection spinlock
// before calling this function.)
//
if (pConnection->ConnectionState != UcConnectStatePerformingSslHandshake
|| pConnection->SslState != UcSslStateServerCertReceived)
{
action = FALSE;
goto Release;
}
//
// Cert::Flags is used to optimize certain cases.
// If HTTP_SSL_SERIALIZED_CERT_PRESENT is not set,
// the server certificate was accepted and was not
// stored in the connection.
//
if (!(pConnection->ServerCertInfo.Cert.Flags &
HTTP_SSL_SERIALIZED_CERT_PRESENT))
{
// Okay to send request on this connection.
action = TRUE;
goto Release;
}
//
// Unoptimized cases.
//
//
// For Ignore and Automatic modes, no validation is needed.
// If there is not server cert info on pServInfo, copy now.
//
if (pServInfo->ServerCertValidation ==HttpSslServerCertValidationIgnore ||
pServInfo->ServerCertValidation ==HttpSslServerCertValidationAutomatic)
{
if (pServInfo->ServerCertInfoState ==
HttpSslServerCertInfoStateNotPresent)
{
// Update the state of server cert info on servinfo.
pServInfo->ServerCertInfoState =
HttpSslServerCertInfoStateNotValidated;
// Move Cert Issuer List from connection to server info.
UC_MOVE_CERT_ISSUER_LIST(pServInfo, pConnection);
// Move certificate from connection to servinfo.
UC_MOVE_SERIALIZED_CERT(pServInfo, pConnection);
}
// Okay to send requests on this connection.
action = TRUE;
goto Release;
}
//
// Take action based on the server cert info state in pServInfo.
//
switch (pServInfo->ServerCertInfoState)
{
case HttpSslServerCertInfoStateNotPresent:
NotPresent:
ASSERT(pServInfo->ServerCertValidation ==
HttpSslServerCertValidationManual ||
pServInfo->ServerCertValidation ==
HttpSslServerCertValidationManualOnce);
//
// Find a pending request to fail.
//
pRequest = UcpFindRequestToFail(pConnection);
if (pRequest == NULL)
{
//
// We could not find a request to fail.
// Hence, we can't send requests on this pConnection.
//
action = FALSE;
}
else
{
// Update the state of server cert info on servinfo.
pServInfo->ServerCertInfoState =
HttpSslServerCertInfoStateNotValidated;
// Move Cert Issuer List from connection to server info.
UC_MOVE_CERT_ISSUER_LIST(pServInfo, pConnection);
// Move certificate from connection to servinfo.
UC_MOVE_SERIALIZED_CERT(pServInfo, pConnection);
// Update the ssl state on the connection.
pConnection->SslState = UcSslStateValidatingServerCert;
//
// Reference the request so that it doesn't go away
// before we fail it below.
//
UC_REFERENCE_REQUEST(pRequest);
//
// Can't send request on pConnection as we are waiting for
// server certificate validation.
//
action = FALSE;
}
break;
case HttpSslServerCertInfoStateNotValidated:
//
// Server Certificate is already present on servinfo but has not
// been validated.
//
ASSERT(pServInfo->ServerCertValidation ==
HttpSslServerCertValidationManual ||
pServInfo->ServerCertValidation ==
HttpSslServerCertValidationManualOnce);
// Can't send any requests on pConnection right now.
action = FALSE;
break;
case HttpSslServerCertInfoStateValidated:
ASSERT(pServInfo->ServerCertValidation ==
HttpSslServerCertValidationManual ||
pServInfo->ServerCertValidation ==
HttpSslServerCertValidationManualOnce);
if (pServInfo->ServerCertValidation ==
HttpSslServerCertValidationManualOnce)
{
// Is the new certificate same as old one?
if (UC_COMPARE_CERT_HASH(&pServInfo->ServerCertInfo,
&pConnection->ServerCertInfo))
{
// New certificate is same as the old one.
// Just move Cert Issuer List from connection to server info.
UC_MOVE_CERT_ISSUER_LIST(pServInfo, pConnection);
// Okay to send requests on this connection.
action = TRUE;
}
else
{
goto NotPresent;
}
}
else // HttpSslServerCertValidationManual
{
// Treat this case as if the certificate was not present.
goto NotPresent;
}
break;
default:
ASSERT(FALSE);
break;
}
Release:
if (action)
{
//
// Handshake is complete. Requests can be sent out on this connection.
//
pConnection->SslState = UcSslStateHandshakeComplete;
pConnection->ConnectionState = UcConnectStateConnectReady;
//
// Free any certificate and issuers list on this connection.
//
UC_FREE_SERIALIZED_CERT(&pConnection->ServerCertInfo,
pConnection->pServerInfo->pProcess);
UC_FREE_CERT_ISSUER_LIST(&pConnection->ServerCertInfo,
pConnection->pServerInfo->pProcess);
}
//
// Release the connection spinlock and server information pushlock.
//
UlReleaseSpinLock(&pConnection->SpinLock, OldIrql);
UlReleasePushLock(&pServInfo->PushLock);
if (pRequest)
{
UlAcquireSpinLock(&pConnection->SpinLock, &OldIrql);
UcFailRequest(pRequest, STATUS_RETRY, OldIrql);
UC_DEREFERENCE_REQUEST(pRequest);
}
return action;
}