Files
yttrium-virtio-gpu/viosock/sys/Socket.c
T
2021-09-09 10:24:37 +03:00

2708 lines
75 KiB
C

/*
* Socket object functions
*
* Copyright (c) 2019 Virtuozzo International GmbH
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met :
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and / or other materials provided with the distribution.
* 3. Neither the names of the copyright holders nor the names of their contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include "precomp.h"
#include "viosock.h"
#if defined(EVENT_TRACING)
#include "Socket.tmh"
#endif
NTSTATUS
VIOSockBind(
IN WDFREQUEST Request
);
NTSTATUS
VIOSockConnect(
IN WDFREQUEST Request
);
NTSTATUS
VIOSockShutdown(
IN WDFREQUEST Request
);
NTSTATUS
VIOSockListen(
IN WDFREQUEST Request
);
NTSTATUS
VIOSockEnumNetEvents(
IN WDFREQUEST Request,
OUT size_t *pLength
);
NTSTATUS
VIOSockEventSelect(
IN WDFREQUEST Request
);
NTSTATUS
VIOSockGetPeerName(
IN WDFREQUEST Request,
OUT size_t *pLength
);
NTSTATUS
VIOSockGetSockName(
IN WDFREQUEST Request,
OUT size_t *pLength
);
NTSTATUS
VIOSockGetSockOpt(
IN WDFREQUEST Request,
OUT size_t *pLength
);
NTSTATUS
VIOSockSetSockOpt(
IN WDFREQUEST Request
);
NTSTATUS
VIOSockIoctl(
IN WDFREQUEST Request,
OUT size_t *pLength
);
typedef union _VIOSOCK_PENDED_CONTEXT {
WDFFILEOBJECT ParentSocket;
}VIOSOCK_PENDED_CONTEXT, *PVIOSOCK_PENDED_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(VIOSOCK_PENDED_CONTEXT, GetRequestPendedContext);
NTSTATUS
VIOSockAccept(
IN HANDLE hListenSocket,
IN WDFREQUEST Request
);
NTSTATUS
VIOSockCreate(
IN WDFDEVICE WdfDevice,
IN WDFREQUEST Request,
IN WDFFILEOBJECT FileObject
);
EVT_WDF_REQUEST_CANCEL VIOSockPendedRequestCancel;
EVT_WDF_TIMER VIOSockPendedTimerFunc;
#ifdef ALLOC_PRAGMA
#pragma alloc_text (PAGE, VIOSockBoundListInit)
#pragma alloc_text (PAGE, VIOSockConnectedListInit)
#pragma alloc_text (PAGE, VIOSockCreateStub)
#pragma alloc_text (PAGE, VIOSockClose)
#pragma alloc_text (PAGE, VIOSockBind)
#pragma alloc_text (PAGE, VIOSockConnect)
#pragma alloc_text (PAGE, VIOSockShutdown)
#pragma alloc_text (PAGE, VIOSockListen)
#pragma alloc_text (PAGE, VIOSockEnumNetEvents)
#pragma alloc_text (PAGE, VIOSockEventSelect)
#pragma alloc_text (PAGE, VIOSockGetPeerName)
#pragma alloc_text (PAGE, VIOSockGetSockName)
#pragma alloc_text (PAGE, VIOSockGetSockOpt)
#pragma alloc_text (PAGE, VIOSockSetSockOpt)
#pragma alloc_text (PAGE, VIOSockIoctl)
#pragma alloc_text (PAGE, VIOSockAccept)
#pragma alloc_text (PAGE, VIOSockCreate)
#pragma alloc_text (PAGE, VIOSockDeviceControl)
#endif
//////////////////////////////////////////////////////////////////////////
NTSTATUS
VIOSockBoundListInit(
IN WDFDEVICE hDevice
)
{
NTSTATUS status;
PDEVICE_CONTEXT pContext = GetDeviceContext(hDevice);
WDF_OBJECT_ATTRIBUTES collectionAttributes;
PAGED_CODE();
// Create collection for socket objects
WDF_OBJECT_ATTRIBUTES_INIT(&collectionAttributes);
collectionAttributes.ParentObject = hDevice;
status = WdfCollectionCreate(&collectionAttributes, &pContext->BoundList);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfCollectionCreate failed - 0x%x\n", status);
}
else
{
WDF_OBJECT_ATTRIBUTES lockAttributes;
WDF_OBJECT_ATTRIBUTES_INIT(&lockAttributes);
lockAttributes.ParentObject = hDevice;
status = WdfSpinLockCreate(&lockAttributes, &pContext->BoundLock);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfSpinLockCreate failed - 0x%x\n", status);
}
}
return status;
}
NTSTATUS
VIOSockBoundAdd(
IN PSOCKET_CONTEXT pSocket,
IN ULONG32 svm_port
)
{
PDEVICE_CONTEXT pContext = GetDeviceContextFromSocket(pSocket);
static ULONG32 uSvmPort;
ULONG uSeed = (ULONG)(ULONG_PTR)pSocket;
NTSTATUS status = STATUS_SUCCESS;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
if (svm_port == VMADDR_PORT_ANY)
{
BOOLEAN bFound = FALSE;
ULONG i;
for (i = 0; i < MAX_PORT_RETRIES; ++i)
{
if (!uSvmPort)
uSvmPort = RtlRandomEx((PULONG)&uSeed);
if (uSvmPort == VMADDR_PORT_ANY)
{
uSvmPort = 0;
continue;
}
if (uSvmPort <= LAST_RESERVED_PORT)
uSvmPort += LAST_RESERVED_PORT + 1;
svm_port = uSvmPort++;
WdfSpinLockAcquire(pContext->BoundLock);
if (!VIOSockBoundFindByPortUnlocked(pContext, svm_port))
{
bFound = TRUE;
pSocket->src_port = svm_port;
status = WdfCollectionAdd(pContext->BoundList, pSocket->ThisSocket);
}
WdfSpinLockRelease(pContext->BoundLock);
if (bFound)
break;
}
if (!bFound)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "No ports available\n");
status = STATUS_INSUFFICIENT_RESOURCES;
}
}
else
{
WdfSpinLockAcquire(pContext->BoundLock);
if (VIOSockBoundFindByPortUnlocked(pContext, svm_port))
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_SOCKET, "Local port %u is already in use\n", svm_port);
status = STATUS_ADDRESS_ALREADY_ASSOCIATED;
}
else
{
pSocket->src_port = svm_port;
status = WdfCollectionAdd(pContext->BoundList, pSocket->ThisSocket);
}
WdfSpinLockRelease(pContext->BoundLock);
}
if (NT_SUCCESS(status))
VIOSockSetFlag(pSocket, SOCK_BOUND);
return status;
}
__inline
VOID
VIOSockBoundRemove(
IN PSOCKET_CONTEXT pSocket
)
{
PDEVICE_CONTEXT pContext = GetDeviceContext(WdfFileObjectGetDevice(pSocket->ThisSocket));
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
WdfSpinLockAcquire(pContext->BoundLock);
if (VIOSockResetFlag(pSocket, SOCK_BOUND))
{
WdfCollectionRemove(pContext->BoundList, pSocket->ThisSocket);
}
WdfSpinLockRelease(pContext->BoundLock);
}
typedef
BOOLEAN
(*PSOCKET_CALLBACK)(
IN PSOCKET_CONTEXT pSocket,
IN PVOID pCallbackContext
);
PSOCKET_CONTEXT
VIOSockBoundEnumUnlocked(
IN PDEVICE_CONTEXT pContext,
IN PSOCKET_CALLBACK pEnumCallback,
IN PVOID pCallbackContext
)
{
PSOCKET_CONTEXT pSocket = NULL;
ULONG i, ItemCount;
ItemCount = WdfCollectionGetCount(pContext->BoundList);
for (i = 0; i < ItemCount; ++i)
{
PSOCKET_CONTEXT pCurrentSocket = GetSocketContext(WdfCollectionGetItem(pContext->BoundList, i));
if (pEnumCallback(pCurrentSocket, pCallbackContext))
{
pSocket = pCurrentSocket;
break;
}
}
return pSocket;
}
PSOCKET_CONTEXT
VIOSockBoundEnum(
IN PDEVICE_CONTEXT pContext,
IN PSOCKET_CALLBACK pEnumCallback,
IN PVOID pCallbackContext
)
{
PSOCKET_CONTEXT pSocket;
WdfSpinLockAcquire(pContext->BoundLock);
pSocket = VIOSockBoundEnumUnlocked(pContext, pEnumCallback, pCallbackContext);
WdfSpinLockRelease(pContext->BoundLock);
return pSocket;
}
static
BOOLEAN
VIOSockBoundFindByPortCallback(
IN PSOCKET_CONTEXT pSocket,
IN PVOID pCallbackContext
)
{
return pSocket->src_port == (ULONG32)(ULONG_PTR)pCallbackContext;
}
PSOCKET_CONTEXT
VIOSockBoundFindByPort(
IN PDEVICE_CONTEXT pContext,
IN ULONG32 ulSrcPort
)
{
return VIOSockBoundEnum(pContext, VIOSockBoundFindByPortCallback, (PVOID)ulSrcPort);
}
PSOCKET_CONTEXT
VIOSockBoundFindByPortUnlocked(
IN PDEVICE_CONTEXT pContext,
IN ULONG32 ulSrcPort
)
{
return VIOSockBoundEnumUnlocked(pContext, VIOSockBoundFindByPortCallback, (PVOID)ulSrcPort);
}
static
BOOLEAN
VIOSockFindByFileCallback(
IN PSOCKET_CONTEXT pSocket,
IN PVOID pCallbackContext
)
{
return WdfFileObjectWdmGetFileObject(pSocket->ThisSocket) ==
(PFILE_OBJECT)pCallbackContext;
}
PSOCKET_CONTEXT
VIOSockBoundFindByFile(
IN PDEVICE_CONTEXT pContext,
IN PFILE_OBJECT pFileObject
)
{
return VIOSockBoundEnum(pContext, VIOSockFindByFileCallback, pFileObject);
}
NTSTATUS
VIOSockConnectedListInit(
IN WDFDEVICE hDevice
)
{
NTSTATUS status;
PDEVICE_CONTEXT pContext = GetDeviceContext(hDevice);
WDF_OBJECT_ATTRIBUTES collectionAttributes;
PAGED_CODE();
// Create collection for socket objects
WDF_OBJECT_ATTRIBUTES_INIT(&collectionAttributes);
collectionAttributes.ParentObject = hDevice;
status = WdfCollectionCreate(&collectionAttributes, &pContext->ConnectedList);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfCollectionCreate failed - 0x%x\n", status);
}
else
{
WDF_OBJECT_ATTRIBUTES lockAttributes;
WDF_OBJECT_ATTRIBUTES_INIT(&lockAttributes);
lockAttributes.ParentObject = hDevice;
status = WdfSpinLockCreate(&lockAttributes, &pContext->ConnectedLock);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_INIT, "WdfSpinLockCreate failed - 0x%x\n", status);
}
}
return status;
}
__inline
NTSTATUS
VIOSockConnectedAdd(
IN PSOCKET_CONTEXT pSocket
)
{
PDEVICE_CONTEXT pContext = GetDeviceContext(WdfFileObjectGetDevice(pSocket->ThisSocket));
NTSTATUS status;
WdfSpinLockAcquire(pContext->ConnectedLock);
status = WdfCollectionAdd(pContext->ConnectedList, pSocket->ThisSocket);
WdfSpinLockRelease(pContext->ConnectedLock);
return status;
}
__inline
VOID
VIOSockConnectedRemove(
IN PSOCKET_CONTEXT pSocket
)
{
PDEVICE_CONTEXT pContext = GetDeviceContext(WdfFileObjectGetDevice(pSocket->ThisSocket));
WdfSpinLockAcquire(pContext->ConnectedLock);
WdfCollectionRemove(pContext->ConnectedList, pSocket->ThisSocket);
WdfSpinLockRelease(pContext->ConnectedLock);
}
PSOCKET_CONTEXT
VIOSockConnectedEnum(
IN PDEVICE_CONTEXT pContext,
IN PSOCKET_CALLBACK pEnumCallback,
IN PVOID pCallbackContext
)
{
PSOCKET_CONTEXT pSocket = NULL;
ULONG i, ItemCount;
WdfSpinLockAcquire(pContext->ConnectedLock);
ItemCount = WdfCollectionGetCount(pContext->ConnectedList);
for (i = 0; i < ItemCount; ++i)
{
PSOCKET_CONTEXT pCurrentSocket = GetSocketContext(WdfCollectionGetItem(pContext->ConnectedList, i));
if (pEnumCallback(pCurrentSocket, pCallbackContext))
{
pSocket = pCurrentSocket;
break;
}
}
WdfSpinLockRelease(pContext->ConnectedLock);
return pSocket;
}
static
BOOLEAN
VIOSockConnectedFindByRxPktCallback(
IN PSOCKET_CONTEXT pSocket,
IN PVOID pCallbackContext
)
{
PVIRTIO_VSOCK_HDR pPkt = (PVIRTIO_VSOCK_HDR)pCallbackContext;
return (pPkt->src_cid == pSocket->dst_cid &&
pPkt->src_port == pSocket->dst_port &&
pPkt->dst_port == pSocket->src_port);
}
PSOCKET_CONTEXT
VIOSockConnectedFindByRxPkt(
IN PDEVICE_CONTEXT pContext,
IN PVIRTIO_VSOCK_HDR pPkt
)
{
return VIOSockConnectedEnum(pContext, VIOSockConnectedFindByRxPktCallback, pPkt);
}
PSOCKET_CONTEXT
VIOSockConnectedFindByFile(
IN PDEVICE_CONTEXT pContext,
IN PFILE_OBJECT pFileObject
)
{
return VIOSockConnectedEnum(pContext, VIOSockFindByFileCallback, pFileObject);
}
_Requires_lock_held_(pSocket->StateLock)
VIOSOCK_STATE
VIOSockStateSet(
IN PSOCKET_CONTEXT pSocket,
IN VIOSOCK_STATE NewState
)
{
VIOSOCK_STATE PrevState;
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_SOCKET, "Socket %d, %!State! --> %!State!\n",
pSocket->SocketId, pSocket->State, NewState);
PrevState = InterlockedExchange((PLONG)&pSocket->State, NewState);
//add/remove socket to/from connected list
if (PrevState != NewState && !IsLoopbackSocket(pSocket))
{
if (NewState == VIOSOCK_STATE_CLOSE &&
(PrevState == VIOSOCK_STATE_CONNECTED ||
PrevState == VIOSOCK_STATE_CLOSING))
{
VIOSockConnectedRemove(pSocket);
}
else if (NewState == VIOSOCK_STATE_CONNECTED)
{
ASSERT(PrevState == VIOSOCK_STATE_CONNECTING || PrevState == VIOSOCK_STATE_CLOSE);
if (!NT_SUCCESS(VIOSockConnectedAdd(pSocket)))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "VIOSockConnectedAdd failed\n");
// InterlockedExchange((PLONG)&pSocket->State, VIOSOCK_STATE_CLOSE);
}
}
}
return PrevState;
}
_Requires_lock_not_held_(pSocket->StateLock)
VIOSOCK_STATE
VIOSockStateSetLocked(
IN PSOCKET_CONTEXT pSocket,
IN VIOSOCK_STATE NewState
)
{
VIOSOCK_STATE PrevState;
WdfSpinLockAcquire(pSocket->StateLock);
PrevState = VIOSockStateSet(pSocket, NewState);
WdfSpinLockRelease(pSocket->StateLock);
return PrevState;
}
__inline
BOOLEAN
VIOSockIsBound(
IN PSOCKET_CONTEXT pSocket
)
{
return pSocket->src_port != VMADDR_PORT_ANY;
}
//////////////////////////////////////////////////////////////////////////
static
VOID
VIOSockPendedRequestCancel(
IN WDFREQUEST Request
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
WDFFILEOBJECT ParentSocket = WDF_NO_HANDLE;
PVIOSOCK_PENDED_CONTEXT pRequest = GetRequestPendedContext(Request);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s, Request: %p\n", __FUNCTION__, Request);
if (pRequest)
ParentSocket = pRequest->ParentSocket;
if (ParentSocket != WDF_NO_HANDLE)
pSocket = GetSocketContext(ParentSocket);
ASSERT(pSocket && pSocket->PendedRequest == Request);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "Socket %d\n", pSocket->SocketId);
WdfSpinLockAcquire(pSocket->RxLock);
VIOSockTimerCancel(&pSocket->PendedTimer);
WdfObjectDereference(Request);
pSocket->PendedRequest = WDF_NO_HANDLE;
WdfSpinLockRelease(pSocket->RxLock);
WdfRequestComplete(Request, STATUS_CANCELLED);
}
//////////////////////////////////////////////////////////////////////////
VOID
VIOSockPendedTimerFunc(
WDFTIMER Timer
)
{
PSOCKET_CONTEXT pSocket = GetSocketContext(WdfTimerGetParentObject(Timer));
WDFREQUEST PendedRequest;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
// ASSERT(VIOSockStateGet(pSocket) == VIOSOCK_STATE_CONNECTING);
if (NT_SUCCESS(VIOSockPendedRequestGetLocked(pSocket, &PendedRequest)) &&
PendedRequest != WDF_NO_HANDLE)
{
WdfRequestComplete(PendedRequest, STATUS_TIMEOUT);
// VIOSockTxCancel(GetDeviceContextFromSocket(pSocket), pSocket, STATUS_TIMEOUT);
}
}
_Requires_lock_held_(pSocket->RxLock)
NTSTATUS
VIOSockPendedRequestSetEx(
IN PSOCKET_CONTEXT pSocket,
IN WDFREQUEST Request,
IN LONGLONG Timeout,
IN BOOLEAN Resume
)
{
NTSTATUS status;
PSOCKET_CONTEXT pRequestSocket = GetSocketContextFromRequest(Request);
PVIOSOCK_PENDED_CONTEXT pRequest = NULL;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s, Socket %d, Request %p\n", __FUNCTION__, pSocket->SocketId, Request);
ASSERT(pSocket && pRequestSocket);
ASSERT(pSocket->PendedRequest == WDF_NO_HANDLE);
//store parent socket handle
if (pRequestSocket != pSocket)
{
WDF_OBJECT_ATTRIBUTES attributes;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(
&attributes,
VIOSOCK_PENDED_CONTEXT
);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "Linking pended request to parent socket\n");
status = WdfObjectAllocateContext(
Request,
&attributes,
&pRequest
);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "WdfObjectAllocateContext failed: 0x%x\n", status);
return status;
}
ASSERT(pRequest->ParentSocket == WDF_NO_HANDLE || pRequest->ParentSocket == pSocket->ThisSocket);
pRequest->ParentSocket = pSocket->ThisSocket;
}
WdfObjectReference(Request);
pSocket->PendedRequest = Request;
status = WdfRequestMarkCancelableEx(pSocket->PendedRequest, VIOSockPendedRequestCancel);
if (!NT_SUCCESS(status))
{
ASSERT(status == STATUS_CANCELLED);
pSocket->PendedRequest = WDF_NO_HANDLE;
WdfObjectDereference(Request);
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Pended request canceled: 0x%x\n", status);
}
else if (Resume)
{
if (!VIOSockTimerResume(&pSocket->PendedTimer))
{
//rollback request pending
if (!NT_SUCCESS(WdfRequestUnmarkCancelable(Request)))
status = STATUS_CANCELLED;
else
status = STATUS_TIMEOUT;
pSocket->PendedRequest = WDF_NO_HANDLE;
WdfObjectDereference(Request);
}
}
else if (Timeout)
{
VIOSockTimerSet(&pSocket->PendedTimer, Timeout);
}
return status;
}
_Requires_lock_held_(pSocket->RxLock)
NTSTATUS
VIOSockPendedRequestGet(
IN PSOCKET_CONTEXT pSocket,
OUT WDFREQUEST *Request
)
{
NTSTATUS status = STATUS_SUCCESS;;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s, Socket %d\n", __FUNCTION__, pSocket->SocketId);
VIOSockTimerSuspend(&pSocket->PendedTimer);
*Request = pSocket->PendedRequest;
if (*Request != WDF_NO_HANDLE)
{
status = WdfRequestUnmarkCancelable(*Request);
ASSERT(NT_SUCCESS(status));
if (!NT_SUCCESS(status))
{
ASSERT(status == STATUS_CANCELLED && pSocket->PendedRequest == WDF_NO_HANDLE);
*Request = WDF_NO_HANDLE; //do not complete canceled request
status = STATUS_CANCELLED;
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Pended request canceled\n");
}
else
{
ASSERT(GetRequestPendedContext(*Request) == NULL || GetRequestPendedContext(*Request)->ParentSocket);
WdfObjectDereference(*Request);
pSocket->PendedRequest = WDF_NO_HANDLE;
}
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, pended request: %p\n", __FUNCTION__, *Request);
return status;
}
//////////////////////////////////////////////////////////////////////////
_Requires_lock_not_held_(pListenSocket->RxLock)
NTSTATUS
VIOSockAcceptEnqueuePkt(
IN PSOCKET_CONTEXT pListenSocket,
IN PVIRTIO_VSOCK_HDR pPkt
)
{
PDEVICE_CONTEXT pContext = GetDeviceContextFromSocket(pListenSocket);
NTSTATUS status;
WDFMEMORY Memory;
LONG lAcceptPended;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
lAcceptPended = InterlockedIncrement(&pListenSocket->AcceptPended);
if (lAcceptPended > pListenSocket->Backlog)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Accept list is full\n");
InterlockedDecrement(&pListenSocket->AcceptPended);
return STATUS_CONNECTION_RESET;
}
//accepted list is empty
if (lAcceptPended == 1)
{
WDFREQUEST PendedRequest;
VIOSockPendedRequestGetLocked(pListenSocket, &PendedRequest);
if (PendedRequest)
{
PSOCKET_CONTEXT pAcceptSocket = GetSocketContextFromRequest(PendedRequest);
InterlockedDecrement(&pListenSocket->AcceptPended);
pAcceptSocket->dst_cid = (ULONG32)pPkt->src_cid;
pAcceptSocket->dst_port = pPkt->src_port;
pAcceptSocket->peer_buf_alloc = pPkt->src_port;
pAcceptSocket->peer_fwd_cnt = pPkt->fwd_cnt;
VIOSockAcceptInitSocket(pAcceptSocket, pListenSocket);
WdfRequestComplete(PendedRequest, STATUS_SUCCESS);
return STATUS_SUCCESS;
}
}
status = WdfMemoryCreateFromLookaside(pContext->AcceptMemoryList,&Memory);
if (NT_SUCCESS(status))
{
PVIOSOCK_ACCEPT_ENTRY pAccept = WdfMemoryGetBuffer(Memory, NULL);
pAccept->Memory = Memory;
pAccept->ConnectSocket = WDF_NO_HANDLE;
pAccept->dst_cid = (ULONG32)pPkt->src_cid;
pAccept->dst_port = pPkt->src_port;
pAccept->peer_buf_alloc = pPkt->src_port;
pAccept->peer_fwd_cnt = pPkt->fwd_cnt;
WdfSpinLockAcquire(pListenSocket->RxLock);
InsertTailList(&pListenSocket->AcceptList, &pAccept->ListEntry);
WdfSpinLockRelease(pListenSocket->RxLock);
VIOSockEventSetBit(pListenSocket, FD_ACCEPT_BIT, STATUS_SUCCESS);
status = STATUS_PENDING;
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "WdfMemoryCreateFromLookaside failed: 0x%x\n", status);
InterlockedDecrement(&pListenSocket->AcceptPended);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s\n", __FUNCTION__);
return status;
}
_Requires_lock_not_held_(pListenSocket->RxLock)
VOID
VIOSockAcceptRemovePkt(
IN PSOCKET_CONTEXT pListenSocket,
IN PVIRTIO_VSOCK_HDR pPkt
)
{
PLIST_ENTRY pCurrentEntry;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
WdfSpinLockAcquire(pListenSocket->RxLock);
for (pCurrentEntry = pListenSocket->AcceptList.Flink;
pCurrentEntry != &pListenSocket->AcceptList;
pCurrentEntry = pCurrentEntry->Flink)
{
PVIOSOCK_ACCEPT_ENTRY pAccept = CONTAINING_RECORD(pCurrentEntry, VIOSOCK_ACCEPT_ENTRY, ListEntry);
if (pAccept->dst_cid == pPkt->src_cid &&
pAccept->dst_port == pPkt->src_port)
{
ASSERT(pAccept->ConnectSocket == WDF_NO_HANDLE);
RemoveEntryList(pCurrentEntry);
InterlockedDecrement(&pListenSocket->AcceptPended);
WdfObjectDelete(pAccept->Memory);
break;
}
}
WdfSpinLockRelease(pListenSocket->RxLock);
}
NTSTATUS
VIOSockAcceptInitSocket(
PSOCKET_CONTEXT pAcceptSocket,
PSOCKET_CONTEXT pListenSocket
)
{
NTSTATUS status = STATUS_SUCCESS;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
pAcceptSocket->type = pListenSocket->type;
pAcceptSocket->src_port = pListenSocket->src_port;
pAcceptSocket->ConnectTimeout = pListenSocket->ConnectTimeout;
pAcceptSocket->BufferMinSize = pListenSocket->BufferMinSize;
pAcceptSocket->BufferMaxSize = pListenSocket->BufferMaxSize;
pAcceptSocket->buf_alloc = pListenSocket->buf_alloc;
VIOSockStateSetLocked(pAcceptSocket, VIOSOCK_STATE_CONNECTED);
status = VIOSockSendResponse(pAcceptSocket);
VIOSockEventSetBit(pListenSocket, FD_ACCEPT_BIT, status);
return status;
}
_Requires_lock_not_held_(pListenSocket->RxLock)
static
BOOLEAN
VIOSockAcceptDequeue(
IN PSOCKET_CONTEXT pListenSocket,
IN PSOCKET_CONTEXT pAcceptSocket,
OUT PBOOLEAN pbSetBit
)
{
BOOLEAN bAccepted = FALSE;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
do
{
PLIST_ENTRY pListEntry;
WdfSpinLockAcquire(pListenSocket->RxLock);
pListEntry = IsListEmpty(&pListenSocket->AcceptList) ? NULL
: RemoveHeadList(&pListenSocket->AcceptList);
WdfSpinLockRelease(pListenSocket->RxLock);
*pbSetBit = FALSE;
if (pListEntry)
{
PVIOSOCK_ACCEPT_ENTRY pAccept = CONTAINING_RECORD(pListEntry, VIOSOCK_ACCEPT_ENTRY, ListEntry);
LONG lAcceptPended = InterlockedDecrement(&pListenSocket->AcceptPended);
//loopback connect
if (pAccept->ConnectSocket != WDF_NO_HANDLE)
{
if (!VIOSockLoopbackAcceptDequeue(pAcceptSocket, pAccept))
{
WdfObjectDelete(pAccept->Memory);
continue;
}
}
*pbSetBit = !!lAcceptPended;
pAcceptSocket->dst_cid = pAccept->dst_cid;
pAcceptSocket->dst_port = pAccept->dst_port;
pAcceptSocket->peer_buf_alloc = pAccept->peer_buf_alloc;
pAcceptSocket->peer_fwd_cnt = pAccept->peer_fwd_cnt;
VIOSockAcceptInitSocket(pAcceptSocket, pListenSocket);
WdfObjectDelete(pAccept->Memory);
bAccepted = TRUE;
}
else
break;
} while (!bAccepted);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, accepted: %d\n", __FUNCTION__, bAccepted);
return bAccepted;
}
_Requires_lock_not_held_(pListenSocket->RxLock)
static
VOID
VIOSockAcceptCleanup(
IN PSOCKET_CONTEXT pListenSocket
)
{
WDFREQUEST PendedRequest;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
WdfSpinLockAcquire(pListenSocket->RxLock);
while (!IsListEmpty(&pListenSocket->AcceptList))
{
PVIOSOCK_ACCEPT_ENTRY pAccept =
CONTAINING_RECORD(RemoveHeadList(&pListenSocket->AcceptList), VIOSOCK_ACCEPT_ENTRY, ListEntry);
if (pAccept->ConnectSocket != WDF_NO_HANDLE)
{
WdfObjectDereference(pAccept->ConnectSocket);
}
WdfObjectDelete(pAccept->Memory);
}
pListenSocket->AcceptPended = 0;
WdfSpinLockRelease(pListenSocket->RxLock);
}
static
NTSTATUS
VIOSockAccept(
IN HANDLE hListenSocket,
IN WDFREQUEST Request
)
{
NTSTATUS status;
PSOCKET_CONTEXT pAcceptSocket = GetSocketContextFromRequest(Request);
PDEVICE_CONTEXT pContext = GetDeviceContextFromSocket(pAcceptSocket);
PFILE_OBJECT pFileObj;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = ObReferenceObjectByHandle(hListenSocket, FILE_READ_ACCESS, *IoFileObjectType,
WdfRequestGetRequestorMode(Request), (PVOID)&pFileObj, NULL);
if (NT_SUCCESS(status))
{
PSOCKET_CONTEXT pListenSocket = VIOSockBoundFindByFile(pContext, pFileObj);
ObDereferenceObject(pFileObj);
if (!pListenSocket)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Listen socket not found\n");
status = STATUS_NOT_SOCKET;
}
else if (VIOSockStateGet(pListenSocket) != VIOSOCK_STATE_LISTEN)
{
status = STATUS_INVALID_PARAMETER;
}
else
{
BOOLEAN bDequeued = FALSE, bSetBit = FALSE;
if (VIOSockIsNonBlocking(pListenSocket))
VIOSockSetFlag(pAcceptSocket, SOCK_NON_BLOCK);
VIOSockEventClearBit(pListenSocket, FD_ACCEPT_BIT);
if (!VIOSockAcceptDequeue(pListenSocket, pAcceptSocket, &bSetBit))
{
if (VIOSockIsNonBlocking(pListenSocket))
status = STATUS_CANT_WAIT;
else
{
if (pListenSocket->PendedRequest == WDF_NO_HANDLE)
status = VIOSockPendedRequestSetLocked(pListenSocket, Request, 0);
else
status = STATUS_DEVICE_BUSY;
if (NT_SUCCESS(status))
status = STATUS_PENDING;
}
}
}
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "ObReferenceObjectByHandle failed: 0x%x\n", status);
status = STATUS_NOT_SOCKET;
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
static
NTSTATUS
VIOSockCreate(
IN WDFDEVICE WdfDevice,
IN WDFREQUEST Request,
IN WDFFILEOBJECT FileObject
)
{
PDEVICE_CONTEXT pContext = GetDeviceContext(WdfDevice);
PSOCKET_CONTEXT pSocket;
NTSTATUS status = STATUS_SUCCESS;
WDF_REQUEST_PARAMETERS parameters;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_CREATE_CLOSE, "%s\n", __FUNCTION__);
ASSERT(FileObject);
if (WDF_NO_HANDLE == FileObject)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_CREATE_CLOSE, "NULL FileObject\n");
return STATUS_UNSUCCESSFUL;
}
pSocket = GetSocketContext(FileObject);
pSocket->ThisSocket = FileObject;
pSocket->SocketId = InterlockedIncrement(&pContext->SocketId);
WDF_REQUEST_PARAMETERS_INIT(&parameters);
//check EA presents
WdfRequestGetParameters(Request, &parameters);
if (parameters.Parameters.Create.EaLength)
{
PFILE_FULL_EA_INFORMATION EaBuffer=
(PFILE_FULL_EA_INFORMATION)WdfRequestWdmGetIrp(Request)->AssociatedIrp.SystemBuffer;
ASSERT(EaBuffer);
if (EaBuffer->EaValueLength >= sizeof(VIRTIO_VSOCK_PARAMS))
{
WDF_OBJECT_ATTRIBUTES Attributes;
HANDLE hListenSocket;
PVIRTIO_VSOCK_PARAMS pParams = (PVIRTIO_VSOCK_PARAMS)((PCHAR)EaBuffer +
((ULONG_PTR)UFIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + 1 + EaBuffer->EaNameLength));
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_SOCKET, "Socket %d (%p) initializing\n",
pSocket->SocketId, pSocket->ThisSocket);
WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
Attributes.ParentObject = FileObject;
status = WdfSpinLockCreate(&Attributes, &pSocket->StateLock);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_CREATE_CLOSE, "WdfSpinLockCreate failed (StateLock) - 0x%x\n", status);
return status;
}
WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
Attributes.ParentObject = FileObject;
status = WdfSpinLockCreate(&Attributes, &pSocket->RxLock);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_CREATE_CLOSE, "WdfSpinLockCreate failed (RxLock) - 0x%x\n", status);
return status;
}
KeInitializeEvent(&pSocket->CloseEvent, NotificationEvent, FALSE);
pSocket->src_port = VMADDR_PORT_ANY;//set unbound state
pSocket->dst_port = VMADDR_PORT_ANY;
pSocket->dst_cid = VMADDR_CID_ANY;
pSocket->State = VIOSOCK_STATE_CLOSE;
pSocket->LoopbackSocket = WDF_NO_HANDLE;
status = VIOSockReadSocketQueueInit(pSocket);
if (!NT_SUCCESS(status))
{
return status;
}
InitializeListHead(&pSocket->RxCbList);
pSocket->RxBytes = 0;
status = VIOSockTimerCreate(&pSocket->PendedTimer, FileObject, VIOSockPendedTimerFunc);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_CREATE_CLOSE, "VIOSockTimerCreate failed (PendedTimer) - 0x%x\n", status);
return status;
}
pSocket->PendedRequest = WDF_NO_HANDLE;
#ifdef _WIN64
if (WdfRequestIsFrom32BitProcess(Request))
{
hListenSocket = Handle32ToHandle((void * POINTER_32)(ULONG)pParams->Socket);
}
else
#endif //_WIN64
{
hListenSocket = (HANDLE)pParams->Socket;
}
//find listen socket
if (hListenSocket)
{
status = VIOSockAccept(hListenSocket, Request);
}
else
{
if (pParams->Type != VIRTIO_VSOCK_TYPE_STREAM)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_CREATE_CLOSE, "Unsupported socket type: %u\n", pSocket->type);
status = STATUS_NOT_SUPPORTED;
}
else
{
pSocket->type = pParams->Type;
pSocket->ConnectTimeout = VSOCK_DEFAULT_CONNECT_TIMEOUT;
pSocket->BufferMinSize = VSOCK_DEFAULT_BUFFER_MIN_SIZE;
pSocket->BufferMaxSize = VSOCK_DEFAULT_BUFFER_MAX_SIZE;
pSocket->SendTimeout = LONG_MAX;
pSocket->RecvTimeout = LONG_MAX;
pSocket->buf_alloc = VSOCK_DEFAULT_BUFFER_SIZE;
}
}
}
else
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_CREATE_CLOSE, "Invalid EA length\n");
status = STATUS_INVALID_PARAMETER;
}
}
else
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_SOCKET, "Control socket %d (%p) initializing\n",
pSocket->SocketId, pSocket->ThisSocket);
//Socket for select and config retrieve
VIOSockSetFlag(pSocket, SOCK_CONTROL);
status = STATUS_SUCCESS;
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_CREATE_CLOSE,"<-- %s\n", __FUNCTION__);
return status;
}
VOID
VIOSockCreateStub(
IN WDFDEVICE WdfDevice,
IN WDFREQUEST Request,
IN WDFFILEOBJECT FileObject
)
{
NTSTATUS status = VIOSockCreate(WdfDevice, Request, FileObject);
PAGED_CODE();
if (status != STATUS_PENDING)
WdfRequestComplete(Request, status);
}
VOID
VIOSockDoClose(
PSOCKET_CONTEXT pSocket
)
{
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
pSocket->PeerShutdown = VIRTIO_VSOCK_SHUTDOWN_MASK;
if (!VIOSockRxHasData(pSocket))
{
VIOSockStateSet(pSocket, VIOSOCK_STATE_CLOSING);
}
KeSetEvent(&pSocket->CloseEvent, IO_NO_INCREMENT, FALSE);
}
VOID
VIOSockClose(
IN WDFFILEOBJECT FileObject
)
{
PSOCKET_CONTEXT pSocket = GetSocketContext(FileObject);
WDFREQUEST Request;
VIOSOCK_STATE PrevState;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_CREATE_CLOSE, "--> %s\n", __FUNCTION__);
if (VIOSockIsFlag(pSocket, SOCK_CONTROL))
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_SOCKET, "Control socket %d closed\n", pSocket->SocketId);
return;
}
VIOSockBoundRemove(pSocket);
if (VIOSockStateGet(pSocket) == VIOSOCK_STATE_CONNECTED ||
VIOSockStateGet(pSocket) == VIOSOCK_STATE_CLOSING)
{
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_CREATE_CLOSE, "Socket %d, peershutdown: %x\n", pSocket->SocketId, pSocket->PeerShutdown);
/* Already received SHUTDOWN from peer, reply with RST */
if ((pSocket->PeerShutdown & VIRTIO_VSOCK_SHUTDOWN_MASK) == VIRTIO_VSOCK_SHUTDOWN_MASK)
{
if(VIOSockStateGet(pSocket) != VIOSOCK_STATE_CLOSING)
VIOSockSendReset(pSocket, FALSE);
}
else
{
LARGE_INTEGER liTimeout;
if ((pSocket->Shutdown & VIRTIO_VSOCK_SHUTDOWN_MASK) != VIRTIO_VSOCK_SHUTDOWN_MASK)
{
VIOSockSendShutdown(pSocket, VIRTIO_VSOCK_SHUTDOWN_MASK);
}
liTimeout.QuadPart = -(VSOCK_CLOSE_TIMEOUT + (VIOSockIsFlag(pSocket, SOCK_LINGER) ?
WDF_ABS_TIMEOUT_IN_SEC(pSocket->LingerTime) : 0));
if (KeWaitForSingleObject(&pSocket->CloseEvent, Executive, KernelMode, FALSE, &liTimeout) == STATUS_TIMEOUT)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_CREATE_CLOSE, "Socket %d, close timeout expires\n", pSocket->SocketId);
VIOSockSendReset(pSocket, FALSE);
VIOSockDoClose(pSocket);
}
}
}
PrevState = VIOSockStateSet(pSocket, VIOSOCK_STATE_CLOSE);
VIOSockPendedRequestGetLocked(pSocket, &Request);
if (Request != WDF_NO_HANDLE)
{
WdfRequestComplete(Request, STATUS_CANCELLED);
}
if (pSocket->EventObject)
{
ObDereferenceObject(pSocket->EventObject);
pSocket->EventObject = NULL;
}
if (pSocket->LoopbackSocket != WDF_NO_HANDLE)
WdfObjectDereference(pSocket->LoopbackSocket);
if (PrevState == VIOSOCK_STATE_LISTEN)
VIOSockAcceptCleanup(pSocket);
else
{
VIOSockTxCleanup(GetDeviceContextFromSocket(pSocket), pSocket->ThisSocket, STATUS_CONNECTION_DISCONNECTED);
VIOSockReadCleanupCb(pSocket);
}
TraceEvents(TRACE_LEVEL_INFORMATION, DBG_SOCKET, "Socket %d closed\n", pSocket->SocketId);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_CREATE_CLOSE, "<-- %s\n", __FUNCTION__);
}
__inline
BOOLEAN
VIOSockIsGuestAddrValid(
IN PSOCKADDR_VM pAddr
)
{
return (pAddr->svm_family == AF_VSOCK) &&
(pAddr->svm_cid != VMADDR_CID_HYPERVISOR) &&
(pAddr->svm_cid != VMADDR_CID_RESERVED) &&
(pAddr->svm_cid != VMADDR_CID_HOST);
}
__inline
BOOLEAN
VIOSockIsHostAddrValid(
IN PSOCKADDR_VM pAddr,
IN ULONG32 ulSrcCid
)
{
return (pAddr->svm_family == AF_VSOCK) &&
(pAddr->svm_cid == VMADDR_CID_HYPERVISOR ||
pAddr->svm_cid == VMADDR_CID_HOST ||
pAddr->svm_cid == ulSrcCid);
}
static
NTSTATUS
VIOSockBind(
IN WDFREQUEST Request
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PDEVICE_CONTEXT pContext = GetDeviceContextFromRequest(Request);
PSOCKADDR_VM pAddr;
SIZE_T stAddrLen;
NTSTATUS status;
static ULONG32 uSvmPort;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pAddr), &pAddr, &stAddrLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stAddrLen >= sizeof(*pAddr));
if (!VIOSockIsGuestAddrValid(pAddr))
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Invalid addr to bind, cid: %u, port: %u\n", pAddr->svm_cid, pAddr->svm_port);
return STATUS_INVALID_ADDRESS_COMPONENT;
}
if (pAddr->svm_cid != (ULONG32)pContext->Config.guest_cid &&
pAddr->svm_cid != VMADDR_CID_ANY)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Invalid cid: %u\n", pAddr->svm_cid);
return STATUS_INVALID_ADDRESS_COMPONENT;
}
if (VIOSockIsBound(pSocket))
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Socket %d already bound\n", pSocket->SocketId);
return STATUS_INVALID_PARAMETER;
}
if (VIOSockStateGet(pSocket) != VIOSOCK_STATE_CLOSE)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Invalid socket state: %u\n", VIOSockStateGet(pSocket));
return STATUS_UNSUCCESSFUL;
}
status = VIOSockBoundAdd(pSocket, pAddr->svm_port);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "VIOSockBoundAdd failed: 0x%x\n", status);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s\n", __FUNCTION__);
return status;
}
static
NTSTATUS
VIOSockConnect(
IN WDFREQUEST Request
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PDEVICE_CONTEXT pContext = GetDeviceContextFromRequest(Request);
PSOCKADDR_VM pAddr;
SIZE_T stAddrLen;
NTSTATUS status;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pAddr), &pAddr, &stAddrLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stAddrLen >= sizeof(*pAddr));
if(!VIOSockIsHostAddrValid(pAddr, (ULONG32)pContext->Config.guest_cid))
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Invalid addr to connect, cid: %u, port: %u\n", pAddr->svm_cid, pAddr->svm_port);
return STATUS_INVALID_ADDRESS_COMPONENT;
}
switch (VIOSockStateGet(pSocket))
{
case VIOSOCK_STATE_CONNECTING:
status = STATUS_CONNECTION_ESTABLISHING;
break;
case VIOSOCK_STATE_CONNECTED:
case VIOSOCK_STATE_CLOSING:
status = STATUS_CONNECTION_ACTIVE;
break;
case VIOSOCK_STATE_LISTEN:
status = STATUS_INVALID_PARAMETER;
break;
default:
status = STATUS_SUCCESS;
}
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Invalid socket state: %u\n", pSocket->State);
return status;
}
if (!VIOSockIsBound(pSocket))
{
//autobind
status = VIOSockBoundAdd(pSocket, VMADDR_PORT_ANY);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "VIOSockBoundAdd failed: 0x%x\n", status);
return status;
}
}
VIOSockEventClearBit(pSocket, FD_CONNECT_BIT);
pSocket->dst_cid = pAddr->svm_cid;
pSocket->dst_port = pAddr->svm_port;
//no lock required
VIOSockStateSet(pSocket, VIOSOCK_STATE_CONNECTING);
if (!VIOSockIsNonBlocking(pSocket))
{
status = VIOSockPendedRequestSetLocked(pSocket, Request, pSocket->ConnectTimeout);
if (!NT_SUCCESS(status))
{
VIOSockStateSet(pSocket, VIOSOCK_STATE_CLOSE);
return status;
}
}
if (pSocket->dst_cid == (ULONG32)pContext->Config.guest_cid)
{
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "Connect to local socket (loopback)\n");
VIOSockSetFlag(pSocket, SOCK_LOOPBACK);
}
else
VIOSockResetFlag(pSocket, SOCK_LOOPBACK);
status = VIOSockSendConnect(pSocket);
if (!NT_SUCCESS(status))
{
//no lock required
VIOSockStateSet(pSocket, VIOSOCK_STATE_CLOSE);
if (!VIOSockIsNonBlocking(pSocket))
{
if (!NT_SUCCESS(VIOSockPendedRequestGetLocked(pSocket, &Request)))
{
status = STATUS_PENDING; //do not complete canceled request
}
}
}
else
status = VIOSockIsNonBlocking(pSocket) ? STATUS_CANT_WAIT : STATUS_PENDING;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
_Requires_lock_not_held_(pSocket->StateLock)
BOOLEAN
VIOSockShutdownFromPeer(
PSOCKET_CONTEXT pSocket,
ULONG uFlags
)
{
BOOLEAN bRes;
ULONG32 uDrain;
WdfSpinLockAcquire(pSocket->StateLock);
uDrain = ~pSocket->PeerShutdown & uFlags;
pSocket->PeerShutdown |= uFlags;
bRes = (pSocket->PeerShutdown & VIRTIO_VSOCK_SHUTDOWN_MASK) == VIRTIO_VSOCK_SHUTDOWN_MASK;
WdfSpinLockRelease(pSocket->StateLock);
// if (uDrain & VIRTIO_VSOCK_SHUTDOWN_SEND)
// VIOSockReadDequeueCb(pSocket, WDF_NO_HANDLE);
// if (uDrain & VIRTIO_VSOCK_SHUTDOWN_RCV)
// {
// //TODO: dequeue requests for current socket only
// }
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, bRes: %u\n", __FUNCTION__, bRes);
return bRes;
}
_Requires_lock_not_held_(pSocket->StateLock)
static
NTSTATUS
VIOSockStateValidateShutdown(
PSOCKET_CONTEXT pSocket,
ULONG32 uHow
)
{
NTSTATUS status;
WdfSpinLockAcquire(pSocket->StateLock);
if ((pSocket->Shutdown & uHow) == uHow)
{
status = STATUS_LOCAL_DISCONNECT;
}
else if(pSocket->PeerShutdown & VIRTIO_VSOCK_SHUTDOWN_MASK)
{
status = STATUS_SUCCESS;
}
else if (VIOSockStateGet(pSocket) == VIOSOCK_STATE_CLOSING)
{
status = STATUS_CONNECTION_RESET;
}
else if (VIOSockStateGet(pSocket) != VIOSOCK_STATE_CONNECTED)
{
status = STATUS_CONNECTION_INVALID;
}
else
status = STATUS_SUCCESS;
WdfSpinLockRelease(pSocket->StateLock);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
static
NTSTATUS
VIOSockShutdown(
IN WDFREQUEST Request
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
int *pHow;
SIZE_T stHowLen;
NTSTATUS status;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pHow), &pHow, &stHowLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stHowLen >= sizeof(*pHow));
/* User level uses SD_RECEIVE (0) and SD_SEND (1), but the kernel uses
* VIRTIO_VSOCK_SHUTDOWN_RCV (1) and VIRTIO_VSOCK_SHUTDOWN_SEND (2),
* so we must increment mode here like the other address families do.
* Note also that the increment makes SD_BOTH (2) into
* RCV_SHUTDOWN | SEND_SHUTDOWN (3), which is what we want.
*/
(*pHow)++;
if ((*pHow & ~VIRTIO_VSOCK_SHUTDOWN_MASK) || !*pHow)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_SOCKET, "Invalid shutdown flags: %u\n", *pHow);
return STATUS_INVALID_PARAMETER;
}
status = VIOSockStateValidateShutdown(pSocket, *pHow);
if (NT_SUCCESS(status))
{
pSocket->Shutdown |= (ULONG32)*pHow;
status = VIOSockSendShutdown(pSocket, *pHow);
}
else if (status == STATUS_LOCAL_DISCONNECT)
{
status = STATUS_SUCCESS;
}
return status;
}
static
NTSTATUS
VIOSockListen(
IN WDFREQUEST Request
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
int *pBacklog;
SIZE_T stBacklogLen;
NTSTATUS status;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pBacklog), &pBacklog, &stBacklogLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stBacklogLen >= sizeof(*pBacklog));
if (VIOSockStateGet(pSocket) == VIOSOCK_STATE_CLOSE)
{
if (VIOSockIsBound(pSocket))
{
pSocket->Backlog = *pBacklog;
InitializeListHead(&pSocket->AcceptList);
//no lock required
VIOSockStateSet(pSocket, VIOSOCK_STATE_LISTEN);
}
else
status = STATUS_INVALID_PARAMETER;
}
else
status = STATUS_CONNECTION_ACTIVE;
return status;
}
__inline
PKEVENT
VIOSockGetEventFromHandle(
IN HANDLE hEvent
)
{
PKEVENT pEvent = NULL;
ObReferenceObjectByHandle(hEvent, STANDARD_RIGHTS_REQUIRED, *ExEventObjectType,
KernelMode, (PVOID)&pEvent, NULL);
return pEvent;
}
_Requires_lock_not_held_(pSocket->StateLock)
static
VOID
VIOSockEventUnregister(
IN PSOCKET_CONTEXT pSocket,
IN PVIRTIO_VSOCK_NETWORK_EVENTS lpNetworkEvents,
IN PKEVENT pEvent
)
{
WdfSpinLockAcquire(pSocket->StateLock);
if (pEvent)
pSocket->EventObject = NULL;
lpNetworkEvents->NetworkEvents = pSocket->Events & pSocket->EventsMask;
RtlCopyMemory(lpNetworkEvents->Status, pSocket->EventsStatus, sizeof(pSocket->EventsStatus));
pSocket->Events = 0;
WdfSpinLockRelease(pSocket->StateLock);
if (pEvent)
{
KeClearEvent(pEvent);
ObDereferenceObject(pEvent);
}
}
static
NTSTATUS
VIOSockEnumNetEvents(
IN WDFREQUEST Request,
OUT size_t *pLength
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PULONGLONG pEventObject;
SIZE_T stEventObjectLen;
PVIRTIO_VSOCK_NETWORK_EVENTS lpNetworkEvents;
SIZE_T stNetworkEventsLen;
NTSTATUS status;
HANDLE hEvent = NULL;
PKEVENT pEvent = NULL;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pEventObject), &pEventObject, &stEventObjectLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stEventObjectLen >= sizeof(*pEventObject));
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(*lpNetworkEvents), &lpNetworkEvents, &stNetworkEventsLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveOutputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveOutputBuffer above
_Analysis_assume_(stNetworkEventsLen >= sizeof(*lpNetworkEvents));
#ifdef _WIN64
if (WdfRequestIsFrom32BitProcess(Request))
{
hEvent = Handle32ToHandle((void * POINTER_32)(ULONG)*pEventObject);
}
else
#endif //_WIN64
{
hEvent = (HANDLE)(ULONG_PTR)*pEventObject;
}
if (hEvent)
{
pEvent = VIOSockGetEventFromHandle(hEvent);
if (!pEvent)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "VIOSockGetEventFromHandle failed\n");
return STATUS_INVALID_PARAMETER;
}
}
if (pEvent && pEvent != pSocket->EventObject)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "Invalid event handle\n");
return STATUS_INVALID_PARAMETER;
}
VIOSockEventUnregister(pSocket, lpNetworkEvents, pEvent);
*pLength = sizeof(*lpNetworkEvents);
return STATUS_SUCCESS;
}
__inline
NTSTATUS
VIOSockSetNonBlocking(
IN PSOCKET_CONTEXT pSocket,
IN BOOLEAN bNonBlocking
)
{
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s, bNonBlocking: %u\n", __FUNCTION__, bNonBlocking);
if (!VIOSockSetFlag(pSocket, SOCK_NON_BLOCK))
{
VIOSockTxCleanup(GetDeviceContextFromSocket(pSocket), pSocket->ThisSocket, STATUS_CANT_WAIT);
VIOSockReadProcessDequeueCb(pSocket);
}
return STATUS_SUCCESS;
}
_Requires_lock_not_held_(pSocket->StateLock)
static
VOID
VIOSockEventRegister(
IN PSOCKET_CONTEXT pSocket,
IN LONG lNetworkEvents,
IN PKEVENT pEvent
)
{
WdfSpinLockAcquire(pSocket->StateLock);
pSocket->EventsMask = lNetworkEvents;
if (pSocket->EventObject)
ObDereferenceObject(pSocket->EventObject);
pSocket->Events = 0;
pSocket->EventObject = pEvent;
WdfSpinLockRelease(pSocket->StateLock);
}
static
NTSTATUS
VIOSockEventSelect(
IN WDFREQUEST Request
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PVIRTIO_VSOCK_EVENT_SELECT pEventSelect;
SIZE_T stEventSelectLen;
NTSTATUS status;
PKEVENT pEvent = NULL;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pEventSelect), &pEventSelect, &stEventSelectLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stEventSelectLen >= sizeof(*pEventSelect));
if (pEventSelect->lNetworkEvents)
{
HANDLE hEvent;
#ifdef _WIN64
if (WdfRequestIsFrom32BitProcess(Request))
{
hEvent = Handle32ToHandle((void * POINTER_32)pEventSelect->hEventObject);
}
else
#endif //_WIN64
{
hEvent = (HANDLE)pEventSelect->hEventObject;
}
if (!hEvent)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_IOCTLS, "Invalid hEventObject\n");
return STATUS_INVALID_PARAMETER;
}
pEvent = VIOSockGetEventFromHandle(hEvent);
if (!pEvent)
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_SOCKET, "VIOSockGetEventFromHandle failed\n");
return STATUS_INVALID_PARAMETER;
}
}
VIOSockSetNonBlocking(pSocket, TRUE);
VIOSockEventRegister(pSocket, pEventSelect->lNetworkEvents, pEvent);
return STATUS_SUCCESS;
}
_Requires_lock_not_held_(pSocket->StateLock)
VOID
VIOSockEventSetBit(
IN PSOCKET_CONTEXT pSocket,
IN ULONG uSetBit,
IN NTSTATUS Status
)
{
ULONG uEvent = (1 << uSetBit);
BOOLEAN bSetEvent;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s, uSetBit: 0x%x\n", __FUNCTION__, uSetBit);
bSetEvent = !!(~pSocket->Events & uEvent & pSocket->EventsMask);
pSocket->Events |= uEvent;
pSocket->EventsStatus[uSetBit] = Status;
VIOSockSelectRun(pSocket);
if (bSetEvent && pSocket->EventObject)
KeSetEvent(pSocket->EventObject, IO_NO_INCREMENT, FALSE);
}
_Requires_lock_not_held_(pSocket->StateLock)
VOID
VIOSockEventSetBitLocked(
IN PSOCKET_CONTEXT pSocket,
IN ULONG uSetBit,
IN NTSTATUS Status
)
{
WdfSpinLockAcquire(pSocket->StateLock);
VIOSockEventSetBit(pSocket, uSetBit, Status);
WdfSpinLockRelease(pSocket->StateLock);
}
_Requires_lock_not_held_(pSocket->StateLock)
VOID
VIOSockEventClearBit(
IN PSOCKET_CONTEXT pSocket,
IN ULONG uClearBit
)
{
ULONG uEvent = (1 << uClearBit);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s, uClearBit: 0x%x\n", __FUNCTION__, uClearBit);
WdfSpinLockAcquire(pSocket->StateLock);
pSocket->Events &= ~uEvent;
WdfSpinLockRelease(pSocket->StateLock);
}
static
NTSTATUS
VIOSockGetPeerName(
IN WDFREQUEST Request,
OUT size_t *pLength
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PSOCKADDR_VM pAddr;
SIZE_T stAddrLen;
NTSTATUS status;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(*pAddr), &pAddr, &stAddrLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveOutputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stAddrLen >= sizeof(*pAddr));
if (VIOSockStateGet(pSocket) == VIOSOCK_STATE_CONNECTED)
{
RtlZeroBytes(pAddr, sizeof(*pAddr));
pAddr->svm_family = AF_VSOCK;
pAddr->svm_cid = pSocket->dst_cid;
pAddr->svm_port = pSocket->dst_port;
*pLength = sizeof(*pAddr);
}
else
status = STATUS_INVALID_DEVICE_STATE;
return STATUS_SUCCESS;
}
static
NTSTATUS
VIOSockGetSockName(
IN WDFREQUEST Request,
OUT size_t *pLength
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PDEVICE_CONTEXT pContext = GetDeviceContextFromRequest(Request);
PSOCKADDR_VM pAddr;
SIZE_T stAddrLen;
NTSTATUS status;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(*pAddr), &pAddr, &stAddrLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveOutputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stAddrLen >= sizeof(*pAddr));
if (VIOSockIsBound(pSocket))
{
RtlZeroBytes(pAddr, sizeof(*pAddr));
pAddr->svm_family = AF_VSOCK;
pAddr->svm_cid = (ULONG32)pContext->Config.guest_cid;
pAddr->svm_port = pSocket->src_port;
*pLength = sizeof(*pAddr);
}
else
status = STATUS_INVALID_DEVICE_STATE;
return STATUS_SUCCESS;
}
/*
* Structure used for manipulating linger option.
*/
typedef struct _LINGER {
USHORT l_onoff; /* option on/off */
USHORT l_linger; /* linger time */
}LINGER, *PLINGER;
#define SO_ACCEPTCONN 0x0002 /* socket has had listen() */
#define SO_LINGER 0x0080 /* linger on close if data present */
#define SOCK_STREAM 1
/*
* Structure used in select() call, taken from the BSD file sys/time.h.
*/
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
#define USEC_PER_SEC 1000000L
static
NTSTATUS
VIOSockGetSockOpt(
IN WDFREQUEST Request,
OUT size_t *pLength
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PVIRTIO_VSOCK_OPT pOpt;
SIZE_T stOptLen;
PVOID pOptVal;
NTSTATUS status;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pOpt), &pOpt, &stOptLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stOptLen >= sizeof(*pOpt));
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(*pOpt), &pOpt, &stOptLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveOutputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stOptLen >= sizeof(*pOpt));
if (!pOpt->optlen || !pOpt->optval)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_IOCTLS, "Invalid *optval\n");
return STATUS_INVALID_PARAMETER;
}
#ifdef _WIN64
if (WdfRequestIsFrom32BitProcess(Request))
{
pOptVal = Ptr32ToPtr((void * POINTER_32)(ULONG)pOpt->optval);
}
else
#endif //_WIN64
{
pOptVal = (PVOID)pOpt->optval;
}
if (WdfRequestGetRequestorMode(Request) == UserMode)
{
WDFMEMORY Memory;
status = WdfRequestProbeAndLockUserBufferForWrite(Request, pOptVal, pOpt->optlen, &Memory);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestProbeAndLockUserBufferForWrite failed: 0x%x\n", status);
return status;
}
pOptVal = WdfMemoryGetBuffer(Memory, NULL);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "OptName: 0x%04x\n", pOpt->optname);
switch (pOpt->optname)
{
case SO_ACCEPTCONN:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
*(PULONG)pOptVal = (VIOSockStateGet(pSocket) == VIOSOCK_STATE_LISTEN);
pOpt->optlen = sizeof(ULONG);
break;
case SO_TYPE:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
*(PULONG)pOptVal = SOCK_STREAM;
pOpt->optlen = sizeof(ULONG);
break;
case SO_LINGER:
if (pOpt->optlen < sizeof(LINGER))
{
status = STATUS_INVALID_PARAMETER;
break;
}
((PLINGER)pOptVal)->l_onoff = VIOSockIsFlag(pSocket, SOCK_LINGER);
((PLINGER)pOptVal)->l_linger = pSocket->LingerTime;
pOpt->optlen = sizeof(LINGER);
break;
case SO_SNDTIMEO:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
*(PULONG)pOptVal = pSocket->SendTimeout;
pOpt->optlen = sizeof(ULONG);
break;
case SO_RCVTIMEO:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
*(PULONG)pOptVal = pSocket->RecvTimeout;
pOpt->optlen = sizeof(ULONG);
break;
case SO_VM_SOCKETS_BUFFER_SIZE:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
*(PULONG)pOptVal = pSocket->buf_alloc;
pOpt->optlen = sizeof(ULONG);
break;
case SO_VM_SOCKETS_BUFFER_MAX_SIZE:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
*(PULONG)pOptVal = pSocket->BufferMaxSize;
pOpt->optlen = sizeof(ULONG);
break;
case SO_VM_SOCKETS_BUFFER_MIN_SIZE:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
*(PULONG)pOptVal = pSocket->BufferMinSize;
pOpt->optlen = sizeof(ULONG);
break;
case SO_VM_SOCKETS_CONNECT_TIMEOUT:
if (pOpt->optlen < sizeof(struct timeval))
{
status = STATUS_INVALID_PARAMETER;
break;
}
else
{
struct timeval* tv = (struct timeval*)pOptVal;
tv->tv_sec = (long)NANO_TO_SEC(pSocket->ConnectTimeout);
tv->tv_usec = (long)NANO_TO_USEC(pSocket->ConnectTimeout - SEC_TO_NANO((LONGLONG)tv->tv_sec));
pOpt->optlen = sizeof(*tv);
}
break;
default:
status = STATUS_NOT_SUPPORTED;
}
if (NT_SUCCESS(status))
*pLength = sizeof(*pOpt);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
__inline
VOID
VIOSockSetLinger(
IN PSOCKET_CONTEXT pSocket,
IN PLINGER pLinger
)
{
if (pLinger->l_onoff)
{
pSocket->LingerTime = pLinger->l_linger;
VIOSockSetFlag(pSocket, SOCK_LINGER);
}
else
VIOSockResetFlag(pSocket, SOCK_LINGER);
}
static
VOID
VIOSockRxUpdateBufferSize(
IN PSOCKET_CONTEXT pSocket,
IN ULONG uBufferSize
)
{
if (uBufferSize > pSocket->BufferMaxSize)
uBufferSize = pSocket->BufferMaxSize;
if (uBufferSize < pSocket->BufferMinSize)
uBufferSize = pSocket->BufferMinSize;
if (uBufferSize != pSocket->buf_alloc)
{
pSocket->buf_alloc = uBufferSize;
VIOSockSendCreditUpdate(pSocket);
}
}
static
NTSTATUS
VIOSockSetSockOpt(
IN WDFREQUEST Request
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PVIRTIO_VSOCK_OPT pOpt;
SIZE_T stOptLen;
PVOID pOptVal;
NTSTATUS status;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pOpt), &pOpt, &stOptLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stOptLen >= sizeof(*pOpt));
if (!pOpt->optlen || !pOpt->optval)
{
TraceEvents(TRACE_LEVEL_WARNING, DBG_IOCTLS, "Invalid *optval\n");
return STATUS_INVALID_PARAMETER;
}
#ifdef _WIN64
if (WdfRequestIsFrom32BitProcess(Request))
{
pOptVal = Ptr32ToPtr((void * POINTER_32)(ULONG)pOpt->optval);
}
else
#endif //_WIN64
{
pOptVal = (PVOID)pOpt->optval;
}
if (WdfRequestGetRequestorMode(Request) == UserMode)
{
WDFMEMORY Memory;
status = WdfRequestProbeAndLockUserBufferForRead(Request, pOptVal, pOpt->optlen, &Memory);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestProbeAndLockUserBufferForRead failed: 0x%x\n", status);
return status;
}
pOptVal = WdfMemoryGetBuffer(Memory, NULL);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "OptName: 0x%04x\n", pOpt->optname);
switch (pOpt->optname)
{
case SO_LINGER:
if (pOpt->optlen < sizeof(LINGER))
{
status = STATUS_INVALID_PARAMETER;
break;
}
VIOSockSetLinger(pSocket, (PLINGER)pOptVal);
break;
case SO_SNDTIMEO:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
pSocket->SendTimeout = *(PULONG)pOptVal;
break;
case SO_RCVTIMEO:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
pSocket->RecvTimeout = *(PULONG)pOptVal;
break;
case SO_VM_SOCKETS_BUFFER_SIZE:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
VIOSockRxUpdateBufferSize(pSocket, *(PULONG)pOptVal);
break;
case SO_VM_SOCKETS_BUFFER_MAX_SIZE:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
pSocket->BufferMaxSize = *(PULONG)pOptVal;
VIOSockRxUpdateBufferSize(pSocket, pSocket->buf_alloc);
break;
case SO_VM_SOCKETS_BUFFER_MIN_SIZE:
if (pOpt->optlen < sizeof(ULONG))
{
status = STATUS_INVALID_PARAMETER;
break;
}
pSocket->BufferMinSize = *(PULONG)pOptVal;
VIOSockRxUpdateBufferSize(pSocket, pSocket->buf_alloc);
break;
case SO_VM_SOCKETS_CONNECT_TIMEOUT:
if (pOpt->optlen < sizeof(struct timeval))
{
status = STATUS_INVALID_PARAMETER;
break;
}
else
{
struct timeval *tv = (struct timeval*)pOptVal;
LONGLONG llTimeout = 0;
if (tv->tv_sec >= 0 && tv->tv_usec < USEC_PER_SEC &&
tv->tv_sec < (LONG_MAX / 1000 - 1))
{
llTimeout = SEC_TO_NANO(tv->tv_sec) + USEC_TO_NANO(tv->tv_usec);
}
if (!llTimeout)
llTimeout = VSOCK_DEFAULT_CONNECT_TIMEOUT;
pSocket->ConnectTimeout = llTimeout;
}
break;
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
#define IOCPARM_MASK 0x7f /* parameters must be < 128 bytes */
#define IOC_VOID 0x20000000 /* no parameters */
#define IOC_OUT 0x40000000 /* copy out parameters */
#define IOC_IN 0x80000000 /* copy in parameters */
#define _IOR(x,y,t) (IOC_OUT|(((long)sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y))
#define _IOW(x,y,t) (IOC_IN|(((long)sizeof(t)&IOCPARM_MASK)<<16)|((x)<<8)|(y))
#define FIONREAD _IOR('f', 127, ULONG) /* get # bytes to read */
#define FIONBIO _IOW('f', 126, ULONG) /* set/clear non-blocking i/o */
#define IOC_WS2 0x08000000
#define _WSAIO(x,y) (IOC_VOID|(x)|(y))
#define _WSAIOR(x,y) (IOC_OUT|(x)|(y))
#define SIO_ADDRESS_LIST_QUERY _WSAIOR(IOC_WS2,22)
#define SIO_ADDRESS_LIST_CHANGE _WSAIO(IOC_WS2,23)
static
NTSTATUS
VIOSockIoctl(
IN WDFREQUEST Request,
OUT size_t *pLength
)
{
PSOCKET_CONTEXT pSocket = GetSocketContextFromRequest(Request);
PVIRTIO_VSOCK_IOCTL_IN pInParams;
SIZE_T stInParamsLen;
PVOID lpvOutBuffer = NULL;
SIZE_T stOutBufferLen = 0;
NTSTATUS status;
PVOID lpvInBuffer = NULL;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "--> %s\n", __FUNCTION__);
status = WdfRequestRetrieveInputBuffer(Request, sizeof(*pInParams), &pInParams, &stInParamsLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveInputBuffer failed: 0x%x\n", status);
return status;
}
// minimum length guaranteed by WdfRequestRetrieveInputBuffer above
_Analysis_assume_(stInParamsLen >= sizeof(*pInParams));
if (pInParams->dwIoControlCode == FIONREAD ||
pInParams->dwIoControlCode == SIO_ADDRESS_LIST_QUERY)
{
status = WdfRequestRetrieveOutputBuffer(Request, 0, &lpvOutBuffer, &stOutBufferLen);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestRetrieveOutputBuffer failed: 0x%x\n", status);
return status;
}
}
if (pInParams->cbInBuffer)
{
#ifdef _WIN64
if (WdfRequestIsFrom32BitProcess(Request))
{
lpvInBuffer = Ptr32ToPtr((void * POINTER_32)(ULONG)pInParams->lpvInBuffer);
}
else
#endif //_WIN64
{
lpvInBuffer = (PVOID)pInParams->lpvInBuffer;
}
if (lpvInBuffer)
{
if (WdfRequestGetRequestorMode(Request) == UserMode)
{
WDFMEMORY Memory;
status = WdfRequestProbeAndLockUserBufferForRead(Request, lpvInBuffer, pInParams->cbInBuffer, &Memory);
if (!NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "WdfRequestProbeAndLockUserBufferForRead failed: 0x%x\n", status);
return status;
}
lpvInBuffer = WdfMemoryGetBuffer(Memory, NULL);
}
}
else
status = STATUS_INVALID_PARAMETER;
}
if (NT_SUCCESS(status))
{
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "dwIoControlCode: 0x%08x\n", pInParams->dwIoControlCode);
switch (pInParams->dwIoControlCode)
{
case FIONREAD:
if (stOutBufferLen >= sizeof(ULONG))
{
*(PULONG)lpvOutBuffer = VIOSockRxHasData(pSocket);
*pLength = sizeof(ULONG);
status = STATUS_SUCCESS;
}
else
status = STATUS_INVALID_PARAMETER;
break;
case FIONBIO:
if (pInParams->cbInBuffer >= sizeof(ULONG))
{
status = VIOSockSetNonBlocking(pSocket, !!(*(PULONG)lpvInBuffer));
}
else
status = STATUS_INVALID_PARAMETER;
break;
//TODO: implement address list change
case SIO_ADDRESS_LIST_QUERY:
case SIO_ADDRESS_LIST_CHANGE:
default:
status = STATUS_NOT_SUPPORTED;
break;
}
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
NTSTATUS
VIOSockDeviceControl(
IN WDFREQUEST Request,
IN ULONG IoControlCode,
IN OUT size_t *pLength
)
{
NTSTATUS status = STATUS_SUCCESS;
PAGED_CODE();
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "<-- %s, code: 0x%08x, socket %d\n",
__FUNCTION__, IoControlCode, GetSocketContextFromRequest(Request)->SocketId);
*pLength = 0;
switch (IoControlCode)
{
case IOCTL_SOCKET_BIND:
status = VIOSockBind(Request);
break;
case IOCTL_SOCKET_CONNECT:
status = VIOSockConnect(Request);
break;
case IOCTL_SOCKET_READ:
status = VIOSockReadWithFlags(Request);
break;
case IOCTL_SOCKET_SHUTDOWN:
status = VIOSockShutdown(Request);
break;
case IOCTL_SOCKET_LISTEN:
status = VIOSockListen(Request);
break;
case IOCTL_SOCKET_ENUM_NET_EVENTS:
status = VIOSockEnumNetEvents(Request, pLength);
break;
case IOCTL_SOCKET_EVENT_SELECT:
status = VIOSockEventSelect(Request);
break;
case IOCTL_SOCKET_GET_PEER_NAME:
status = VIOSockGetPeerName(Request, pLength);
break;
case IOCTL_SOCKET_GET_SOCK_NAME:
status = VIOSockGetSockName(Request, pLength);
break;
case IOCTL_SOCKET_GET_SOCK_OPT:
status = VIOSockGetSockOpt(Request, pLength);
break;
case IOCTL_SOCKET_SET_SOCK_OPT:
status = VIOSockSetSockOpt(Request);
break;
case IOCTL_SOCKET_IOCTL:
status = VIOSockIoctl(Request, pLength);
break;
default:
TraceEvents(TRACE_LEVEL_ERROR, DBG_IOCTLS, "Invalid socket ioctl\n");
status = STATUS_INVALID_DEVICE_REQUEST;
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_IOCTLS, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
_Requires_lock_not_held_(pSocket->StateLock)
NTSTATUS
VIOSockStateValidate(
PSOCKET_CONTEXT pSocket,
BOOLEAN bTx
)
{
NTSTATUS status;
BOOLEAN bIsShutdown, bIsPeerShutdown;
WdfSpinLockAcquire(pSocket->StateLock);
if (bTx)
{
bIsShutdown = pSocket->Shutdown & VIRTIO_VSOCK_SHUTDOWN_SEND;
bIsPeerShutdown = pSocket->PeerShutdown & VIRTIO_VSOCK_SHUTDOWN_RCV;
}
else
{
bIsShutdown = pSocket->Shutdown & VIRTIO_VSOCK_SHUTDOWN_RCV;
bIsPeerShutdown = pSocket->PeerShutdown & VIRTIO_VSOCK_SHUTDOWN_SEND;
}
if (bIsShutdown)
{
status = STATUS_LOCAL_DISCONNECT;
}
else if (bIsPeerShutdown)
{
status = STATUS_REMOTE_DISCONNECT;
}
else if (VIOSockStateGet(pSocket) == VIOSOCK_STATE_CLOSING)
{
status = STATUS_CONNECTION_RESET;
}
else if (VIOSockStateGet(pSocket) != VIOSOCK_STATE_CONNECTED)
{
status = STATUS_CONNECTION_INVALID;
}
else
status = STATUS_SUCCESS;
WdfSpinLockRelease(pSocket->StateLock);
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_SOCKET, "<-- %s, status: 0x%08x\n", __FUNCTION__, status);
return status;
}
WDFFILEOBJECT
VIOSockGetSocketFromHandle(
IN PDEVICE_CONTEXT pContext,
IN ULONGLONG uSocket,
IN BOOLEAN bIs32BitProcess
)
{
NTSTATUS status;
PFILE_OBJECT pFileObj;
HANDLE hSocket;
#ifdef _WIN64
if (bIs32BitProcess)
{
hSocket = Handle32ToHandle((void * POINTER_32)(ULONG)uSocket);
}
else
#endif //_WIN64
{
hSocket = (HANDLE)uSocket;
}
status = ObReferenceObjectByHandle(hSocket, STANDARD_RIGHTS_REQUIRED, *IoFileObjectType,
KernelMode, (PVOID)&pFileObj, NULL);
if (NT_SUCCESS(status))
{
PSOCKET_CONTEXT pSocket = VIOSockConnectedFindByFile(pContext, pFileObj);
if (!pSocket)
{
pSocket = VIOSockBoundFindByFile(pContext, pFileObj);
}
ObDereferenceObject(pFileObj);
if (pSocket)
{
WdfObjectReference(pSocket->ThisSocket);
return pSocket->ThisSocket;
}
}
return WDF_NO_HANDLE;
}
VOID
VIOSockHandleTransportReset(
IN PDEVICE_CONTEXT pContext
)
{
WDFFILEOBJECT Socket;
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_HW_ACCESS, "--> %s\n", __FUNCTION__);
while (WDF_NO_HANDLE != (Socket = WdfCollectionGetFirstItem(pContext->ConnectedList)))
{
PSOCKET_CONTEXT pCurrentSocket = GetSocketContext(Socket);
ASSERT(VIOSockStateGet(pCurrentSocket) == VIOSOCK_STATE_CONNECTED);
VIOSockStateSet(pCurrentSocket, VIOSOCK_STATE_CLOSE);
VIOSockEventSetBit(pCurrentSocket, FD_CLOSE_BIT, STATUS_CONNECTION_RESET);
}
TraceEvents(TRACE_LEVEL_VERBOSE, DBG_HW_ACCESS, "<-- %s\n", __FUNCTION__);
}