Files
nt-lite/code/user/tests/net_irp_smoke/net_irp_smoke.c
T

348 lines
12 KiB
C

// User-mode smoke test for socket IRP dispatch, public async sockets, cancellation, and statistics.
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <ntlite/syscall.h>
#define EVENT_AUTO_RESET 0u
#define NET_IRP_SMOKE_UDP_PORT 40219u
#define NET_IRP_SMOKE_HTTP_PORT 80u
// Prints one NUL-terminated string to the kernel console.
static void print(const char* s) {
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromCstr(s) };
SysWriteConsole(&req);
}
// Prints one unsigned integer to the kernel console.
static void print_u64(uint64_t v) {
char buf[32];
int i = 0;
if (v == 0) {
print("0");
return;
}
while (v && i < (int)sizeof(buf)) {
buf[i++] = (char)('0' + (v % 10));
v /= 10;
}
while (i--) {
char c = buf[i];
SYS_WRITE_CONSOLE_REQUEST req = { SysSvFromParts(&c, 1) };
SysWriteConsole(&req);
}
}
// Terminates the current process.
static void exit_now(int32_t code) {
for (;;) SysExitProcess(code);
}
// Fails the test with one message.
static void fail(const char* message) {
print("net_irp_smoke: ");
print(message);
print("\n");
exit_now(1);
}
// Fails the test with one status value.
static void fail_status(const char* what, uint32_t status) {
print("net_irp_smoke: ");
print(what);
print(" status=");
print_u64(status);
print("\n");
exit_now(1);
}
// Reads the public network IRP statistics snapshot.
static SYS_NET_IRP_STATS query_stats(void) {
SYS_NET_IRP_STATS stats;
memset(&stats, 0, sizeof(stats));
uint32_t status = SysQueryKernelStats(SYS_KERNEL_STATS_NET_IRP, &stats, sizeof(stats), 0);
if (status != STATUS_SUCCESS)
fail_status("query net irp stats", status);
if (stats.Size != sizeof(stats))
fail("net irp stats size mismatch");
if (stats.Version != SYS_STATS_VERSION)
fail("net irp stats version mismatch");
return stats;
}
// Builds an IPv4 socket address.
static SYS_SOCKET_ADDRESS make_ipv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d, uint16_t port) {
SYS_SOCKET_ADDRESS address;
memset(&address, 0, sizeof(address));
address.Size = sizeof(address);
address.Family = SYS_AF_INET;
address.Port = port;
address.Address[0] = a;
address.Address[1] = b;
address.Address[2] = c;
address.Address[3] = d;
return address;
}
// Sets one uint32 socket option.
static void set_u32_option(uint32_t socket, uint32_t option, uint32_t value) {
SYS_SOCKET_OPTION_REQUEST req;
memset(&req, 0, sizeof(req));
req.Option = option;
req.Value = &value;
req.ValueSize = sizeof(value);
uint32_t status = SysSetSocketOption(socket, &req);
if (status != STATUS_SUCCESS)
fail_status("set socket option", status);
}
// Waits for an async socket request to publish a terminal IOSB status.
static void wait_for_async(uint32_t event, SYS_IO_STATUS_BLOCK* iosb, const char* what) {
for (uint32_t i = 0; i < 200u && iosb->Status == STATUS_PENDING; ++i) {
uint32_t status = SysWait(event, 0);
if (status != STATUS_SUCCESS && status != STATUS_ALERTED &&
status != STATUS_KERNEL_APC && status != STATUS_USER_APC) {
fail_status(what, status);
}
}
if (iosb->Status == STATUS_PENDING)
fail("async request stayed pending");
}
// Verifies nonblocking UDP receive returns would-block through the IRP path.
static void check_nonblocking_receive(void) {
uint32_t udp = 0;
uint32_t status = SysCreateSocket(SYS_AF_INET, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &udp);
if (status != STATUS_SUCCESS)
fail_status("create udp socket", status);
SYS_SOCKET_ADDRESS bindAddress = make_ipv4(0, 0, 0, 0, NET_IRP_SMOKE_UDP_PORT);
status = SysBindSocket(udp, &bindAddress, sizeof(bindAddress));
if (status != STATUS_SUCCESS) {
SysCloseHandle(udp);
fail_status("bind udp socket", status);
}
set_u32_option(udp, SYS_SOCKET_OPT_NONBLOCKING, 1);
char buffer[16];
SYS_SOCKET_ADDRESS remote;
size_t got = 0;
size_t remoteSize = sizeof(remote);
memset(buffer, 0, sizeof(buffer));
memset(&remote, 0, sizeof(remote));
SYS_SOCKET_RECV_FROM_REQUEST req;
memset(&req, 0, sizeof(req));
req.Buffer = buffer;
req.Length = sizeof(buffer);
req.RemoteAddressOut = &remote;
req.RemoteAddressSizeInOut = &remoteSize;
req.ReceivedOut = &got;
status = SysRecvFromSocket(udp, &req);
SysCloseHandle(udp);
if (status != STATUS_WOULD_BLOCK)
fail_status("nonblocking udp recvfrom", status);
}
// Verifies a pending async UDP receive can be cancelled and waited through its request handle.
static void check_async_receive_cancel(void) {
uint32_t udp = 0;
uint32_t status = SysCreateSocket(SYS_AF_INET, SYS_SOCK_DGRAM, SYS_IPPROTO_UDP, &udp);
if (status != STATUS_SUCCESS)
fail_status("create cancel udp socket", status);
SYS_SOCKET_ADDRESS bindAddress = make_ipv4(0, 0, 0, 0, NET_IRP_SMOKE_UDP_PORT + 1u);
status = SysBindSocket(udp, &bindAddress, sizeof(bindAddress));
if (status != STATUS_SUCCESS) {
SysCloseHandle(udp);
fail_status("bind cancel udp socket", status);
}
uint32_t event = 0;
status = SysCreateEvent(EVENT_AUTO_RESET, 0, &event);
if (status != STATUS_SUCCESS) {
SysCloseHandle(udp);
fail_status("create event", status);
}
char buffer[16];
SYS_SOCKET_ADDRESS remote;
size_t remoteSize = sizeof(remote);
uint32_t requestHandle = 0;
SYS_IO_STATUS_BLOCK iosb;
memset(buffer, 0, sizeof(buffer));
memset(&remote, 0, sizeof(remote));
iosb.Status = 0;
iosb.Information = 0;
SYS_ASYNC_SOCKET_REQUEST req;
memset(&req, 0, sizeof(req));
req.Size = sizeof(req);
req.SocketHandle = udp;
req.EventHandle = event;
req.Operation = SYS_ASYNC_SOCKET_OP_RECV_FROM;
req.Buffer = buffer;
req.Length = sizeof(buffer);
req.AddressOut = &remote;
req.AddressSizeInOut = &remoteSize;
req.Iosb = &iosb;
req.RequestHandleOut = &requestHandle;
SYS_NET_IRP_STATS beforeSubmit = query_stats();
status = SysSocketIoAsync(&req);
if (status != STATUS_PENDING || requestHandle == 0) {
SysCloseHandle(event);
SysCloseHandle(udp);
fail_status("submit async recvfrom", status);
}
uint32_t enteredIrp = 0;
for (uint32_t i = 0; i < 40u; ++i) {
SYS_NET_IRP_STATS now = query_stats();
if (now.ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM] >
beforeSubmit.ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM]) {
enteredIrp = 1;
break;
}
SysSleep(10);
}
if (!enteredIrp)
fail("async recvfrom did not enter the IRP path");
uint32_t cancelStatus = SysCancelIo(requestHandle);
if (cancelStatus != STATUS_SUCCESS && cancelStatus != STATUS_PENDING)
fail_status("cancel async recvfrom", cancelStatus);
wait_for_async(event, &iosb, "wait async recvfrom cancel");
if (iosb.Status != STATUS_CANCELLED)
fail_status("async recvfrom iosb", iosb.Status);
if (iosb.Information != 0)
fail("async recvfrom cancel transferred bytes");
status = SysWaitSingle(requestHandle, 0, 0);
if (status != STATUS_SUCCESS)
fail_status("wait cancelled request handle", status);
SysCloseHandle(requestHandle);
SysCloseHandle(event);
SysCloseHandle(udp);
}
// Tries a host-backed HTTP transfer to prove async send and receive success paths.
static void try_http_async_success(void) {
uint32_t tcp = 0;
uint32_t status = SysCreateSocket(SYS_AF_INET, SYS_SOCK_STREAM, SYS_IPPROTO_TCP, &tcp);
if (status != STATUS_SUCCESS) {
print("net_irp_smoke: http success path skipped create status=");
print_u64(status);
print("\n");
return;
}
SYS_SOCKET_ADDRESS address = make_ipv4(10, 0, 2, 2, NET_IRP_SMOKE_HTTP_PORT);
status = SysConnectSocket(tcp, &address, sizeof(address));
if (status != STATUS_SUCCESS) {
print("net_irp_smoke: http success path skipped connect status=");
print_u64(status);
print("\n");
SysCloseHandle(tcp);
return;
}
uint32_t event = 0;
status = SysCreateEvent(EVENT_AUTO_RESET, 0, &event);
if (status != STATUS_SUCCESS) {
SysCloseHandle(tcp);
fail_status("create http event", status);
}
static const char requestText[] = "GET / HTTP/1.0\r\nHost: 10.0.2.2\r\n\r\n";
SYS_IO_STATUS_BLOCK sendIosb;
uint32_t sendRequest = 0;
memset(&sendIosb, 0, sizeof(sendIosb));
SYS_ASYNC_SOCKET_REQUEST sendReq;
memset(&sendReq, 0, sizeof(sendReq));
sendReq.Size = sizeof(sendReq);
sendReq.SocketHandle = tcp;
sendReq.EventHandle = event;
sendReq.Operation = SYS_ASYNC_SOCKET_OP_SEND;
sendReq.Buffer = (void*)requestText;
sendReq.Length = sizeof(requestText) - 1u;
sendReq.Iosb = &sendIosb;
sendReq.RequestHandleOut = &sendRequest;
status = SysSocketIoAsync(&sendReq);
if (status != STATUS_PENDING || sendRequest == 0)
fail_status("submit async send", status);
wait_for_async(event, &sendIosb, "wait async send");
if (sendIosb.Status != STATUS_SUCCESS)
fail_status("async send iosb", sendIosb.Status);
if (sendIosb.Information != sizeof(requestText) - 1u)
fail("async send byte count mismatch");
SysCloseHandle(sendRequest);
char response[128];
SYS_IO_STATUS_BLOCK recvIosb;
uint32_t recvRequest = 0;
memset(response, 0, sizeof(response));
memset(&recvIosb, 0, sizeof(recvIosb));
SYS_ASYNC_SOCKET_REQUEST recvReq;
memset(&recvReq, 0, sizeof(recvReq));
recvReq.Size = sizeof(recvReq);
recvReq.SocketHandle = tcp;
recvReq.EventHandle = event;
recvReq.Operation = SYS_ASYNC_SOCKET_OP_RECV;
recvReq.Buffer = response;
recvReq.Length = sizeof(response) - 1u;
recvReq.Iosb = &recvIosb;
recvReq.RequestHandleOut = &recvRequest;
status = SysSocketIoAsync(&recvReq);
if (status != STATUS_PENDING || recvRequest == 0)
fail_status("submit async recv", status);
wait_for_async(event, &recvIosb, "wait async recv");
if (recvIosb.Status != STATUS_SUCCESS)
fail_status("async recv iosb", recvIosb.Status);
if (recvIosb.Information == 0)
fail("async recv returned no bytes");
SysCloseHandle(recvRequest);
SysCloseHandle(event);
SysCloseHandle(tcp);
}
// Verifies counters prove socket requests entered and completed through the IRP path.
static void check_stats_delta(const SYS_NET_IRP_STATS* before) {
SYS_NET_IRP_STATS after = query_stats();
if (after.Submitted <= before->Submitted)
fail("net irp submitted counter did not advance");
if (after.Completed <= before->Completed)
fail("net irp completed counter did not advance");
if (after.WouldBlock <= before->WouldBlock)
fail("net irp would-block counter did not advance");
if (after.CancelRequested <= before->CancelRequested)
fail("net irp cancel-request counter did not advance");
if (after.Cancelled <= before->Cancelled)
fail("net irp cancelled counter did not advance");
if (after.ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM] <= before->ByOperation[SYS_NET_IRP_OP_RECEIVE_FROM])
fail("net irp recvfrom operation counter did not advance");
}
// Runs the network IRP smoke test.
int main(void) {
print("net_irp_smoke: start\n");
SYS_NET_IRP_STATS before = query_stats();
check_nonblocking_receive();
check_async_receive_cancel();
try_http_async_success();
check_stats_delta(&before);
print("net_irp_smoke: PASS\n");
exit_now(0);
return 0;
}