977 lines
35 KiB
C
977 lines
35 KiB
C
// Kernel socket layer. This file owns process socket objects and provider-neutral socket dispatch.
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
|
|
#include <kernel/net_socket.h>
|
|
#include <kernel/ke_event.h>
|
|
#include <kernel/ke_irp.h>
|
|
#include <kernel/ke_process.h>
|
|
#include <kernel/ke_sched.h>
|
|
#include <kernel/ke_semaphore.h>
|
|
#include <kernel/ke_spinlock.h>
|
|
#include <kernel/ke_thread.h>
|
|
#include <kernel/mm_virt.h>
|
|
#include <kernel/ob_object.h>
|
|
#include <ntlite/ddi/io_driver.h>
|
|
#include <ntlite/ddi/net_stack.h>
|
|
#include <ntlite/syscall.h>
|
|
|
|
#define NET_SOCKET_IO_MAX_BYTES (1024u * 1024u)
|
|
#define NET_SOCKET_UDP_MAX_BYTES 65507u
|
|
#define NET_SOCKET_RAW_MAX_BYTES 65535u
|
|
|
|
static SPINLOCK gNetIrpStatsLock = SPINLOCK_INIT;
|
|
static NET_IRP_STATS gNetIrpStats;
|
|
|
|
// Records that a socket operation entered the IRP boundary.
|
|
static void NetIrpStatsSubmit(uint32_t operation) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock);
|
|
gNetIrpStats.Submitted++;
|
|
if (operation < NetSocketIrpOperationCount)
|
|
gNetIrpStats.ByOperation[operation]++;
|
|
KeReleaseSpinlock(&gNetIrpStatsLock, flags);
|
|
}
|
|
|
|
// Records that a submitted socket IRP returned pending to the socket facade.
|
|
static void NetIrpStatsPending(void) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock);
|
|
gNetIrpStats.Pending++;
|
|
KeReleaseSpinlock(&gNetIrpStatsLock, flags);
|
|
}
|
|
|
|
// Records the final status of a socket IRP after synchronous wait completion.
|
|
static void NetIrpStatsComplete(KSTATUS status, bool asynchronous) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock);
|
|
gNetIrpStats.Completed++;
|
|
if (asynchronous)
|
|
gNetIrpStats.CompletedAsynchronously++;
|
|
else
|
|
gNetIrpStats.CompletedSynchronously++;
|
|
|
|
if (status == STATUS_CANCELLED)
|
|
gNetIrpStats.Cancelled++;
|
|
else if (status == STATUS_WOULD_BLOCK)
|
|
gNetIrpStats.WouldBlock++;
|
|
else if (status == STATUS_TIMEOUT)
|
|
gNetIrpStats.Timeout++;
|
|
else if (KFAILED(status))
|
|
gNetIrpStats.Failed++;
|
|
|
|
KeReleaseSpinlock(&gNetIrpStatsLock, flags);
|
|
}
|
|
|
|
// Returns a stable snapshot of socket IRP diagnostics.
|
|
__declspec(dllexport) void NetQueryIrpStats(NET_IRP_STATS* out) {
|
|
if (!out)
|
|
return;
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock);
|
|
*out = gNetIrpStats;
|
|
KeReleaseSpinlock(&gNetIrpStatsLock, flags);
|
|
}
|
|
|
|
// Records that a public async socket request requested cancellation.
|
|
static void NetIrpStatsCancelRequested(void) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gNetIrpStatsLock);
|
|
gNetIrpStats.CancelRequested++;
|
|
KeReleaseSpinlock(&gNetIrpStatsLock, flags);
|
|
}
|
|
|
|
struct NET_ASYNC_SOCKET_REQUEST_OBJECT {
|
|
IO_ASYNC_REQUEST_OBJECT Async;
|
|
|
|
PROCESS* OwnerProcess;
|
|
SOCKET_OBJECT* Socket;
|
|
uint32_t Operation;
|
|
uint32_t SocketFlags;
|
|
uint32_t Backlog;
|
|
uint32_t ShutdownHow;
|
|
uint32_t Option;
|
|
uint32_t PollEvents;
|
|
|
|
void* KernelBuffer;
|
|
void* UserBuffer;
|
|
size_t Length;
|
|
|
|
SYS_SOCKET_ADDRESS_K Address;
|
|
size_t AddressSize;
|
|
SYS_SOCKET_ADDRESS_K* AddressOut;
|
|
size_t* AddressSizeInOut;
|
|
|
|
void* OptionValue;
|
|
void* UserOptionValue;
|
|
size_t OptionValueSize;
|
|
size_t* OptionActualSizeOut;
|
|
|
|
uint32_t* HandleOut;
|
|
uint32_t* PollReturnedEventsOut;
|
|
|
|
MM_LOCKED_USER_BUFFER* LockedAddressOut;
|
|
MM_LOCKED_USER_BUFFER* LockedAddressSize;
|
|
MM_LOCKED_USER_BUFFER* LockedOptionValue;
|
|
MM_LOCKED_USER_BUFFER* LockedOptionActualSize;
|
|
MM_LOCKED_USER_BUFFER* LockedHandleOut;
|
|
MM_LOCKED_USER_BUFFER* LockedPollReturnedEvents;
|
|
};
|
|
|
|
|
|
static SPINLOCK gAsyncSocketInitLock = SPINLOCK_INIT;
|
|
static int gAsyncSocketInitState;
|
|
static SPINLOCK gAsyncSocketQueueLock = SPINLOCK_INIT;
|
|
static LIST_ENTRY gAsyncSocketQueue;
|
|
static SEMAPHORE* gAsyncSocketSemaphore;
|
|
|
|
// Releases operation-specific user locks that the common async object does not own.
|
|
static void NetAsyncSocketReleaseExtraLocks(NET_ASYNC_SOCKET_REQUEST_OBJECT* req) {
|
|
if (!req)
|
|
return;
|
|
|
|
if (req->LockedAddressOut) {
|
|
MmUnlockUserBuffer(req->LockedAddressOut);
|
|
req->LockedAddressOut = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (req->LockedAddressSize) {
|
|
MmUnlockUserBuffer(req->LockedAddressSize);
|
|
req->LockedAddressSize = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (req->LockedOptionValue) {
|
|
MmUnlockUserBuffer(req->LockedOptionValue);
|
|
req->LockedOptionValue = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (req->LockedOptionActualSize) {
|
|
MmUnlockUserBuffer(req->LockedOptionActualSize);
|
|
req->LockedOptionActualSize = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (req->LockedHandleOut) {
|
|
MmUnlockUserBuffer(req->LockedHandleOut);
|
|
req->LockedHandleOut = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
if (req->LockedPollReturnedEvents) {
|
|
MmUnlockUserBuffer(req->LockedPollReturnedEvents);
|
|
req->LockedPollReturnedEvents = (MM_LOCKED_USER_BUFFER*)0;
|
|
}
|
|
}
|
|
|
|
// Releases heap storage no longer needed after a socket operation has finished.
|
|
static void NetAsyncSocketReleaseKernelStorage(NET_ASYNC_SOCKET_REQUEST_OBJECT* req) {
|
|
if (!req)
|
|
return;
|
|
|
|
if (req->KernelBuffer) {
|
|
MmFreePool(req->KernelBuffer);
|
|
req->KernelBuffer = (void*)0;
|
|
}
|
|
if (req->OptionValue) {
|
|
MmFreePool(req->OptionValue);
|
|
req->OptionValue = (void*)0;
|
|
}
|
|
}
|
|
|
|
// Releases socket-specific references owned by an async socket request.
|
|
static void NetAsyncSocketRequestCleanup(IO_ASYNC_REQUEST_OBJECT* async, void* context) {
|
|
(void)context;
|
|
NET_ASYNC_SOCKET_REQUEST_OBJECT* req =
|
|
(NET_ASYNC_SOCKET_REQUEST_OBJECT*)async;
|
|
|
|
NetAsyncSocketReleaseExtraLocks(req);
|
|
NetAsyncSocketReleaseKernelStorage(req);
|
|
|
|
if (req->Socket) {
|
|
ObDereferenceObject(&req->Socket->Header);
|
|
req->Socket = (SOCKET_OBJECT*)0;
|
|
}
|
|
if (req->OwnerProcess) {
|
|
ObDereferenceObject(&req->OwnerProcess->Header);
|
|
req->OwnerProcess = (PROCESS*)0;
|
|
}
|
|
}
|
|
|
|
// Cancels an async socket request from the shared async I/O handle.
|
|
static KSTATUS NetCancelAsyncSocketRequest(IO_ASYNC_REQUEST_OBJECT* async, void* context) {
|
|
(void)context;
|
|
if (!async)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
NetIrpStatsCancelRequested();
|
|
|
|
uintptr_t flags = KeAcquireSpinlock(&gAsyncSocketQueueLock);
|
|
IO_ASYNC_REQUEST_STATE state = IoAsyncRequestGetState(async);
|
|
|
|
if (state == IoAsyncRequestQueued) {
|
|
ListRemove(&async->Link);
|
|
IoAsyncRequestRequestCancel(async);
|
|
IoAsyncRequestSetState(async, IoAsyncRequestCancelled);
|
|
KeReleaseSpinlock(&gAsyncSocketQueueLock, flags);
|
|
|
|
IoCompleteAsyncRequest(async, STATUS_CANCELLED, 0);
|
|
ObDereferenceObject(&async->Header);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
if (state == IoAsyncRequestRunning) {
|
|
IoAsyncRequestRequestCancel(async);
|
|
KeReleaseSpinlock(&gAsyncSocketQueueLock, flags);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
KeReleaseSpinlock(&gAsyncSocketQueueLock, flags);
|
|
return STATUS_OBJECT_DEAD;
|
|
}
|
|
|
|
// Removes one queued async socket request for the worker.
|
|
static NET_ASYNC_SOCKET_REQUEST_OBJECT* NetAsyncSocketDequeue(void) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gAsyncSocketQueueLock);
|
|
LIST_ENTRY* node = ListPopHead(&gAsyncSocketQueue);
|
|
if (!node) {
|
|
KeReleaseSpinlock(&gAsyncSocketQueueLock, flags);
|
|
return (NET_ASYNC_SOCKET_REQUEST_OBJECT*)0;
|
|
}
|
|
|
|
NET_ASYNC_SOCKET_REQUEST_OBJECT* req =
|
|
(NET_ASYNC_SOCKET_REQUEST_OBJECT*)CONTAINING_RECORD(node, IO_ASYNC_REQUEST_OBJECT, Link);
|
|
if (IoAsyncRequestIsCancelRequested(&req->Async))
|
|
IoAsyncRequestSetState(&req->Async, IoAsyncRequestCancelled);
|
|
else
|
|
IoAsyncRequestSetState(&req->Async, IoAsyncRequestRunning);
|
|
KeReleaseSpinlock(&gAsyncSocketQueueLock, flags);
|
|
return req;
|
|
}
|
|
|
|
// Publishes a remote address output for accept and recvfrom.
|
|
static size_t NetAsyncSocketPublishAddress(NET_ASYNC_SOCKET_REQUEST_OBJECT* req,
|
|
const SYS_SOCKET_ADDRESS_K* address) {
|
|
if (!req || !req->AddressOut || !address)
|
|
return 0;
|
|
|
|
memcpy(req->AddressOut, address, sizeof(*address));
|
|
if (req->AddressSizeInOut)
|
|
*req->AddressSizeInOut = sizeof(*address);
|
|
return sizeof(*address);
|
|
}
|
|
|
|
// Executes one queued async socket operation on the worker thread.
|
|
static void NetAsyncSocketExecute(NET_ASYNC_SOCKET_REQUEST_OBJECT* req,
|
|
KSTATUS* statusOut,
|
|
size_t* informationOut) {
|
|
KSTATUS status = STATUS_INVALID_PARAMETER;
|
|
size_t information = 0;
|
|
|
|
if (!req || !statusOut || !informationOut)
|
|
return;
|
|
|
|
switch (req->Operation) {
|
|
case SYS_ASYNC_SOCKET_OP_BIND:
|
|
status = NetSocketBind(req->Socket, &req->Address, req->AddressSize);
|
|
break;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_LISTEN:
|
|
status = NetSocketListen(req->Socket, req->Backlog);
|
|
break;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_ACCEPT: {
|
|
SOCKET_OBJECT* accepted = (SOCKET_OBJECT*)0;
|
|
SYS_SOCKET_ADDRESS_K remote;
|
|
memset(&remote, 0, sizeof(remote));
|
|
status = NetSocketAccept(req->Socket, &accepted, req->AddressOut ? &remote : (SYS_SOCKET_ADDRESS_K*)0, &req->Async);
|
|
if (KSUCCESS(status)) {
|
|
uint32_t handle = 0;
|
|
status = KeAllocateHandleWithRights(req->OwnerProcess,
|
|
&accepted->Header,
|
|
HANDLE_RIGHT_READ | HANDLE_RIGHT_WRITE | HANDLE_RIGHT_QUERY,
|
|
&handle);
|
|
ObDereferenceObject(&accepted->Header);
|
|
if (KSUCCESS(status)) {
|
|
if (req->HandleOut)
|
|
*req->HandleOut = handle;
|
|
information = handle;
|
|
(void)NetAsyncSocketPublishAddress(req, &remote);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_CONNECT:
|
|
status = NetSocketConnect(req->Socket, &req->Address, req->AddressSize, &req->Async);
|
|
break;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_SEND: {
|
|
size_t sent = 0;
|
|
status = NetSocketSend(req->Socket, req->KernelBuffer, req->Length, req->SocketFlags, &sent);
|
|
information = sent;
|
|
break;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_RECV: {
|
|
size_t received = 0;
|
|
status = NetSocketReceive(req->Socket, req->KernelBuffer, req->Length, req->SocketFlags, &received, &req->Async);
|
|
if (KSUCCESS(status) && received)
|
|
memcpy(req->UserBuffer, req->KernelBuffer, received);
|
|
information = received;
|
|
break;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_SEND_TO: {
|
|
size_t sent = 0;
|
|
status = NetSocketSendTo(req->Socket, req->KernelBuffer, req->Length,
|
|
req->SocketFlags, &req->Address, req->AddressSize, &sent);
|
|
information = sent;
|
|
break;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_RECV_FROM: {
|
|
size_t received = 0;
|
|
SYS_SOCKET_ADDRESS_K remote;
|
|
memset(&remote, 0, sizeof(remote));
|
|
status = NetSocketReceiveFrom(req->Socket, req->KernelBuffer, req->Length,
|
|
req->SocketFlags,
|
|
req->AddressOut ? &remote : (SYS_SOCKET_ADDRESS_K*)0,
|
|
&received,
|
|
&req->Async);
|
|
if (KSUCCESS(status) && received)
|
|
memcpy(req->UserBuffer, req->KernelBuffer, received);
|
|
if (KSUCCESS(status))
|
|
(void)NetAsyncSocketPublishAddress(req, &remote);
|
|
information = received;
|
|
break;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_SHUTDOWN:
|
|
status = NetSocketShutdown(req->Socket, req->ShutdownHow);
|
|
break;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_SET_OPT:
|
|
status = NetSocketSetOption(req->Socket, req->Option, req->OptionValue, req->OptionValueSize);
|
|
break;
|
|
|
|
case SYS_ASYNC_SOCKET_OP_GET_OPT: {
|
|
size_t actual = 0;
|
|
status = NetSocketGetOption(req->Socket, req->Option, req->OptionValue, req->OptionValueSize, &actual);
|
|
if (KSUCCESS(status) && actual && req->UserOptionValue)
|
|
memcpy(req->UserOptionValue, req->OptionValue, actual);
|
|
if ((KSUCCESS(status) || status == STATUS_BUFFER_TOO_SMALL) && req->OptionActualSizeOut)
|
|
*req->OptionActualSizeOut = actual;
|
|
information = actual;
|
|
break;
|
|
}
|
|
|
|
case SYS_ASYNC_SOCKET_OP_POLL: {
|
|
uint32_t returnedEvents = 0;
|
|
status = NetSocketPoll(req->Socket, req->PollEvents, &returnedEvents);
|
|
if (req->PollReturnedEventsOut)
|
|
*req->PollReturnedEventsOut = returnedEvents;
|
|
information = returnedEvents;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
status = STATUS_INVALID_PARAMETER;
|
|
break;
|
|
}
|
|
|
|
*statusOut = status;
|
|
*informationOut = information;
|
|
}
|
|
|
|
// Worker for public async socket requests.
|
|
static void NetAsyncSocketWorker(void* arg) {
|
|
(void)arg;
|
|
|
|
for (;;) {
|
|
KSTATUS waitStatus = KeWaitSemaphore(gAsyncSocketSemaphore);
|
|
if (!KSUCCESS(waitStatus)) {
|
|
KeYield();
|
|
continue;
|
|
}
|
|
|
|
NET_ASYNC_SOCKET_REQUEST_OBJECT* req = NetAsyncSocketDequeue();
|
|
if (!req)
|
|
continue;
|
|
|
|
KSTATUS status = STATUS_CANCELLED;
|
|
size_t information = 0;
|
|
if (!IoAsyncRequestIsCancelRequested(&req->Async) &&
|
|
IoAsyncRequestGetState(&req->Async) != IoAsyncRequestCancelled) {
|
|
NetAsyncSocketExecute(req, &status, &information);
|
|
}
|
|
|
|
NetAsyncSocketReleaseExtraLocks(req);
|
|
NetAsyncSocketReleaseKernelStorage(req);
|
|
IoCompleteAsyncRequest(&req->Async, status, information);
|
|
ObDereferenceObject(&req->Async.Header);
|
|
}
|
|
}
|
|
|
|
// Starts the async socket worker on first use.
|
|
static KSTATUS NetEnsureAsyncSocketWorkerStarted(void) {
|
|
uintptr_t flags = KeAcquireSpinlock(&gAsyncSocketInitLock);
|
|
if (gAsyncSocketInitState == 2) {
|
|
KeReleaseSpinlock(&gAsyncSocketInitLock, flags);
|
|
return STATUS_SUCCESS;
|
|
}
|
|
if (gAsyncSocketInitState == 1) {
|
|
KeReleaseSpinlock(&gAsyncSocketInitLock, flags);
|
|
while (gAsyncSocketInitState == 1)
|
|
KeYield();
|
|
return (gAsyncSocketInitState == 2) ? STATUS_SUCCESS : STATUS_FAILURE;
|
|
}
|
|
|
|
gAsyncSocketInitState = 1;
|
|
KeReleaseSpinlock(&gAsyncSocketInitLock, flags);
|
|
|
|
LIST_INIT(&gAsyncSocketQueue);
|
|
|
|
KSTATUS status = KeSemaphoreCreate(0, 0x7fffffff, &gAsyncSocketSemaphore);
|
|
if (KSUCCESS(status)) {
|
|
THREAD* worker = (THREAD*)0;
|
|
status = KeCreateKernelThread((uintptr_t)NetAsyncSocketWorker,
|
|
0,
|
|
PriorityNormal,
|
|
&worker);
|
|
(void)worker;
|
|
}
|
|
|
|
flags = KeAcquireSpinlock(&gAsyncSocketInitLock);
|
|
gAsyncSocketInitState = KSUCCESS(status) ? 2 : 0;
|
|
KeReleaseSpinlock(&gAsyncSocketInitLock, flags);
|
|
return status;
|
|
}
|
|
|
|
// Submits a public async socket request to the socket worker.
|
|
KSTATUS NetSocketSubmitAsyncKernel(const NET_ASYNC_SOCKET_SUBMIT* submit,
|
|
NET_ASYNC_SOCKET_REQUEST_OBJECT** requestOut) {
|
|
if (requestOut)
|
|
*requestOut = (NET_ASYNC_SOCKET_REQUEST_OBJECT*)0;
|
|
if (!submit || !submit->OwnerProcess || !submit->Socket || !submit->Iosb)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
KSTATUS status = NetEnsureAsyncSocketWorkerStarted();
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
IO_ASYNC_REQUEST_OBJECT* async = (IO_ASYNC_REQUEST_OBJECT*)0;
|
|
status = IoCreateAsyncRequest(sizeof(NET_ASYNC_SOCKET_REQUEST_OBJECT),
|
|
NetAsyncSocketRequestCleanup,
|
|
(void*)0,
|
|
NetCancelAsyncSocketRequest,
|
|
(void*)0,
|
|
&async);
|
|
if (!KSUCCESS(status))
|
|
return status;
|
|
|
|
NET_ASYNC_SOCKET_REQUEST_OBJECT* req = (NET_ASYNC_SOCKET_REQUEST_OBJECT*)async;
|
|
req->OwnerProcess = submit->OwnerProcess;
|
|
req->Socket = submit->Socket;
|
|
req->Operation = submit->Operation;
|
|
req->SocketFlags = submit->SocketFlags;
|
|
req->Backlog = submit->Backlog;
|
|
req->ShutdownHow = submit->ShutdownHow;
|
|
req->Option = submit->Option;
|
|
req->PollEvents = submit->PollEvents;
|
|
req->KernelBuffer = submit->KernelBuffer;
|
|
req->UserBuffer = submit->UserBuffer;
|
|
req->Length = submit->Length;
|
|
req->Address = submit->Address;
|
|
req->AddressSize = submit->AddressSize;
|
|
req->AddressOut = submit->AddressOut;
|
|
req->AddressSizeInOut = submit->AddressSizeInOut;
|
|
req->OptionValue = submit->OptionValue;
|
|
req->UserOptionValue = submit->UserOptionValue;
|
|
req->OptionValueSize = submit->OptionValueSize;
|
|
req->OptionActualSizeOut = submit->OptionActualSizeOut;
|
|
req->HandleOut = submit->HandleOut;
|
|
req->PollReturnedEventsOut = submit->PollReturnedEventsOut;
|
|
req->LockedAddressOut = submit->LockedAddressOut;
|
|
req->LockedAddressSize = submit->LockedAddressSize;
|
|
req->LockedOptionValue = submit->LockedOptionValue;
|
|
req->LockedOptionActualSize = submit->LockedOptionActualSize;
|
|
req->LockedHandleOut = submit->LockedHandleOut;
|
|
req->LockedPollReturnedEvents = submit->LockedPollReturnedEvents;
|
|
|
|
ObReferenceObject(&req->OwnerProcess->Header);
|
|
ObReferenceObject(&req->Socket->Header);
|
|
IoInitializeAsyncRequestCompletion(&req->Async,
|
|
submit->Iosb,
|
|
submit->CompletionEvent,
|
|
submit->CompletionApc,
|
|
submit->LockedBuffer,
|
|
submit->LockedIosb);
|
|
|
|
ObReferenceObject(&req->Async.Header);
|
|
uintptr_t qflags = KeAcquireSpinlock(&gAsyncSocketQueueLock);
|
|
ListInsertTail(&gAsyncSocketQueue, &req->Async.Link);
|
|
KeReleaseSpinlock(&gAsyncSocketQueueLock, qflags);
|
|
KeSignalSemaphore(gAsyncSocketSemaphore);
|
|
|
|
if (requestOut)
|
|
*requestOut = req;
|
|
else
|
|
ObDereferenceObject(&req->Async.Header);
|
|
return STATUS_PENDING;
|
|
}
|
|
|
|
// Returns the object header embedded in a socket async request.
|
|
OBJECT* NetAsyncSocketRequestObject(NET_ASYNC_SOCKET_REQUEST_OBJECT* request) {
|
|
return request ? &request->Async.Header : (OBJECT*)0;
|
|
}
|
|
|
|
// Submits a socket provider request through the active stack DEVICE_OBJECT.
|
|
static KSTATUS NetSubmitSocketIrp(const NET_STACK_PROVIDER* provider,
|
|
NET_SOCKET_IRP_REQUEST* request,
|
|
IO_ASYNC_REQUEST_OBJECT* async) {
|
|
if (!provider || !provider->StackDevice || !request)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (request->Size != sizeof(*request))
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (request->Operation == NetSocketIrpInvalid ||
|
|
request->Operation >= NetSocketIrpOperationCount)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
request->CancelRequested = async ? &async->CancelRequested : (volatile uint8_t*)0;
|
|
|
|
EVENT* event = NULL;
|
|
KSTATUS st = KeEventCreate(EventAutoReset, 0, &event);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
IRP irp;
|
|
IoInitializeIrp(&irp, 1);
|
|
IoBuildDeviceIoControlIrp(&irp,
|
|
IRP_MJ_INTERNAL_DEVICE_CONTROL,
|
|
NET_IOCTL_SOCKET,
|
|
request,
|
|
sizeof(*request),
|
|
NULL,
|
|
0);
|
|
irp.CompletionEvent = event;
|
|
|
|
NetIrpStatsSubmit(request->Operation);
|
|
st = IoSubmitIrp(provider->StackDevice, &irp);
|
|
|
|
bool asynchronous = false;
|
|
if (st == STATUS_PENDING) {
|
|
asynchronous = true;
|
|
NetIrpStatsPending();
|
|
(void)KeWaitForEvent(event, 0);
|
|
st = irp.Status;
|
|
}
|
|
|
|
NetIrpStatsComplete(st, asynchronous);
|
|
ObDereferenceObject(&event->Header);
|
|
return st;
|
|
}
|
|
|
|
|
|
// Closes a provider socket through the IRP path while object teardown is still safe.
|
|
static void NetSocketCloseProvider(const NET_STACK_PROVIDER* provider, void* providerSocket) {
|
|
if (!provider || !providerSocket)
|
|
return;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpClose;
|
|
request.u.Close.Socket = providerSocket;
|
|
(void)NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Releases provider state owned by a process socket object.
|
|
static void NetSocketDestroy(OBJECT* object) {
|
|
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)object;
|
|
if (socket->Provider && socket->ProviderSocket) {
|
|
NetSocketCloseProvider(socket->Provider, socket->ProviderSocket);
|
|
socket->ProviderSocket = NULL;
|
|
}
|
|
}
|
|
|
|
// Allocates a process-visible socket object around provider-owned state.
|
|
static KSTATUS NetSocketAllocate(const NET_STACK_PROVIDER* provider,
|
|
void* providerSocket,
|
|
uint32_t domain,
|
|
uint32_t type,
|
|
uint32_t protocol,
|
|
SOCKET_OBJECT** out) {
|
|
if (!provider || !providerSocket || !out)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
SOCKET_OBJECT* socket = (SOCKET_OBJECT*)ObAllocateObject(ObjectTypeSocket, sizeof(SOCKET_OBJECT));
|
|
if (!socket)
|
|
return STATUS_NO_MEMORY;
|
|
|
|
socket->Header.Destroy = NetSocketDestroy;
|
|
socket->Provider = provider;
|
|
socket->ProviderSocket = providerSocket;
|
|
socket->Domain = domain;
|
|
socket->Type = type;
|
|
socket->Protocol = protocol;
|
|
*out = socket;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Returns the provider and provider-private state behind a live socket object.
|
|
static KSTATUS NetSocketGetProviderSocket(SOCKET_OBJECT* socket,
|
|
const NET_STACK_PROVIDER** providerOut,
|
|
void** providerSocketOut) {
|
|
if (!socket || !providerOut || !providerSocketOut)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (!socket->Provider || !socket->ProviderSocket)
|
|
return STATUS_OBJECT_DEAD;
|
|
|
|
*providerOut = socket->Provider;
|
|
*providerSocketOut = socket->ProviderSocket;
|
|
return STATUS_SUCCESS;
|
|
}
|
|
|
|
// Creates a socket by submitting a create request to the active stack device.
|
|
KSTATUS NetSocketCreate(uint32_t domain, uint32_t type, uint32_t protocol, SOCKET_OBJECT** out) {
|
|
if (!out)
|
|
return STATUS_INVALID_PARAMETER;
|
|
*out = NULL;
|
|
|
|
const NET_STACK_PROVIDER* provider = NetGetStackProvider();
|
|
if (!provider)
|
|
return STATUS_NOT_SUPPORTED;
|
|
|
|
void* providerSocket = NULL;
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpCreate;
|
|
request.u.Create.Domain = domain;
|
|
request.u.Create.Type = type;
|
|
request.u.Create.Protocol = protocol;
|
|
request.u.Create.ProviderSocketOut = &providerSocket;
|
|
|
|
KSTATUS st = NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
if (!providerSocket)
|
|
return STATUS_FAILURE;
|
|
|
|
st = NetSocketAllocate(provider, providerSocket, domain, type, protocol, out);
|
|
if (!KSUCCESS(st))
|
|
NetSocketCloseProvider(provider, providerSocket);
|
|
return st;
|
|
}
|
|
|
|
// Binds a socket address through the stack device IRP path.
|
|
KSTATUS NetSocketBind(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address, size_t addressSize) {
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpBind;
|
|
request.u.Bind.Socket = providerSocket;
|
|
request.u.Bind.Address = address;
|
|
request.u.Bind.AddressSize = addressSize;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Marks a stream socket as listening through the stack device IRP path.
|
|
KSTATUS NetSocketListen(SOCKET_OBJECT* socket, uint32_t backlog) {
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpListen;
|
|
request.u.Listen.Socket = providerSocket;
|
|
request.u.Listen.Backlog = backlog;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Accepts a provider socket through IRP and wraps the result in a process socket object.
|
|
KSTATUS NetSocketAccept(SOCKET_OBJECT* socket, SOCKET_OBJECT** acceptedOut,
|
|
SYS_SOCKET_ADDRESS_K* remoteOut,
|
|
IO_ASYNC_REQUEST_OBJECT* async) {
|
|
if (!acceptedOut)
|
|
return STATUS_INVALID_PARAMETER;
|
|
*acceptedOut = NULL;
|
|
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
void* acceptedProviderSocket = NULL;
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpAccept;
|
|
request.u.Accept.Socket = providerSocket;
|
|
request.u.Accept.AcceptedOut = &acceptedProviderSocket;
|
|
request.u.Accept.RemoteOut = remoteOut;
|
|
|
|
st = NetSubmitSocketIrp(provider, &request, async);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
if (!acceptedProviderSocket)
|
|
return STATUS_FAILURE;
|
|
|
|
st = NetSocketAllocate(provider,
|
|
acceptedProviderSocket,
|
|
socket->Domain,
|
|
socket->Type,
|
|
socket->Protocol,
|
|
acceptedOut);
|
|
if (!KSUCCESS(st))
|
|
NetSocketCloseProvider(provider, acceptedProviderSocket);
|
|
return st;
|
|
}
|
|
|
|
|
|
// Connects a socket through the stack device IRP path.
|
|
KSTATUS NetSocketConnect(SOCKET_OBJECT* socket, const SYS_SOCKET_ADDRESS_K* address,
|
|
size_t addressSize, IO_ASYNC_REQUEST_OBJECT* async) {
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpConnect;
|
|
request.u.Connect.Socket = providerSocket;
|
|
request.u.Connect.Address = address;
|
|
request.u.Connect.AddressSize = addressSize;
|
|
return NetSubmitSocketIrp(provider, &request, async);
|
|
}
|
|
|
|
|
|
// Sends stream bytes through the stack device IRP path.
|
|
KSTATUS NetSocketSend(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags, size_t* sentOut) {
|
|
if (sentOut)
|
|
*sentOut = 0;
|
|
if (!buffer && length)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (length > NET_SOCKET_IO_MAX_BYTES)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpSend;
|
|
request.u.Send.Socket = providerSocket;
|
|
request.u.Send.Buffer = buffer;
|
|
request.u.Send.Length = length;
|
|
request.u.Send.Flags = flags;
|
|
request.u.Send.SentOut = sentOut;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Receives stream bytes through the stack device IRP path.
|
|
KSTATUS NetSocketReceive(SOCKET_OBJECT* socket, void* buffer, size_t length,
|
|
uint32_t flags, size_t* receivedOut,
|
|
IO_ASYNC_REQUEST_OBJECT* async) {
|
|
if (receivedOut)
|
|
*receivedOut = 0;
|
|
if (!buffer && length)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (length > NET_SOCKET_IO_MAX_BYTES)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpReceive;
|
|
request.u.Receive.Socket = providerSocket;
|
|
request.u.Receive.Buffer = buffer;
|
|
request.u.Receive.Length = length;
|
|
request.u.Receive.Flags = flags;
|
|
request.u.Receive.ReceivedOut = receivedOut;
|
|
return NetSubmitSocketIrp(provider, &request, async);
|
|
}
|
|
|
|
|
|
// Sends datagram bytes through the stack device IRP path.
|
|
KSTATUS NetSocketSendTo(SOCKET_OBJECT* socket, const void* buffer, size_t length, uint32_t flags,
|
|
const SYS_SOCKET_ADDRESS_K* remote, size_t remoteSize, size_t* sentOut) {
|
|
if (sentOut)
|
|
*sentOut = 0;
|
|
if (!buffer && length)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (socket && socket->Type == SYS_SOCK_DGRAM && length > NET_SOCKET_UDP_MAX_BYTES)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (socket && socket->Type == SYS_SOCK_RAW && length > NET_SOCKET_RAW_MAX_BYTES)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (socket && socket->Type != SYS_SOCK_DGRAM && socket->Type != SYS_SOCK_RAW)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpSendTo;
|
|
request.u.SendTo.Socket = providerSocket;
|
|
request.u.SendTo.Buffer = buffer;
|
|
request.u.SendTo.Length = length;
|
|
request.u.SendTo.Flags = flags;
|
|
request.u.SendTo.Remote = remote;
|
|
request.u.SendTo.RemoteSize = remoteSize;
|
|
request.u.SendTo.SentOut = sentOut;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Receives datagram bytes through the stack device IRP path.
|
|
KSTATUS NetSocketReceiveFrom(SOCKET_OBJECT* socket, void* buffer, size_t length,
|
|
uint32_t flags, SYS_SOCKET_ADDRESS_K* remoteOut,
|
|
size_t* receivedOut, IO_ASYNC_REQUEST_OBJECT* async) {
|
|
if (receivedOut)
|
|
*receivedOut = 0;
|
|
if (!buffer && length)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (socket && socket->Type == SYS_SOCK_DGRAM && length > NET_SOCKET_UDP_MAX_BYTES)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (socket && socket->Type == SYS_SOCK_RAW && length > NET_SOCKET_RAW_MAX_BYTES)
|
|
return STATUS_INVALID_PARAMETER;
|
|
if (socket && socket->Type != SYS_SOCK_DGRAM && socket->Type != SYS_SOCK_RAW)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpReceiveFrom;
|
|
request.u.ReceiveFrom.Socket = providerSocket;
|
|
request.u.ReceiveFrom.Buffer = buffer;
|
|
request.u.ReceiveFrom.Length = length;
|
|
request.u.ReceiveFrom.Flags = flags;
|
|
request.u.ReceiveFrom.RemoteOut = remoteOut;
|
|
request.u.ReceiveFrom.ReceivedOut = receivedOut;
|
|
return NetSubmitSocketIrp(provider, &request, async);
|
|
}
|
|
|
|
|
|
// Shuts down one or both socket directions through the stack device IRP path.
|
|
KSTATUS NetSocketShutdown(SOCKET_OBJECT* socket, uint32_t how) {
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpShutdown;
|
|
request.u.Shutdown.Socket = providerSocket;
|
|
request.u.Shutdown.How = how;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Sets a socket option through the stack device IRP path.
|
|
KSTATUS NetSocketSetOption(SOCKET_OBJECT* socket, uint32_t option, const void* value, size_t valueSize) {
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpSetOption;
|
|
request.u.SetOption.Socket = providerSocket;
|
|
request.u.SetOption.Option = option;
|
|
request.u.SetOption.Value = value;
|
|
request.u.SetOption.ValueSize = valueSize;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Reads a socket option through the stack device IRP path.
|
|
KSTATUS NetSocketGetOption(SOCKET_OBJECT* socket, uint32_t option, void* value, size_t valueSize, size_t* actualSizeOut) {
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpGetOption;
|
|
request.u.GetOption.Socket = providerSocket;
|
|
request.u.GetOption.Option = option;
|
|
request.u.GetOption.Value = value;
|
|
request.u.GetOption.ValueSize = valueSize;
|
|
request.u.GetOption.ActualSizeOut = actualSizeOut;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Polls socket readiness through the stack device IRP path.
|
|
KSTATUS NetSocketPoll(SOCKET_OBJECT* socket, uint32_t events, uint32_t* returnedEventsOut) {
|
|
if (!returnedEventsOut)
|
|
return STATUS_INVALID_PARAMETER;
|
|
*returnedEventsOut = 0;
|
|
|
|
const NET_STACK_PROVIDER* provider = NULL;
|
|
void* providerSocket = NULL;
|
|
KSTATUS st = NetSocketGetProviderSocket(socket, &provider, &providerSocket);
|
|
if (!KSUCCESS(st))
|
|
return st;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpPoll;
|
|
request.u.Poll.Socket = providerSocket;
|
|
request.u.Poll.Events = events;
|
|
request.u.Poll.ReturnedEventsOut = returnedEventsOut;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|
|
|
|
// Resolves a host name through the stack device IRP path.
|
|
KSTATUS NetResolveHost(const char* name, uint32_t family, SYS_SOCKET_ADDRESS_K* addressOut) {
|
|
if (!name || !addressOut)
|
|
return STATUS_INVALID_PARAMETER;
|
|
|
|
const NET_STACK_PROVIDER* provider = NetGetStackProvider();
|
|
if (!provider)
|
|
return STATUS_NOT_SUPPORTED;
|
|
|
|
NET_SOCKET_IRP_REQUEST request;
|
|
memset(&request, 0, sizeof(request));
|
|
request.Size = sizeof(request);
|
|
request.Operation = NetSocketIrpResolveHost;
|
|
request.u.ResolveHost.Name = name;
|
|
request.u.ResolveHost.Family = family;
|
|
request.u.ResolveHost.AddressOut = addressOut;
|
|
return NetSubmitSocketIrp(provider, &request, (IO_ASYNC_REQUEST_OBJECT*)0);
|
|
}
|