Files
raven/native/nt/winsock_api.cpp
Clansty 24d09e747b feat: answer getprotobyname and getprotobynumber from the protocols database
- Both calls read the host's protocols database into one PROTOENT the process reuses, as Winsock reuses its per-thread one: the name, an empty list of aliases and the protocol number.

- The record keeps its pointers, so the 32-bit and 64-bit layouts are written apart; a protocol the database does not list answers NULL with WSANO_DATA, as the services calls beside them do.

- The new guest fixture protocol_client.c checks that both spellings of a protocol name the same record, that a second call reuses it, and that an unlisted name and an unlisted number are refused. It is built for 64-bit and 32-bit callers.

Assisted-by: Claude Code:deepseek-v4.1-flash-1017

AI-Scope: Wrote the two handlers next to the existing getservbyname ones, the PROTOENT writer for both pointer widths, and the guest fixture; checked with ctest (native_protocol_database_client, native_protocol_database_client_x32).
2026-11-02 11:59:35 +08:00

2412 lines
95 KiB
C++

#include "child_process.h"
#include "completion_port.h"
#include "kernel_context.h"
#include <algorithm>
#include "text.h"
#include <arpa/inet.h>
#include <array>
#include <cerrno>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <map>
#include <sstream>
#include <tuple>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
namespace raven {
namespace {
// A socket is a host descriptor that stays non-blocking inside Raven: a blocking call waits on the host without stopping other guest
// threads, and whether the guest sees blocking behavior is a property of the object.
class SocketObject : public KernelObject {
public:
std::u16string_view type_name() const override { return u"File"; }
explicit SocketObject(int descriptor) : descriptor(descriptor) {}
~SocketObject() override { ::close(descriptor); }
int signaled(uint32_t) const override { return 1; }
int descriptor;
bool guest_nonblocking = false;
// Set when the program closed the handle while transfers on it were still pending: they end as aborted.
bool closed = false;
// A non-blocking connect is under way: select reports its success as writable and its failure as an exception.
bool connecting = false;
int type = SOCK_STREAM;
std::shared_ptr<CompletionPort> port;
uint64_t key = 0;
// SetFileCompletionNotificationModes: no packet for a transfer that finishes at once, and no event set for it.
bool skip_port_on_success = false, skip_event_on_success = false;
// WSAEventSelect: the event to set, the network events asked for, the ones that may be reported again, and the ones recorded
// since the program last enumerated them, with their error codes (WSANETWORKEVENTS order).
uint64_t select_event = 0;
uint32_t select_mask = 0, select_enabled = 0, select_recorded = 0;
std::array<int32_t, 10> select_errors{};
};
constexpr uint32_t wsaeintr = 10004, wsaebadf = 10009, wsaeacces = 10013, wsaefault = 10014, wsaeinval = 10022, wsaemfile = 10024,
wsaewouldblock = 10035, wsaeinprogress = 10036, wsaealready = 10037, wsaenotsock = 10038, wsaedestaddrreq = 10039, wsaemsgsize = 10040,
wsaeprototype = 10041, wsaenoprotoopt = 10042, wsaeprotonosupport = 10043, wsaesocktnosupport = 10044, wsaeopnotsupp = 10045,
wsaeafnosupport = 10047, wsaeaddrinuse = 10048, wsaeaddrnotavail = 10049, wsaenetdown = 10050, wsaenetunreach = 10051,
wsaeconnaborted = 10053, wsaeconnreset = 10054, wsaenobufs = 10055, wsaeisconn = 10056, wsaenotconn = 10057, wsaeshutdown = 10058,
wsaetimedout = 10060, wsaeconnrefused = 10061, wsaehostunreach = 10065, wsanotinitialised = 10093, wsahost_not_found = 11001;
constexpr uint64_t status_pending = 0x103, overlapped_error = 0xe0000000;
constexpr uint32_t wsa_io_incomplete = 996;
constexpr uint32_t fd_read = 0x1, fd_write = 0x2, fd_oob = 0x4, fd_accept = 0x8, fd_connect = 0x10, fd_close = 0x20;
uint32_t wsa_error(int host)
{
switch (host) {
case EINTR: return wsaeintr;
case EBADF: return wsaenotsock;
case EACCES: case EPERM: return wsaeacces;
case EFAULT: return wsaefault;
case EINVAL: return wsaeinval;
case EMFILE: case ENFILE: return wsaemfile;
case EAGAIN: return wsaewouldblock;
case EINPROGRESS: return wsaeinprogress;
case EALREADY: return wsaealready;
case ENOTSOCK: return wsaenotsock;
case EDESTADDRREQ: return wsaedestaddrreq;
case EMSGSIZE: return wsaemsgsize;
case EPROTOTYPE: return wsaeprototype;
case ENOPROTOOPT: return wsaenoprotoopt;
case EPROTONOSUPPORT: return wsaeprotonosupport;
case ESOCKTNOSUPPORT: return wsaesocktnosupport;
case EOPNOTSUPP: return wsaeopnotsupp;
case EAFNOSUPPORT: return wsaeafnosupport;
case EADDRINUSE: return wsaeaddrinuse;
case EADDRNOTAVAIL: return wsaeaddrnotavail;
case ENETDOWN: return wsaenetdown;
case ENETUNREACH: return wsaenetunreach;
case ECONNABORTED: case EPIPE: return wsaeconnaborted;
case ECONNRESET: return wsaeconnreset;
case ENOBUFS: case ENOMEM: return wsaenobufs;
case EISCONN: return wsaeisconn;
case ENOTCONN: return wsaenotconn;
case ESHUTDOWN: return wsaeshutdown;
case ETIMEDOUT: return wsaetimedout;
case ECONNREFUSED: return wsaeconnrefused;
case EHOSTUNREACH: return wsaehostunreach;
default: return wsaeinval;
}
}
// RAVEN_TRACE_SOCKETS: how many bytes a socket received, from whom and when, without the content.
void trace_received(int descriptor, ssize_t count, const char *path)
{
static const bool traced = std::getenv("RAVEN_TRACE_SOCKETS");
if (!traced || count <= 0)
return;
sockaddr_storage peer{};
socklen_t size = sizeof(peer);
char text[INET6_ADDRSTRLEN] = "?";
uint16_t port = 0;
if (!::getpeername(descriptor, reinterpret_cast<sockaddr *>(&peer), &size)) {
if (peer.ss_family == AF_INET) {
const auto &address = reinterpret_cast<const sockaddr_in &>(peer);
::inet_ntop(AF_INET, &address.sin_addr, text, sizeof(text));
port = ntohs(address.sin_port);
} else if (peer.ss_family == AF_INET6) {
const auto &address = reinterpret_cast<const sockaddr_in6 &>(peer);
::inet_ntop(AF_INET6, &address.sin6_addr, text, sizeof(text));
port = ntohs(address.sin6_port);
}
}
std::fprintf(stderr, "raven: socket %d received %zd bytes from %s:%u at %lld ms (%s)\n", descriptor, count, text, port,
(long long)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count(), path);
}
constexpr uint16_t windows_inet6 = 23;
int host_family(int family) { return family == windows_inet6 ? AF_INET6 : family; }
int windows_family(int family) { return family == AF_INET6 ? windows_inet6 : family; }
// A sockaddr as the host wants it: the family number is the only difference from the Windows layout.
std::vector<uint8_t> host_address(ApiCall &call, uint64_t address, size_t length)
{
auto bytes = call.read(address, length);
if (bytes.size() >= 2) {
uint16_t family;
std::memcpy(&family, bytes.data(), 2);
family = uint16_t(host_family(family));
std::memcpy(bytes.data(), &family, 2);
}
return bytes;
}
void put_address(ApiCall &call, uint64_t address, uint64_t length_address, const sockaddr_storage &storage, socklen_t length)
{
if (!address || !length_address)
return;
const uint32_t capacity = call.load<uint32_t>(length_address);
std::vector<uint8_t> bytes(reinterpret_cast<const uint8_t *>(&storage), reinterpret_cast<const uint8_t *>(&storage) + length);
if (bytes.size() >= 2) {
uint16_t family;
std::memcpy(&family, bytes.data(), 2);
family = uint16_t(windows_family(family));
std::memcpy(bytes.data(), &family, 2);
}
call.write(address, std::span<const uint8_t>(bytes.data(), std::min<size_t>(bytes.size(), capacity)));
call.store<uint32_t>(length_address, uint32_t(length));
}
constexpr uint32_t sol_socket_windows = 0xffff;
std::vector<uint8_t> message_control(ApiCall &call, int descriptor, uint64_t address, uint32_t length)
{
if (length > 65536) throw wsaenobufs;
if (length && !address) throw wsaefault;
const auto raw = call.read(address, length);
const size_t width = call.pointer_width(), header = width + 8;
std::vector<uint8_t> result;
for (size_t at = 0; at < raw.size();) {
if (raw.size() - at < header) throw wsaeinval;
uint64_t size = 0;
uint32_t level = 0, type = 0;
std::memcpy(&size, raw.data() + at, width);
std::memcpy(&level, raw.data() + at + width, 4);
std::memcpy(&type, raw.data() + at + width + 4, 4);
if (size < header || size > raw.size() - at) throw wsaeinval;
const auto *data = raw.data() + at + header;
const size_t count = size_t(size) - header;
int host_type = 0;
std::vector<uint8_t> payload;
if (level == IPPROTO_IP && type == 19) {
if (count != 8) throw wsaeinval;
in_pktinfo info{};
std::memcpy(&info.ipi_spec_dst, data, 4);
std::memcpy(&info.ipi_ifindex, data + 4, 4);
payload.resize(sizeof(info));
std::memcpy(payload.data(), &info, sizeof(info));
host_type = IP_PKTINFO;
} else if (level == IPPROTO_IPV6 && type == 19) {
if (count != sizeof(in6_pktinfo)) throw wsaeinval;
payload.assign(data, data + count);
host_type = IPV6_PKTINFO;
} else if ((level == IPPROTO_IP || level == IPPROTO_IPV6) && type == 50) {
if (count != 4) throw wsaeinval;
uint32_t ecn = 0;
std::memcpy(&ecn, data, 4);
if (ecn > 3) throw wsaeinval;
int setting = 0;
socklen_t setting_size = sizeof(setting);
host_type = level == IPPROTO_IP ? IP_TOS : IPV6_TCLASS;
if (::getsockopt(descriptor, int(level), host_type, &setting, &setting_size)) throw wsa_error(errno);
setting = (setting & ~3) | int(ecn);
payload.resize(sizeof(setting));
std::memcpy(payload.data(), &setting, sizeof(setting));
} else if (level == IPPROTO_IPV6 && type == 21) {
if (count != 4) throw wsaeinval;
payload.assign(data, data + count);
host_type = IPV6_HOPLIMIT;
} else {
throw wsaenoprotoopt;
}
const size_t offset = result.size();
result.resize(offset + CMSG_SPACE(payload.size()), 0);
auto *control = reinterpret_cast<cmsghdr *>(result.data() + offset);
control->cmsg_level = int(level);
control->cmsg_type = host_type;
control->cmsg_len = CMSG_LEN(payload.size());
std::memcpy(CMSG_DATA(control), payload.data(), payload.size());
const size_t next = (at + size_t(size) + width - 1) & ~(width - 1);
if (next > raw.size()) {
if (at + size == raw.size()) break;
throw wsaeinval;
}
at = next;
}
return result;
}
}
// One TCP endpoint as the host lists it, with the process that holds the socket when it is a process of this runtime.
struct TcpEntry {
bool v6 = false;
std::array<uint8_t, 16> local{}, remote{};
uint32_t local_port = 0, remote_port = 0, state = 0, pid = 0;
};
// Linux socket states in /proc/net/tcp order and the MIB_TCP_STATE values Windows reports for them.
// The AFD endpoint: \Device\Afd opened as a file, whose IOCTL_AFD_POLL waits until any of the sockets named becomes ready for one
// of the events asked for. This is the readiness poll the event loops of mio (and so Tokio), wepoll and libuv are built on; its
// structures are the ones those open-source projects define. A poll pends in the reactor and finishes as overlapped I/O does: the
// output records and the IO_STATUS_BLOCK, the event, and a packet with the APC context for the completion port the endpoint belongs to.
class AfdEndpoint : public KernelObject {
public:
std::u16string_view type_name() const override { return u"File"; }
int signaled(uint32_t) const override { return 1; }
struct Poll {
uint64_t status_block = 0;
bool cancelled = false, done = false;
};
std::shared_ptr<CompletionPort> port;
uint64_t key = 0;
std::vector<std::shared_ptr<Poll>> polls;
};
std::shared_ptr<KernelObject> make_afd_endpoint() { return std::make_shared<AfdEndpoint>(); }
namespace {
// AFD_POLL_* events.
constexpr uint32_t afd_receive = 0x1, afd_receive_expedited = 0x2, afd_send = 0x4, afd_disconnect = 0x8, afd_abort = 0x10,
afd_local_close = 0x20, afd_accept = 0x80, afd_connect_fail = 0x100;
// What a host poll result means in AFD's events for this socket.
uint32_t afd_events(SocketObject &socket, short ready)
{
uint32_t events = 0;
if (ready & POLLIN) {
int listening = 0;
socklen_t length = sizeof(listening);
::getsockopt(socket.descriptor, SOL_SOCKET, SO_ACCEPTCONN, &listening, &length);
events |= listening ? afd_accept : afd_receive;
}
if (ready & POLLPRI)
events |= afd_receive_expedited;
if (ready & POLLERR)
events |= socket.connecting ? afd_connect_fail : afd_abort;
else if (ready & POLLOUT) {
events |= afd_send;
socket.connecting = false;
}
if (ready & (POLLRDHUP | POLLHUP))
events |= afd_disconnect;
return events;
}
}
bool afd_device_control(KernelContext &context, ApiCall &call)
{
const auto endpoint = context.objects.object_as<AfdEndpoint>(call[0]);
if (!endpoint)
return false;
constexpr uint32_t ioctl_afd_poll = 0x12024, status_pending = 0x103, status_cancelled = 0xc0000120U;
if (call.u32(5) != ioctl_afd_poll) {
call.ret(0xc0000010U);
return true;
}
// AFD_POLL_INFO: the timeout, the count of handles, the exclusive flag, then one record per socket: its handle, the events, a status.
const size_t width = call.pointer_width(), record = width + 8, header = 16;
const uint64_t input = call[6], output = call[8], status_block = call[4];
if (!input || !output || !status_block || call[2]) {
call.ret(call[2] ? 0xc00000bbU : 0xc000000dU);
return true;
}
const uint32_t count = call.load<uint32_t>(input + 8);
if (!count || call.u32(7) < header + count * record || call.u32(9) < header + count * record) {
call.ret(0xc000000dU);
return true;
}
struct Watched {
uint64_t handle;
uint32_t events;
std::shared_ptr<SocketObject> socket;
};
std::vector<Watched> watched;
for (uint32_t index = 0; index < count; ++index) {
const uint64_t at = input + header + index * record;
const uint64_t handle = width == 8 ? call.load<uint64_t>(at) : call.load<uint32_t>(at);
const auto socket = context.objects.object_as<SocketObject>(handle);
if (!socket) {
call.ret(0xc0000008U);
return true;
}
watched.push_back({handle, call.load<uint32_t>(at + width), socket});
}
// The timeout: zero to look once, negative for an interval in 100-nanosecond units, the largest value for none; a positive value
// is a system time.
const int64_t timeout = call.load<int64_t>(input);
const auto now_ticks = [] {
using namespace std::chrono;
return int64_t(duration_cast<nanoseconds>(system_clock::now().time_since_epoch()).count() / 100) + 116444736000000000LL;
};
const int64_t deadline = timeout == INT64_MAX ? INT64_MAX : timeout < 0 ? now_ticks() - timeout : timeout;
const uint32_t exclusive = call.load<uint32_t>(input + 12);
auto poll = std::make_shared<AfdEndpoint::Poll>();
poll->status_block = status_block;
endpoint->polls.push_back(poll);
auto &process = context.process;
auto &objects = context.objects;
const uint64_t event = call[1], apc_context = call[3];
const auto put = [&process](uint64_t address, uint64_t value, size_t size) {
process.write_memory(address, std::span<const uint8_t>(reinterpret_cast<const uint8_t *>(&value), size));
};
put(status_block, status_pending, width);
put(status_block + width, 0, width);
const auto attempt = [=, &process, &objects, endpoint = std::weak_ptr<AfdEndpoint>(endpoint)]() {
const auto owner = endpoint.lock();
if (poll->done)
return true;
std::vector<pollfd> descriptors;
for (const auto &item : watched)
descriptors.push_back({item.socket->closed ? -1 : item.socket->descriptor, short(POLLIN | POLLPRI | POLLOUT | POLLRDHUP), 0});
::poll(descriptors.data(), descriptors.size(), 0);
std::vector<std::pair<uint64_t, uint32_t>> ready;
for (size_t index = 0; index < watched.size(); ++index) {
const auto &item = watched[index];
const uint32_t events = (item.socket->closed ? afd_local_close : afd_events(*item.socket, descriptors[index].revents)) & item.events;
if (events)
ready.emplace_back(item.handle, events);
}
const bool cancelled = poll->cancelled || !owner;
if (ready.empty() && !cancelled && deadline != INT64_MAX && now_ticks() < deadline)
return false;
if (ready.empty() && !cancelled && deadline == INT64_MAX)
return false;
if (cancelled)
ready.clear();
std::vector<uint8_t> bytes(header + ready.size() * record);
std::memcpy(bytes.data(), &timeout, 8);
const uint32_t reported = uint32_t(ready.size());
std::memcpy(bytes.data() + 8, &reported, 4);
std::memcpy(bytes.data() + 12, &exclusive, 4);
for (size_t index = 0; index < ready.size(); ++index) {
std::memcpy(bytes.data() + header + index * record, &ready[index].first, width);
std::memcpy(bytes.data() + header + index * record + width, &ready[index].second, 4);
}
process.write_memory(output, bytes);
const uint32_t status = cancelled ? status_cancelled : 0;
put(status_block + width, bytes.size(), width);
put(status_block, status, width);
if (event)
objects.set_event(event, true);
if (owner && owner->port)
owner->port->post({bytes.size(), owner->key, apc_context, cancelled ? 995u : 0u});
poll->done = true;
if (owner)
std::erase(owner->polls, poll);
return true;
};
context.reactor->add(attempt, watched.front().socket->descriptor, short(POLLIN | POLLPRI | POLLOUT | POLLRDHUP));
call.ret(status_pending);
return true;
}
std::optional<bool> afd_cancel(KernelContext &context, uint64_t handle, uint64_t status_block)
{
const auto endpoint = context.objects.object_as<AfdEndpoint>(handle);
if (!endpoint)
return std::nullopt;
bool found = false;
for (const auto &poll : endpoint->polls)
if (!status_block || poll->status_block == status_block) {
poll->cancelled = true;
found = true;
}
context.reactor->poll();
return found;
}
bool set_socket_completion_modes(NativeObjects &objects, uint64_t handle, uint32_t modes)
{
// An endpoint's polls always pend, so the modes change nothing for it.
if (objects.object_as<AfdEndpoint>(handle))
return true;
const auto socket = objects.object_as<SocketObject>(handle);
if (!socket)
return false;
socket->skip_port_on_success = modes & 1;
socket->skip_event_on_success = modes & 2;
return true;
}
static uint32_t windows_tcp_state(unsigned host)
{
static constexpr uint32_t states[] = {0, 5, 3, 4, 6, 7, 11, 1, 8, 10, 2, 9};
return host < std::size(states) ? states[host] : 1;
}
// The sockets of the processes this user runs, by inode: the owner a TCP table names.
static std::map<uint64_t, uint32_t> socket_owners()
{
std::map<uint64_t, uint32_t> owners;
std::error_code error;
for (const auto &process : std::filesystem::directory_iterator("/proc", error)) {
const auto name = process.path().filename().string();
if (name.empty() || !std::all_of(name.begin(), name.end(), [](char c) { return c >= '0' && c <= '9'; }))
continue;
std::error_code inner;
for (const auto &descriptor : std::filesystem::directory_iterator(process.path() / "fd", inner)) {
char target[64];
const auto length = ::readlink(descriptor.path().c_str(), target, sizeof(target) - 1);
if (length <= 8)
continue;
target[length] = 0;
uint64_t inode = 0;
if (std::sscanf(target, "socket:[%lu]", &inode) == 1)
owners.emplace(inode, uint32_t(std::stoul(name)));
}
}
return owners;
}
static std::vector<TcpEntry> tcp_entries(bool v6)
{
std::vector<TcpEntry> entries;
std::ifstream table(v6 ? "/proc/net/tcp6" : "/proc/net/tcp");
std::string line;
std::getline(table, line);
std::map<uint64_t, uint32_t> owners;
bool owners_read = false;
while (std::getline(table, line)) {
std::istringstream fields(line);
std::string slot, local, remote, state, queues, timer, retransmits, uid, timeout, inode;
if (!(fields >> slot >> local >> remote >> state >> queues >> timer >> retransmits >> uid >> timeout >> inode))
continue;
TcpEntry entry;
entry.v6 = v6;
const auto address = [&](const std::string &text, std::array<uint8_t, 16> &out, uint32_t &port) {
const auto colon = text.find(':');
const auto hex = text.substr(0, colon);
// The kernel prints each 32-bit word of the address in host order.
for (size_t word = 0; word * 8 < hex.size() && word < 4; ++word) {
const uint32_t value = uint32_t(std::stoul(hex.substr(word * 8, 8), nullptr, 16));
std::memcpy(out.data() + word * 4, &value, 4);
}
port = uint32_t(std::stoul(text.substr(colon + 1), nullptr, 16));
};
address(local, entry.local, entry.local_port);
address(remote, entry.remote, entry.remote_port);
entry.state = windows_tcp_state(unsigned(std::stoul(state, nullptr, 16)));
const uint64_t node = std::stoull(inode);
if (node) {
if (!owners_read) {
owners = socket_owners();
owners_read = true;
}
if (const auto found = owners.find(node); found != owners.end())
entry.pid = found->second;
}
entries.push_back(entry);
}
return entries;
}
void register_winsock_api(ApiRegistry &registry, KernelContext &context)
{
auto &objects = context.objects;
auto &threads = context.threads;
const auto socket_of = [&objects](uint64_t handle) { return objects.object_as<SocketObject>(handle); };
const auto fail = [](ApiCall &call, uint32_t error, uint64_t value = UINT64_MAX) {
static const bool traced = std::getenv("RAVEN_TRACE_SOCKETS");
if (traced && error != 10035)
std::fprintf(stderr, "raven: %s on socket %llx fails with %u\n", api_in_progress, (unsigned long long)call[0], error);
call.fail(error, value);
};
// Waits until the descriptor is ready for the events or the time is up; true when it is ready. Other guest threads run meanwhile.
const auto wait_ready = [&threads](int descriptor, short events, std::optional<std::chrono::steady_clock::time_point> wake) {
const auto ready = [descriptor, events] {
pollfd entry{descriptor, events, 0};
return ::poll(&entry, 1, 0) > 0;
};
if (ready())
return true;
HostWait waiting;
return threads.block(ready, wake);
};
const auto make_socket = [&objects](ApiCall &call, int family, int type, int protocol) -> uint64_t {
const int descriptor = ::socket(host_family(family), type | SOCK_NONBLOCK | SOCK_CLOEXEC, protocol);
if (descriptor < 0) {
call.fail(wsa_error(errno), UINT64_MAX);
return UINT64_MAX;
}
auto object = std::make_shared<SocketObject>(descriptor);
object->type = type;
const auto made = objects.add_object(object);
if (made.status) {
call.fail(wsaemfile, UINT64_MAX);
return UINT64_MAX;
}
return made.value;
};
registry.add("socket", 3, [make_socket](ApiCall &call) { call.ret(make_socket(call, call.i32(0), call.i32(1), call.i32(2))); });
const auto make_wsa = [make_socket](ApiCall &call) {
int family = call.i32(0) ? call.i32(0) : AF_INET, type = call.i32(1) ? call.i32(1) : SOCK_STREAM, protocol = call.i32(2);
// FROM_PROTOCOL_INFO (-1) takes the value from the WSAPROTOCOL_INFO given, as WSAEnumProtocols describes the provider.
if (call[3]) {
if (family == -1)
family = call.load<int32_t>(call[3] + 76);
if (type == -1)
type = call.load<int32_t>(call[3] + 88);
if (protocol == -1)
protocol = call.load<int32_t>(call[3] + 92);
}
call.ret(make_socket(call, family, type, protocol));
};
registry.add("WSASocketA", 6, make_wsa);
registry.add("WSASocketW", 6, make_wsa);
registry.add("closesocket", 1, [&objects, &context, socket_of, fail](ApiCall &call) {
if (!socket_of(call[0])) {
fail(call, wsaenotsock, UINT64_MAX);
return;
}
socket_of(call[0])->closed = true;
objects.close(call[0]);
// Transfers still pending on the socket end as aborted now: a busy process may not look at them again until their descriptor
// turns ready, which for a quiet connection can be the server's own timeout.
context.reactor->poll();
call.ret(0);
});
registry.add("shutdown", 2, [socket_of, fail](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
static constexpr int how[] = {SHUT_RD, SHUT_WR, SHUT_RDWR};
if (call.u32(1) > 2 || ::shutdown(socket->descriptor, how[call.u32(1)])) {
fail(call, call.u32(1) > 2 ? wsaeinval : wsa_error(errno));
return;
}
call.ret(0);
});
registry.add("bind", 3, [socket_of, fail](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || call.i32(2) < 2) {
fail(call, socket ? wsaefault : wsaenotsock);
return;
}
const auto address = host_address(call, call[1], size_t(call.i32(2)));
if (::bind(socket->descriptor, reinterpret_cast<const sockaddr *>(address.data()), socklen_t(address.size()))) {
fail(call, wsa_error(errno));
return;
}
call.ret(0);
});
registry.add("listen", 2, [socket_of, fail](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || ::listen(socket->descriptor, call.i32(1))) {
fail(call, socket ? wsa_error(errno) : wsaenotsock);
return;
}
call.ret(0);
});
registry.add("connect", 3, [socket_of, fail, wait_ready](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || call.i32(2) < 2) {
fail(call, socket ? wsaefault : wsaenotsock);
return;
}
const auto address = host_address(call, call[1], size_t(call.i32(2)));
if (std::getenv("RAVEN_TRACE_SOCKETS") && address.size() >= sizeof(sockaddr_in) && reinterpret_cast<const sockaddr *>(address.data())->sa_family == AF_INET) {
const auto *target = reinterpret_cast<const sockaddr_in *>(address.data());
char text[INET_ADDRSTRLEN] = {};
::inet_ntop(AF_INET, &target->sin_addr, text, sizeof(text));
std::fprintf(stderr, "raven: socket %llx connects to %s:%u\n", (unsigned long long)call[0], text, unsigned(ntohs(target->sin_port)));
}
if (::connect(socket->descriptor, reinterpret_cast<const sockaddr *>(address.data()), socklen_t(address.size())) == 0) {
call.ret(0);
return;
}
if (errno != EINPROGRESS) {
fail(call, wsa_error(errno));
return;
}
if (socket->guest_nonblocking) {
socket->connecting = true;
fail(call, wsaewouldblock);
return;
}
wait_ready(socket->descriptor, POLLOUT, std::nullopt);
int error = 0;
socklen_t length = sizeof(error);
::getsockopt(socket->descriptor, SOL_SOCKET, SO_ERROR, &error, &length);
if (error) {
fail(call, wsa_error(error));
return;
}
call.ret(0);
});
registry.add("accept", 3, [&objects, socket_of, fail, wait_ready](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
for (;;) {
sockaddr_storage storage{};
socklen_t length = sizeof(storage);
socket->select_enabled |= fd_accept;
const int accepted = ::accept4(socket->descriptor, reinterpret_cast<sockaddr *>(&storage), &length, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (accepted >= 0) {
const auto made = objects.add_object(std::make_shared<SocketObject>(accepted));
if (made.status) {
::close(accepted);
fail(call, wsaemfile);
return;
}
put_address(call, call[1], call[2], storage, length);
call.ret(made.value);
return;
}
if (errno != EAGAIN && errno != EWOULDBLOCK) {
fail(call, wsa_error(errno));
return;
}
if (socket->guest_nonblocking) {
fail(call, wsaewouldblock);
return;
}
wait_ready(socket->descriptor, POLLIN, std::nullopt);
}
});
// Data transfer: every form reads and writes through one pair of helpers so blocking and flags behave the same.
const auto host_flags = [](uint32_t flags) {
int result = MSG_NOSIGNAL;
if (flags & 1)
result |= MSG_OOB;
if (flags & 2)
result |= MSG_PEEK;
if (flags & 4)
result |= MSG_DONTROUTE;
if (flags & 8)
result |= MSG_WAITALL;
return result;
};
// Receives into the guest buffer; the byte count, or -1 with the Windows error set.
const auto receive = [wait_ready, host_flags](ApiCall &call, const std::shared_ptr<SocketObject> &socket, uint64_t buffer, size_t length,
uint32_t flags, sockaddr_storage *from, socklen_t *from_length) -> int64_t {
std::vector<uint8_t> bytes(std::min<size_t>(length, 1 << 20));
// Reading re-enables the readable notification of WSAEventSelect.
socket->select_enabled |= fd_read;
for (;;) {
if (from_length)
*from_length = sizeof(sockaddr_storage);
const ssize_t count = ::recvfrom(socket->descriptor, bytes.data(), bytes.size(), (host_flags(flags) & ~MSG_WAITALL) | MSG_DONTWAIT,
reinterpret_cast<sockaddr *>(from), from_length);
if (count >= 0) {
call.write(buffer, std::span<const uint8_t>(bytes.data(), size_t(count)));
trace_received(socket->descriptor, count, "direct");
return count;
}
if (errno == EINTR)
continue;
if (errno != EAGAIN && errno != EWOULDBLOCK) {
call.set_last_error(wsa_error(errno));
return -1;
}
if (socket->guest_nonblocking) {
call.set_last_error(wsaewouldblock);
return -1;
}
wait_ready(socket->descriptor, POLLIN, std::nullopt);
}
};
const auto transmit = [wait_ready, host_flags](ApiCall &call, const std::shared_ptr<SocketObject> &socket, uint64_t buffer, size_t length,
uint32_t flags, const sockaddr *to, socklen_t to_length) -> int64_t {
const auto bytes = call.read(buffer, length);
if (std::getenv("RAVEN_TRACE_SOCKETS"))
std::fprintf(stderr, "raven: socket sends %zu bytes: %.*s\n", bytes.size(), int(std::min<size_t>(bytes.size(), 600)), reinterpret_cast<const char *>(bytes.data()));
size_t sent = 0;
while (sent < bytes.size() || bytes.empty()) {
const ssize_t count = ::sendto(socket->descriptor, bytes.data() + sent, bytes.size() - sent, host_flags(flags) | MSG_DONTWAIT, to, to_length);
if (count >= 0) {
sent += size_t(count);
if (socket->type != SOCK_STREAM || bytes.empty())
break;
continue;
}
if (errno == EINTR)
continue;
if (errno != EAGAIN && errno != EWOULDBLOCK) {
call.set_last_error(wsa_error(errno));
return -1;
}
// A non-blocking socket reports what it could take; with nothing taken it reports that it would block, and the writable
// notification of WSAEventSelect comes again once it can take more.
socket->select_enabled |= fd_write;
if (socket->guest_nonblocking) {
if (sent)
break;
call.set_last_error(wsaewouldblock);
return -1;
}
wait_ready(socket->descriptor, POLLOUT, std::nullopt);
}
return int64_t(sent);
};
registry.add("recv", 4, [socket_of, fail, receive](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
const auto count = receive(call, socket, call[1], size_t(std::max(call.i32(2), 0)), call.u32(3), nullptr, nullptr);
call.ret(count < 0 ? uint64_t(UINT64_MAX) : uint64_t(count));
});
registry.add("recvfrom", 6, [socket_of, fail, receive](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
sockaddr_storage from{};
socklen_t from_length = sizeof(from);
const auto count = receive(call, socket, call[1], size_t(std::max(call.i32(2), 0)), call.u32(3), call[4] ? &from : nullptr, call[4] ? &from_length : nullptr);
if (count >= 0 && call[4])
put_address(call, call[4], call[5], from, from_length);
call.ret(count < 0 ? uint64_t(UINT64_MAX) : uint64_t(count));
});
registry.add("send", 4, [socket_of, fail, transmit](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
const auto count = transmit(call, socket, call[1], size_t(std::max(call.i32(2), 0)), call.u32(3), nullptr, 0);
call.ret(count < 0 ? uint64_t(UINT64_MAX) : uint64_t(count));
});
registry.add("sendto", 6, [socket_of, fail, transmit](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
std::vector<uint8_t> to;
if (call[4] && call.i32(5) >= 2)
to = host_address(call, call[4], size_t(call.i32(5)));
const auto count = transmit(call, socket, call[1], size_t(std::max(call.i32(2), 0)), call.u32(3),
to.empty() ? nullptr : reinterpret_cast<const sockaddr *>(to.data()), socklen_t(to.size()));
call.ret(count < 0 ? uint64_t(UINT64_MAX) : uint64_t(count));
});
// WSABUF is a length and a pointer: 8 bytes in a 32-bit program, 16 in a 64-bit one.
struct Buffer {
uint64_t address;
uint32_t length;
};
const auto buffers_of = [](ApiCall &call, uint64_t array, uint32_t count) {
std::vector<Buffer> result;
const size_t width = call.pointer_width(), stride = width == 4 ? 8 : 16;
for (uint32_t index = 0; index < count && index < 64; ++index)
result.push_back({call.load_pointer(array + index * stride + (width == 4 ? 4 : 8)), call.load<uint32_t>(array + index * stride)});
return result;
};
// Overlapped transfers: the work is tried at once; what cannot finish yet waits in the reactor and completes later, reporting through
// the OVERLAPPED structure, its event and the completion port the socket belongs to.
struct Transfer {
std::shared_ptr<SocketObject> socket;
bool receiving = false;
std::vector<Buffer> buffers;
std::vector<uint8_t> outgoing;
std::vector<uint8_t> control;
size_t sent = 0;
uint32_t flags = 0;
uint64_t from = 0, from_length = 0, flags_out = 0, overlapped = 0;
std::vector<uint8_t> to;
size_t width = 4;
};
auto &process = context.process;
auto &reactor = *context.reactor;
// 0 when the transfer finished (status and bytes are set), 1 when it has to wait.
const auto attempt = [&process, host_flags](Transfer &transfer, uint32_t &status, uint64_t &bytes) -> int {
auto &socket = *transfer.socket;
if (socket.closed) {
status = 995;
bytes = 0;
return 0;
}
if (transfer.receiving) {
size_t total = 0;
for (const auto &buffer : transfer.buffers)
total += buffer.length;
std::vector<uint8_t> data(std::min<size_t>(total, 1 << 20));
sockaddr_storage from{};
socklen_t from_length = sizeof(from);
const ssize_t count = ::recvfrom(socket.descriptor, data.data(), data.size(), (host_flags(transfer.flags) & ~MSG_WAITALL) | MSG_DONTWAIT,
transfer.from ? reinterpret_cast<sockaddr *>(&from) : nullptr, transfer.from ? &from_length : nullptr);
if (count < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return 1;
status = wsa_error(errno);
bytes = 0;
return 0;
}
trace_received(socket.descriptor, count, "overlapped");
size_t offset = 0;
for (const auto &buffer : transfer.buffers) {
const size_t part = std::min<size_t>(buffer.length, size_t(count) - offset);
if (part)
process.write_memory(buffer.address, std::span<const uint8_t>(data.data() + offset, part));
offset += part;
}
if (transfer.from) {
std::vector<uint8_t> raw(reinterpret_cast<const uint8_t *>(&from), reinterpret_cast<const uint8_t *>(&from) + from_length);
if (raw.size() >= 2) {
uint16_t family;
std::memcpy(&family, raw.data(), 2);
family = uint16_t(windows_family(family));
std::memcpy(raw.data(), &family, 2);
}
process.write_memory(transfer.from, raw);
if (transfer.from_length) {
const uint32_t length = uint32_t(from_length);
process.write_memory(transfer.from_length, std::span<const uint8_t>(reinterpret_cast<const uint8_t *>(&length), 4));
}
}
status = 0;
bytes = uint64_t(count);
return 0;
}
while (transfer.sent < transfer.outgoing.size() || transfer.outgoing.empty()) {
ssize_t count;
if (!transfer.control.empty()) {
iovec data{transfer.outgoing.data(), transfer.outgoing.size()};
msghdr message{};
message.msg_name = transfer.to.empty() ? nullptr : transfer.to.data();
message.msg_namelen = socklen_t(transfer.to.size());
message.msg_iov = &data;
message.msg_iovlen = 1;
message.msg_control = transfer.control.data();
message.msg_controllen = transfer.control.size();
count = ::sendmsg(socket.descriptor, &message, host_flags(transfer.flags) | MSG_DONTWAIT);
} else count = ::sendto(socket.descriptor, transfer.outgoing.empty() ? nullptr : transfer.outgoing.data() + transfer.sent, transfer.outgoing.size() - transfer.sent,
host_flags(transfer.flags) | MSG_DONTWAIT, transfer.to.empty() ? nullptr : reinterpret_cast<const sockaddr *>(transfer.to.data()),
socklen_t(transfer.to.size()));
if (count < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
return 1;
status = wsa_error(errno);
bytes = transfer.sent;
return 0;
}
transfer.sent += size_t(count);
if (socket.type != SOCK_STREAM || transfer.outgoing.empty())
break;
}
status = 0;
bytes = transfer.sent;
return 0;
};
// OVERLAPPED: the status and the byte count come first, the event handle after the two offset words.
const auto finish = [&process, &objects](const std::shared_ptr<SocketObject> &socket, uint64_t overlapped, size_t width, uint32_t status, uint64_t bytes,
bool at_once) {
const auto put = [&](uint64_t at, uint64_t value) {
std::vector<uint8_t> raw(width);
std::memcpy(raw.data(), &value, width);
process.write_memory(at, raw);
};
// The status word holds zero for success and the Windows Sockets error under a marker otherwise; WSAGetOverlappedResult reads it.
put(overlapped, status ? (overlapped_error | status) : 0);
put(overlapped + width, bytes);
const auto handle = [&] {
const auto raw = process.read_memory(overlapped + 2 * width + 8, width);
uint64_t value = 0;
std::memcpy(&value, raw.data(), width);
return value;
}();
// A transfer that finished at once with success skips what the completion modes ask to skip.
const bool succeeded_at_once = at_once && !status;
if ((handle & ~uint64_t(1)) && !(succeeded_at_once && socket->skip_event_on_success))
objects.set_event(handle & ~uint64_t(1), true);
// A handle with its low bit set asks for no completion packet.
if (socket->port && !(handle & 1) && !(succeeded_at_once && socket->skip_port_on_success))
socket->port->post({bytes, socket->key, overlapped, status});
};
const auto complete = [finish](const Transfer &transfer, uint32_t status, uint64_t bytes, bool at_once) {
finish(transfer.socket, transfer.overlapped, transfer.width, status, bytes, at_once);
};
// Starts a transfer for the WSA calls; returns the call's result (0 or SOCKET_ERROR) with the byte count stored when it finished.
const auto start = [attempt, complete, &reactor, &objects](ApiCall &call, Transfer transfer, uint64_t bytes_out) {
transfer.width = call.pointer_width();
uint32_t status = 0;
uint64_t bytes = 0;
if (!transfer.overlapped) {
call.fail(wsaeopnotsupp, UINT32_MAX);
return;
}
// Starting an operation resets the OVERLAPPED's event, which only its completion sets: a program that reuses the structure
// must not see the previous transfer's signal as this one's.
if (const auto event = call.load_pointer(transfer.overlapped + 2 * transfer.width + 8) & ~uint64_t(1))
objects.set_event(event, false);
if (attempt(transfer, status, bytes) == 0) {
if (status) {
call.fail(status, UINT32_MAX);
return;
}
complete(transfer, 0, bytes, true);
if (bytes_out)
call.store<uint32_t>(bytes_out, uint32_t(bytes));
call.ret(0);
return;
}
// Until it finishes the OVERLAPPED says the operation is pending.
{
std::vector<uint8_t> pending(transfer.width, 0);
const uint64_t value = status_pending;
std::memcpy(pending.data(), &value, transfer.width);
call.write(transfer.overlapped, pending);
}
auto shared = std::make_shared<Transfer>(std::move(transfer));
const int descriptor = shared->socket->descriptor;
const short events = shared->receiving ? POLLIN : POLLOUT;
reactor.add([shared, attempt, complete]() {
uint32_t result = 0;
uint64_t count = 0;
// A program may release the memory of a transfer still pending, as one that frees a fiber's stack does after closing the
// socket; the kernel then fails the copy into it and the operation ends unseen, without harm to the process.
try {
if (attempt(*shared, result, count) != 0)
return false;
complete(*shared, result, count, false);
} catch (const std::runtime_error &failure) {
if (std::getenv("RAVEN_TRACE_SOCKETS"))
std::fprintf(stderr, "raven: pending transfer on socket %d dropped: %s\n", shared->socket->descriptor, failure.what());
}
return true;
}, descriptor, events);
call.fail(997, UINT32_MAX);
};
// Without an OVERLAPPED the call is an ordinary blocking or non-blocking transfer.
registry.add("WSARecv", 7, [socket_of, fail, receive, buffers_of, start](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock, UINT32_MAX);
return;
}
if (call[6]) {
fail(call, wsaeopnotsupp, UINT32_MAX);
return;
}
const uint32_t flags = call[4] ? call.load<uint32_t>(call[4]) : 0;
if (call[5]) {
Transfer transfer;
transfer.socket = socket;
transfer.receiving = true;
transfer.buffers = buffers_of(call, call[1], call.u32(2));
transfer.flags = flags;
transfer.overlapped = call[5];
start(call, std::move(transfer), call[3]);
return;
}
int64_t total = 0;
for (const auto &buffer : buffers_of(call, call[1], call.u32(2))) {
if (!buffer.length)
continue;
const auto count = receive(call, socket, buffer.address, buffer.length, flags, nullptr, nullptr);
if (count < 0) {
if (total)
break;
call.ret(UINT32_MAX);
return;
}
total += count;
if (uint32_t(count) < buffer.length)
break;
}
if (call[3])
call.store<uint32_t>(call[3], uint32_t(total));
if (call[4])
call.store<uint32_t>(call[4], 0);
call.ret(0);
});
registry.add("WSASend", 7, [socket_of, fail, transmit, buffers_of, start](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock, UINT32_MAX);
return;
}
if (call[6]) {
fail(call, wsaeopnotsupp, UINT32_MAX);
return;
}
if (call[5]) {
Transfer transfer;
transfer.socket = socket;
transfer.flags = call.u32(4);
transfer.overlapped = call[5];
for (const auto &buffer : buffers_of(call, call[1], call.u32(2))) {
const auto bytes = call.read(buffer.address, buffer.length);
transfer.outgoing.insert(transfer.outgoing.end(), bytes.begin(), bytes.end());
if (std::getenv("RAVEN_TRACE_SOCKETS"))
std::fprintf(stderr, "raven: socket sends %zu bytes: %.*s\n", bytes.size(), int(std::min<size_t>(bytes.size(), 600)), reinterpret_cast<const char *>(bytes.data()));
}
start(call, std::move(transfer), call[3]);
return;
}
int64_t total = 0;
for (const auto &buffer : buffers_of(call, call[1], call.u32(2))) {
if (!buffer.length)
continue;
const auto count = transmit(call, socket, buffer.address, buffer.length, call.u32(4), nullptr, 0);
if (count < 0) {
if (total)
break;
call.ret(UINT32_MAX);
return;
}
total += count;
if (uint32_t(count) < buffer.length)
break;
}
if (call[3])
call.store<uint32_t>(call[3], uint32_t(total));
call.ret(0);
});
registry.add("WSARecvFrom", 9, [socket_of, fail, receive, buffers_of, start](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock, UINT32_MAX);
return;
}
if (call[8]) {
fail(call, wsaeopnotsupp, UINT32_MAX);
return;
}
const auto buffers = buffers_of(call, call[1], call.u32(2));
if (buffers.empty()) {
fail(call, wsaeinval, UINT32_MAX);
return;
}
const uint32_t flags = call[4] ? call.load<uint32_t>(call[4]) : 0;
if (call[7]) {
Transfer transfer;
transfer.socket = socket;
transfer.receiving = true;
transfer.buffers = buffers;
transfer.flags = flags;
transfer.from = call[5];
transfer.from_length = call[6];
transfer.overlapped = call[7];
start(call, std::move(transfer), call[3]);
return;
}
sockaddr_storage from{};
socklen_t from_length = sizeof(from);
const auto count = receive(call, socket, buffers[0].address, buffers[0].length, flags, call[5] ? &from : nullptr, call[5] ? &from_length : nullptr);
if (count < 0) {
call.ret(UINT32_MAX);
return;
}
if (call[5])
put_address(call, call[5], call[6], from, from_length);
if (call[3])
call.store<uint32_t>(call[3], uint32_t(count));
if (call[4])
call.store<uint32_t>(call[4], 0);
call.ret(0);
});
registry.add("WSASendTo", 9, [socket_of, fail, transmit, buffers_of, start](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock, UINT32_MAX);
return;
}
if (call[8]) {
fail(call, wsaeopnotsupp, UINT32_MAX);
return;
}
const auto buffers = buffers_of(call, call[1], call.u32(2));
if (buffers.empty()) {
fail(call, wsaeinval, UINT32_MAX);
return;
}
std::vector<uint8_t> to;
if (call[5] && call.i32(6) >= 2)
to = host_address(call, call[5], size_t(call.i32(6)));
if (call[7]) {
Transfer transfer;
transfer.socket = socket;
transfer.flags = call.u32(4);
transfer.to = to;
transfer.overlapped = call[7];
for (const auto &buffer : buffers) {
const auto bytes = call.read(buffer.address, buffer.length);
transfer.outgoing.insert(transfer.outgoing.end(), bytes.begin(), bytes.end());
}
start(call, std::move(transfer), call[3]);
return;
}
const auto count = transmit(call, socket, buffers[0].address, buffers[0].length, call.u32(4),
to.empty() ? nullptr : reinterpret_cast<const sockaddr *>(to.data()), socklen_t(to.size()));
if (count < 0) {
call.ret(UINT32_MAX);
return;
}
if (call[3])
call.store<uint32_t>(call[3], uint32_t(count));
call.ret(0);
});
registry.add("WSASendMsg", 6, [socket_of, fail, buffers_of, start, attempt, wait_ready](ApiCall &call) {
try {
const auto socket = socket_of(call[0]);
if (!socket) { fail(call, wsaenotsock); return; }
if (socket->type != SOCK_DGRAM && socket->type != SOCK_RAW) { fail(call, wsaeopnotsupp); return; }
if (call[5] || (call.u32(2) & ~5U)) { fail(call, wsaeopnotsupp); return; }
if (!call[1] || (!call[3] && !call[4])) { fail(call, wsaefault); return; }
const size_t width = call.pointer_width();
call.read(call[1], width == 4 ? 28 : 56);
if (call[3]) call.write(call[3], call.read(call[3], 4));
if (call[4]) call.write(call[4], call.read(call[4], 3 * width + 8));
const uint64_t name = call.load_pointer(call[1]);
const int32_t name_size = call.load<int32_t>(call[1] + width);
if (name_size < 0 || name_size > int32_t(sizeof(sockaddr_storage)) || (name_size && !name)) throw wsaefault;
Transfer transfer;
transfer.socket = socket;
transfer.flags = call.u32(2);
transfer.overlapped = call[4];
if (name) {
if (name_size < 2) throw wsaefault;
transfer.to = host_address(call, name, size_t(name_size));
}
const size_t buffers_at = width == 4 ? 8 : 16, control_at = width == 4 ? 16 : 32;
const uint64_t array = call.load_pointer(call[1] + buffers_at);
const uint32_t count = call.load<uint32_t>(call[1] + buffers_at + width);
if (!array || !count) throw wsaefault;
if (count > 64) throw wsaenobufs;
for (const auto &buffer : buffers_of(call, array, count)) {
if (buffer.length > (1U << 20) - transfer.outgoing.size()) throw wsaemsgsize;
const auto bytes = call.read(buffer.address, buffer.length);
transfer.outgoing.insert(transfer.outgoing.end(), bytes.begin(), bytes.end());
}
transfer.control = message_control(call, socket->descriptor,
call.load_pointer(call[1] + control_at + width), call.load<uint32_t>(call[1] + control_at));
if (transfer.overlapped) { start(call, std::move(transfer), call[3]); return; }
uint32_t status = 0;
uint64_t sent = 0;
while (attempt(transfer, status, sent)) {
socket->select_enabled |= fd_write;
if (socket->guest_nonblocking) { fail(call, wsaewouldblock); return; }
wait_ready(socket->descriptor, POLLOUT, std::nullopt);
}
if (status) { fail(call, status); return; }
call.store<uint32_t>(call[3], uint32_t(sent));
call.ret(0);
} catch (const ApiFault &) {
fail(call, wsaefault);
} catch (const uint32_t &error) {
fail(call, error);
}
});
// WSARecvMsg(socket, message, received, overlapped, routine): WSARecvFrom over the parts of WSAMSG (the name and its length, the
// buffers, the control buffer and the flags). No control data is delivered, so its length comes back as zero.
registry.add("WSARecvMsg", 5, [&registry, fail](ApiCall &call) {
if (!call[1]) {
fail(call, wsaefault);
return;
}
const size_t width = call.pointer_width(), buffers_at = width == 4 ? 8 : 16, control_at = width == 4 ? 16 : 32,
flags_at = width == 4 ? 24 : 48;
const uint64_t message = call[1];
const auto receive_from = registry.find("WSARecvFrom");
ApiCall inner(call.process, call.environment, {call[0], call.load_pointer(message + buffers_at), call.load<uint32_t>(message + buffers_at + width),
call[2], message + flags_at, call.load_pointer(message), call.load_pointer(message) ? message + width : 0, call[3], call[4]});
receive_from->second(inner);
if (inner.result != uint64_t(UINT64_MAX) || call.last_error() == 997)
call.store<uint32_t>(message + control_at, 0);
call.ret(inner.result);
});
// The extension functions of Winsock are found through WSAIoctl by GUID; each is an export the runtime answers like any other.
registry.add("ConnectEx", 7, [socket_of, fail, finish, &reactor](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || !call[6] || call.i32(2) < 2) {
fail(call, socket ? wsaeinval : wsaenotsock, 0);
return;
}
const size_t width = call.pointer_width();
const uint64_t overlapped = call[6];
const auto address = host_address(call, call[1], size_t(call.i32(2)));
auto data = call[3] && call.u32(4) ? call.read(call[3], call.u32(4)) : std::vector<uint8_t>{};
const auto sent_out = call[5];
const auto done = [socket, finish, overlapped, width, data = std::move(data)](uint32_t status, bool at_once) {
uint64_t bytes = 0;
if (!status && !data.empty()) {
const ssize_t count = ::send(socket->descriptor, data.data(), data.size(), MSG_NOSIGNAL | MSG_DONTWAIT);
bytes = count > 0 ? uint64_t(count) : 0;
}
finish(socket, overlapped, width, status, bytes, at_once);
return bytes;
};
if (::connect(socket->descriptor, reinterpret_cast<const sockaddr *>(address.data()), socklen_t(address.size())) == 0) {
const auto bytes = done(0, true);
if (sent_out)
call.store<uint32_t>(sent_out, uint32_t(bytes));
call.ret(1);
return;
}
if (errno != EINPROGRESS) {
fail(call, wsa_error(errno), 0);
return;
}
reactor.add([socket, done]() {
if (socket->closed) {
done(995, false);
return true;
}
pollfd entry{socket->descriptor, POLLOUT, 0};
if (::poll(&entry, 1, 0) <= 0)
return false;
int error = 0;
socklen_t length = sizeof(error);
::getsockopt(socket->descriptor, SOL_SOCKET, SO_ERROR, &error, &length);
done(error ? wsa_error(error) : 0, false);
return true;
}, socket->descriptor, short(POLLOUT));
fail(call, 997, 0);
});
registry.add("AcceptEx", 8, [socket_of, fail, finish, &reactor, &process](ApiCall &call) {
const auto listener = socket_of(call[0]);
const auto target = socket_of(call[1]);
if (!listener || !target || !call[7]) {
fail(call, listener && target ? wsaeinval : wsaenotsock, 0);
return;
}
if (call.u32(3)) {
// Receiving the first data together with the connection is not done: the call says so.
fail(call, wsaeopnotsupp, 0);
return;
}
const size_t width = call.pointer_width();
const uint64_t overlapped = call[7], buffer = call[2], local_length = call.u32(4), remote_length = call.u32(5);
const auto attempt = [listener, target, &process, buffer, local_length, remote_length]() -> int {
sockaddr_storage peer{};
socklen_t peer_length = sizeof(peer);
const int accepted = ::accept4(listener->descriptor, reinterpret_cast<sockaddr *>(&peer), &peer_length, SOCK_NONBLOCK | SOCK_CLOEXEC);
if (accepted < 0)
return errno == EAGAIN || errno == EWOULDBLOCK ? -1 : int(wsa_error(errno));
::dup3(accepted, target->descriptor, SOCK_CLOEXEC);
::close(accepted);
const auto write_address = [&](uint64_t at, uint32_t capacity, const sockaddr_storage &storage, socklen_t length) {
std::vector<uint8_t> raw(capacity);
std::memcpy(raw.data(), &storage, std::min<size_t>(length, capacity));
uint16_t family;
std::memcpy(&family, raw.data(), 2);
family = uint16_t(windows_family(family));
std::memcpy(raw.data(), &family, 2);
process.write_memory(at, raw);
};
sockaddr_storage local{};
socklen_t local_size = sizeof(local);
::getsockname(target->descriptor, reinterpret_cast<sockaddr *>(&local), &local_size);
write_address(buffer, local_length, local, local_size);
write_address(buffer + local_length, remote_length, peer, peer_length);
return 0;
};
const int first = attempt();
if (first == 0) {
finish(listener, overlapped, width, 0, 0, true);
call.ret(1);
return;
}
if (first > 0) {
fail(call, uint32_t(first), 0);
return;
}
reactor.add([attempt, finish, listener, overlapped, width]() {
if (listener->closed) {
finish(listener, overlapped, width, 995, 0, false);
return true;
}
const int result = attempt();
if (result < 0)
return false;
finish(listener, overlapped, width, uint32_t(result), 0, false);
return true;
}, listener->descriptor, short(POLLIN));
fail(call, 997, 0);
});
registry.add("GetAcceptExSockaddrs", 8, [](ApiCall &call) {
const uint64_t local = call[0] + call.u32(1), remote = local + call.u32(2);
const auto length_of = [&](uint64_t address) {
const auto family = call.load<uint16_t>(address);
return uint32_t(family == windows_inet6 ? 28 : 16);
};
if (call[4])
call.store_pointer(call[4], local);
if (call[5])
call.store<uint32_t>(call[5], length_of(local));
if (call[6])
call.store_pointer(call[6], remote);
if (call[7])
call.store<uint32_t>(call[7], length_of(remote));
});
registry.add("DisconnectEx", 4, [socket_of, fail, finish](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock, 0);
return;
}
::shutdown(socket->descriptor, SHUT_RDWR);
if (call[1])
finish(socket, call[1], call.pointer_width(), 0, 0, true);
call.ret(1);
});
// Completion ports: one queue that overlapped operations report to and worker threads take from.
context.threads.add_pump([&reactor] { reactor.poll(); });
registry.add("CreateIoCompletionPort", 4, [&objects, socket_of](ApiCall &call) {
std::shared_ptr<CompletionPort> port;
if (call[1]) {
port = objects.object_as<CompletionPort>(call[1]);
if (!port) {
call.fail(6, 0);
return;
}
}
const bool create = !port;
uint64_t handle = call[1];
if (create) {
port = std::make_shared<CompletionPort>();
const auto made = objects.add_object(port);
if (made.status) {
call.fail(4, 0);
return;
}
handle = made.value;
}
if (call[0] != UINT64_MAX) {
const auto socket = socket_of(call[0]);
const auto file = objects.object_as<FileObject>(call[0]);
if (const auto endpoint = objects.object_as<AfdEndpoint>(call[0])) {
endpoint->port = port;
endpoint->key = call[2];
call.ret(handle);
return;
}
if (!socket && !file) {
if (create)
objects.close(handle);
call.fail(6, 0);
return;
}
if (socket) {
socket->port = port;
socket->key = call[2];
} else {
file->port = port;
file->key = call[2];
}
}
call.ret(handle);
});
registry.add("PostQueuedCompletionStatus", 4, [&objects](ApiCall &call) {
const auto port = objects.object_as<CompletionPort>(call[0]);
if (!port) {
call.fail(6);
return;
}
port->post({call.u32(1), call[2], call[3], 0});
call.ret(1);
});
// Waits for one packet; empty when the time ran out.
const auto wait_packet = [&reactor, &threads](const std::shared_ptr<CompletionPort> &port, uint32_t milliseconds) -> std::optional<CompletionPacket> {
std::optional<std::chrono::steady_clock::time_point> wake;
if (milliseconds != UINT32_MAX)
wake = std::chrono::steady_clock::now() + std::chrono::milliseconds(milliseconds);
for (;;) {
reactor.poll();
CompletionPacket packet;
if (port->take(packet))
return packet;
if (milliseconds == 0 || (wake && std::chrono::steady_clock::now() >= *wake))
return std::nullopt;
HostWait waiting;
threads.block([&] { reactor.poll(); return !port->empty(); }, wake);
}
};
registry.add("GetQueuedCompletionStatus", 5, [&objects, wait_packet](ApiCall &call) {
const auto port = objects.object_as<CompletionPort>(call[0]);
if (!port) {
call.fail(6);
return;
}
const auto packet = wait_packet(port, call.u32(4));
if (!packet) {
if (call[3])
call.store_pointer(call[3], 0);
call.fail(258);
return;
}
if (call[1])
call.store<uint32_t>(call[1], uint32_t(packet->bytes));
if (call[2])
call.store_pointer(call[2], packet->key);
if (call[3])
call.store_pointer(call[3], packet->overlapped);
if (packet->status) {
call.fail(packet->status);
return;
}
call.ret(1);
});
// OVERLAPPED_ENTRY: the key, the OVERLAPPED pointer, an internal word and the byte count.
registry.add("GetQueuedCompletionStatusEx", 6, [&objects, wait_packet](ApiCall &call) {
const auto port = objects.object_as<CompletionPort>(call[0]);
if (!port || !call[1] || !call.u32(2) || !call[3]) {
call.fail(port ? 87 : 6);
return;
}
const size_t width = call.pointer_width(), stride = width == 4 ? 16 : 32;
uint32_t removed = 0;
auto packet = wait_packet(port, call.u32(4));
while (packet && removed < call.u32(2)) {
const uint64_t entry = call[1] + removed * stride;
call.store_pointer(entry, packet->key);
call.store_pointer(entry + width, packet->overlapped);
call.store_pointer(entry + 2 * width, packet->status);
call.store<uint32_t>(entry + 3 * width, uint32_t(packet->bytes));
++removed;
// The next packet is taken only when there is room for it; one taken and not handed out would be lost.
if (removed < call.u32(2))
packet = wait_packet(port, 0);
}
if (!removed) {
call.fail(258);
return;
}
call.store<uint32_t>(call[3], removed);
call.ret(1);
});
registry.add("getsockname", 3, [socket_of, fail](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || !call[2]) {
fail(call, socket ? wsaefault : wsaenotsock);
return;
}
sockaddr_storage storage{};
socklen_t length = sizeof(storage);
if (::getsockname(socket->descriptor, reinterpret_cast<sockaddr *>(&storage), &length)) {
fail(call, wsa_error(errno));
return;
}
put_address(call, call[1], call[2], storage, length);
call.ret(0);
});
registry.add("getpeername", 3, [socket_of, fail](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || !call[2]) {
fail(call, socket ? wsaefault : wsaenotsock);
return;
}
sockaddr_storage storage{};
socklen_t length = sizeof(storage);
if (::getpeername(socket->descriptor, reinterpret_cast<sockaddr *>(&storage), &length)) {
fail(call, wsa_error(errno));
return;
}
put_address(call, call[1], call[2], storage, length);
call.ret(0);
});
// Options: the level and the option number differ between Windows and the host, and a few values have another shape.
struct Option {
int level, name;
};
const auto host_option = [](uint32_t level, uint32_t name) -> std::optional<Option> {
if (level == sol_socket_windows) {
switch (name) {
case 0x0004: return Option{SOL_SOCKET, SO_REUSEADDR};
case 0x0008: return Option{SOL_SOCKET, SO_KEEPALIVE};
case 0x0020: return Option{SOL_SOCKET, SO_BROADCAST};
case 0x0080: return Option{SOL_SOCKET, SO_LINGER};
case 0x1001: return Option{SOL_SOCKET, SO_SNDBUF};
case 0x1002: return Option{SOL_SOCKET, SO_RCVBUF};
case 0x1005: return Option{SOL_SOCKET, SO_SNDTIMEO};
case 0x1006: return Option{SOL_SOCKET, SO_RCVTIMEO};
case 0x1007: return Option{SOL_SOCKET, SO_ERROR};
case 0x1008: return Option{SOL_SOCKET, SO_TYPE};
default: return std::nullopt;
}
}
if (level == IPPROTO_TCP && name == 1)
return Option{IPPROTO_TCP, TCP_NODELAY};
if (level == IPPROTO_IPV6 && name == 27)
return Option{IPPROTO_IPV6, IPV6_V6ONLY};
if (level == IPPROTO_IP) {
if (name == 4)
return Option{IPPROTO_IP, IP_TOS};
if (name == 2)
return Option{IPPROTO_IP, IP_TTL};
}
return std::nullopt;
};
registry.add("setsockopt", 5, [socket_of, fail, host_option](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
const auto option = host_option(call.u32(1), call.u32(2));
if (!option) {
fail(call, wsaenoprotoopt);
return;
}
const auto value = call[3] ? call.read(call[3], size_t(std::max(call.i32(4), 0))) : std::vector<uint8_t>{};
int result;
if (option->level == SOL_SOCKET && (option->name == SO_RCVTIMEO || option->name == SO_SNDTIMEO) && value.size() >= 4) {
uint32_t milliseconds;
std::memcpy(&milliseconds, value.data(), 4);
timeval timeout{time_t(milliseconds / 1000), suseconds_t(milliseconds % 1000 * 1000)};
result = ::setsockopt(socket->descriptor, option->level, option->name, &timeout, sizeof(timeout));
} else if (option->level == SOL_SOCKET && option->name == SO_LINGER && value.size() >= 4) {
uint16_t on, seconds;
std::memcpy(&on, value.data(), 2);
std::memcpy(&seconds, value.data() + 2, 2);
linger setting{on, seconds};
result = ::setsockopt(socket->descriptor, option->level, option->name, &setting, sizeof(setting));
} else if (!value.empty() && value.size() < sizeof(int)) {
// Winsock reads a Boolean option from as little as one byte, which Linux refuses as shorter than an int.
int widened = 0;
for (size_t at = 0; at < value.size(); ++at)
widened |= int(value[at]) << (8 * at);
result = ::setsockopt(socket->descriptor, option->level, option->name, &widened, sizeof(widened));
} else {
result = ::setsockopt(socket->descriptor, option->level, option->name, value.data(), socklen_t(value.size()));
}
if (result) {
fail(call, wsa_error(errno));
return;
}
call.ret(0);
});
registry.add("getsockopt", 5, [socket_of, fail, host_option](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || !call[3] || !call[4]) {
fail(call, socket ? wsaefault : wsaenotsock);
return;
}
const auto option = host_option(call.u32(1), call.u32(2));
if (!option) {
fail(call, wsaenoprotoopt);
return;
}
int value = 0;
socklen_t length = sizeof(value);
if (::getsockopt(socket->descriptor, option->level, option->name, &value, &length)) {
fail(call, wsa_error(errno));
return;
}
if (option->level == SOL_SOCKET && option->name == SO_ERROR)
value = int(value ? wsa_error(value) : 0);
const uint32_t capacity = call.load<uint32_t>(call[4]);
if (capacity < 4) {
fail(call, wsaefault);
return;
}
call.store<int32_t>(call[3], value);
call.store<uint32_t>(call[4], 4);
call.ret(0);
});
constexpr uint32_t fionbio = 0x8004667e, fionread = 0x4004667f, sio_keepalive_vals = 0x98000004, sio_get_extension_function = 0xc8000006,
sio_base_handle = 0x48000022, sio_bsp_handle = 0x4800001b, sio_bsp_handle_select = 0x4800001c, sio_bsp_handle_poll = 0x4800001d;
const auto resolve_export = [&context](const char *library, const char *name) { return context.modules.resolve({library, name, 0, false, 0}); };
// WSAEventSelect: the sockets are looked at whenever the threads are scheduled; a network event the program asked for is recorded
// once until the call that consumes it enables it again, and recording one sets the program's event.
auto selected = std::make_shared<std::vector<std::weak_ptr<SocketObject>>>();
const auto select_descriptor = [](const SocketObject &socket) {
const auto enabled = socket.select_mask & socket.select_enabled;
if (!socket.select_mask || socket.closed || (!enabled && !socket.connecting))
return pollfd{-1, 0, 0};
short events = 0;
if (enabled & (fd_read | fd_accept | fd_close)) events |= POLLIN;
if ((enabled & fd_write) || socket.connecting) events |= POLLOUT;
if (enabled & fd_oob) events |= POLLPRI;
return pollfd{socket.descriptor, events, 0};
};
const auto record_select = [&objects](SocketObject &socket, short ready) {
uint32_t found = 0;
const auto record = [&](uint32_t event, size_t bit, int32_t error) {
if (!(socket.select_mask & event) || !(socket.select_enabled & event))
return;
socket.select_enabled &= ~event;
socket.select_recorded |= event;
socket.select_errors[bit] = error;
found |= event;
};
if (socket.connecting) {
if (!(ready & (POLLOUT | POLLERR | POLLHUP)))
return;
socket.connecting = false;
int error = 0;
socklen_t length = sizeof(error);
::getsockopt(socket.descriptor, SOL_SOCKET, SO_ERROR, &error, &length);
record(fd_connect, 4, error ? int32_t(wsa_error(error)) : 0);
if (error) {
record(fd_close, 5, int32_t(wsa_error(error)));
if (found && socket.select_event)
objects.set_event(socket.select_event, true);
return;
}
}
int listening = 0;
socklen_t length = sizeof(listening);
::getsockopt(socket.descriptor, SOL_SOCKET, SO_ACCEPTCONN, &listening, &length);
if (listening) {
if (ready & POLLIN)
record(fd_accept, 3, 0);
} else {
if (ready & POLLIN) {
char probe;
const ssize_t count = ::recv(socket.descriptor, &probe, 1, MSG_PEEK | MSG_DONTWAIT);
if (count > 0)
record(fd_read, 0, 0);
else if (count == 0)
record(fd_close, 5, 0);
else if (errno != EAGAIN && errno != EWOULDBLOCK)
record(fd_close, 5, int32_t(wsa_error(errno)));
} else if (ready & (POLLHUP | POLLERR)) {
record(fd_close, 5, int32_t(wsaeconnreset));
}
if (ready & POLLPRI)
record(fd_oob, 2, 0);
if (ready & POLLOUT)
record(fd_write, 1, 0);
}
if (found && socket.select_event)
objects.set_event(socket.select_event, true);
};
const auto check_select = [select_descriptor, record_select](SocketObject &socket) {
auto entry = select_descriptor(socket);
if (entry.fd >= 0 && ::poll(&entry, 1, 0) > 0)
record_select(socket, entry.revents);
};
const auto check_all = [selected, select_descriptor, record_select, entries = std::vector<pollfd>()]() mutable {
std::erase_if(*selected, [](const auto &weak) { return weak.expired(); });
entries.clear();
bool active = false;
for (const auto &weak : *selected)
if (const auto socket = weak.lock()) {
entries.push_back(select_descriptor(*socket));
active |= entries.back().fd >= 0;
} else {
entries.push_back({-1, 0, 0});
}
if (!active || ::poll(entries.data(), entries.size(), 0) <= 0)
return;
for (size_t index = 0; index < entries.size(); ++index)
if (entries[index].revents)
if (const auto socket = (*selected)[index].lock())
record_select(*socket, entries[index].revents);
};
context.threads.add_pump(check_all);
context.threads.add_service(check_all);
context.threads.add_external([selected] {
return std::any_of(selected->begin(), selected->end(), [](const auto &weak) {
const auto socket = weak.lock();
return socket && socket->select_mask && !socket->closed;
});
});
registry.add("WSAEventSelect", 3, [socket_of, fail, selected, check_select](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock, uint32_t(-1));
return;
}
if (call.u32(2) && !call[1]) {
fail(call, wsaeinval, uint32_t(-1));
return;
}
// Selecting events makes the socket non-blocking, and every event may be reported from then on.
socket->select_event = call[1];
socket->select_mask = call.u32(2);
socket->select_enabled = 0x3ff;
socket->select_recorded = 0;
socket->select_errors = {};
socket->guest_nonblocking = true;
if (std::none_of(selected->begin(), selected->end(), [&](const auto &weak) { return weak.lock() == socket; }))
selected->push_back(socket);
check_select(*socket);
call.ret(0);
});
// WSANETWORKEVENTS: the recorded events, then ten error codes indexed by event bit.
registry.add("WSAEnumNetworkEvents", 3, [&objects, socket_of, fail, check_select](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || !call[2]) {
fail(call, socket ? wsaefault : wsaenotsock, uint32_t(-1));
return;
}
check_select(*socket);
if (std::getenv("RAVEN_TRACE_SOCKETS"))
std::fprintf(stderr, "raven: socket %llx network events %x, connect error %d, close error %d\n", (unsigned long long)call[0],
socket->select_recorded, socket->select_errors[4], socket->select_errors[5]);
std::vector<uint8_t> bytes(44, 0);
std::memcpy(bytes.data(), &socket->select_recorded, 4);
std::memcpy(bytes.data() + 4, socket->select_errors.data(), 40);
call.write(call[2], bytes);
socket->select_recorded = 0;
socket->select_errors = {};
if (call[1])
objects.set_event(call[1], false);
call.ret(0);
});
// The outcome of an overlapped transfer, waited for when the caller asks to.
registry.add("WSAGetOverlappedResult", 5, [socket_of, fail, &reactor, &threads](ApiCall &call) {
if (!socket_of(call[0]) || !call[1] || !call[2]) {
fail(call, socket_of(call[0]) ? wsaefault : wsaenotsock, 0);
return;
}
const size_t width = call.pointer_width();
const auto word = [&](uint64_t at) {
const auto raw = call.read(at, width);
uint64_t value = 0;
std::memcpy(&value, raw.data(), width);
return value;
};
if (word(call[1]) == status_pending) {
if (!call.u32(3)) {
fail(call, wsa_io_incomplete, 0);
return;
}
HostWait waiting;
threads.block([&] {
reactor.poll();
return word(call[1]) != status_pending;
}, std::nullopt);
}
const auto status = word(call[1]);
call.store<uint32_t>(call[2], uint32_t(word(call[1] + width)));
if (call[4])
call.store<uint32_t>(call[4], 0);
if (status) {
fail(call, (status & overlapped_error) == overlapped_error ? uint32_t(status & 0xffff) : uint32_t(wsaeconnreset), 0);
return;
}
call.ret(1);
});
// The TCP endpoints of the host with the process that owns each one (iphlpapi): programs check who is on the other end of a
// connection to themselves.
registry.add("GetExtendedTcpTable", 6, [](ApiCall &call) {
constexpr uint32_t af_inet = 2, af_inet6 = 23, insufficient_buffer = 122, invalid_parameter = 87, not_supported = 50;
const uint32_t family = call.u32(3), kind = call.u32(4);
if (!call[1] || (family != af_inet && family != af_inet6)) {
call.ret(invalid_parameter);
return;
}
// TCP_TABLE_BASIC_* (0-2) and TCP_TABLE_OWNER_PID_* (3-5); the module tables are not offered.
if (kind > 5) {
call.ret(not_supported);
return;
}
const bool v6 = family == af_inet6, owner = kind >= 3;
const bool listeners = kind == 0 || kind == 2 || kind == 3 || kind == 5, connections = kind == 0 || kind == 1 || kind == 4 || kind == 5;
auto entries = tcp_entries(v6);
std::erase_if(entries, [&](const TcpEntry &entry) { return entry.state == 2 ? !listeners : !connections; });
if (call.u32(2))
std::sort(entries.begin(), entries.end(), [](const TcpEntry &a, const TcpEntry &b) {
return std::tie(a.local, a.local_port, a.remote, a.remote_port) < std::tie(b.local, b.local_port, b.remote, b.remote_port);
});
const size_t row = v6 ? (owner ? 56 : 52) : (owner ? 24 : 20);
const uint32_t needed = uint32_t(4 + row * entries.size());
const uint32_t capacity = call.load<uint32_t>(call[1]);
if (!call[0] || capacity < needed) {
call.store<uint32_t>(call[1], needed);
call.ret(insufficient_buffer);
return;
}
std::vector<uint8_t> bytes(needed, 0);
const auto put = [&](size_t at, uint32_t value) { std::memcpy(bytes.data() + at, &value, 4); };
put(0, uint32_t(entries.size()));
for (size_t index = 0; index < entries.size(); ++index) {
const auto &entry = entries[index];
const size_t at = 4 + index * row;
const uint32_t local_port = htons(uint16_t(entry.local_port)), remote_port = htons(uint16_t(entry.remote_port));
if (!v6) {
// MIB_TCPROW(_OWNER_PID): state, local address and port, remote address and port, owner.
put(at, entry.state);
std::memcpy(bytes.data() + at + 4, entry.local.data(), 4);
put(at + 8, local_port);
std::memcpy(bytes.data() + at + 12, entry.remote.data(), 4);
put(at + 16, entry.state == 2 ? 0 : remote_port);
if (owner)
put(at + 20, entry.pid);
} else {
// MIB_TCP6ROW(_OWNER_PID): local address, scope, port, remote address, scope, port, state, owner.
std::memcpy(bytes.data() + at, entry.local.data(), 16);
put(at + 20, local_port);
std::memcpy(bytes.data() + at + 24, entry.remote.data(), 16);
put(at + 44, entry.state == 2 ? 0 : remote_port);
put(at + 48, entry.state);
if (owner)
put(at + 52, entry.pid);
}
}
call.write(call[0], bytes);
call.store<uint32_t>(call[1], needed);
call.ret(0);
});
registry.add("ioctlsocket", 3, [socket_of, fail](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket || !call[2]) {
fail(call, socket ? wsaefault : wsaenotsock);
return;
}
if (call.u32(1) == fionbio) {
socket->guest_nonblocking = call.load<uint32_t>(call[2]) != 0;
} else if (call.u32(1) == fionread) {
int available = 0;
::ioctl(socket->descriptor, FIONREAD, &available);
call.store<uint32_t>(call[2], uint32_t(available));
} else {
fail(call, wsaeinval);
return;
}
call.ret(0);
});
registry.add("WSAIoctl", 9, [socket_of, fail, resolve_export](ApiCall &call) {
const auto socket = socket_of(call[0]);
if (!socket) {
fail(call, wsaenotsock);
return;
}
// Completion by overlapped structure or routine is not carried out: say so rather than finish the call as if it were.
if (call[7] || call[8]) {
fail(call, wsaeopnotsupp);
return;
}
const auto code = call.u32(1);
if (code == fionbio && call[2] && call.u32(3) >= 4) {
socket->guest_nonblocking = call.load<uint32_t>(call[2]) != 0;
} else if (code == fionread && call[4] && call.u32(5) >= 4) {
int available = 0;
::ioctl(socket->descriptor, FIONREAD, &available);
call.store<uint32_t>(call[4], uint32_t(available));
} else if (code == sio_get_extension_function && call[2] && call.u32(3) >= 16 && call[4] && call.u32(5) >= call.pointer_width()) {
static constexpr struct { uint32_t first; uint16_t second, third; uint8_t rest[8]; const char *name; } known[] = {
{0x25a207b9, 0xddf3, 0x4660, {0x8e, 0xe9, 0x76, 0xe5, 0x8c, 0x74, 0x06, 0x3e}, "ConnectEx"},
{0xb5367df1, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}, "AcceptEx"},
{0xb5367df2, 0xcbac, 0x11cf, {0x95, 0xca, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92}, "GetAcceptExSockaddrs"},
{0x7fda2e11, 0x8630, 0x436f, {0xa0, 0x31, 0xf5, 0x36, 0xa6, 0xee, 0xc1, 0x57}, "DisconnectEx"},
{0xa441e712, 0x754f, 0x43ca, {0x84, 0xa7, 0x0d, 0xee, 0x44, 0xcf, 0x60, 0x6d}, "WSASendMsg"},
{0xf689d7c8, 0x6f1f, 0x436b, {0x8a, 0x53, 0xe5, 0x4f, 0xe3, 0x51, 0xc3, 0x22}, "WSARecvMsg"},
};
const auto guid = call.read(call[2], 16);
const char *name = nullptr;
for (const auto &entry : known) {
uint32_t first;
uint16_t second, third;
std::memcpy(&first, guid.data(), 4);
std::memcpy(&second, guid.data() + 4, 2);
std::memcpy(&third, guid.data() + 6, 2);
if (first == entry.first && second == entry.second && third == entry.third && std::memcmp(guid.data() + 8, entry.rest, 8) == 0)
name = entry.name;
}
if (!name) {
fail(call, wsaeinval);
return;
}
try {
call.store_pointer(call[4], resolve_export("ws2_32.dll", name));
} catch (const std::runtime_error &) {
fail(call, wsaeopnotsupp);
return;
}
} else if ((code == sio_base_handle || code == sio_bsp_handle || code == sio_bsp_handle_select || code == sio_bsp_handle_poll) &&
call[4] && call.u32(5) >= call.pointer_width()) {
// No layered service provider sits on a socket here, so the socket is its own base handle.
call.store_pointer(call[4], call[0]);
} else if (code == sio_keepalive_vals && call[2] && call.u32(3) >= 12) {
const int enabled = call.load<uint32_t>(call[2]) != 0, idle = int(std::max<uint32_t>(call.load<uint32_t>(call[2] + 4) / 1000, 1)),
interval = int(std::max<uint32_t>(call.load<uint32_t>(call[2] + 8) / 1000, 1));
::setsockopt(socket->descriptor, SOL_SOCKET, SO_KEEPALIVE, &enabled, sizeof(enabled));
::setsockopt(socket->descriptor, IPPROTO_TCP, TCP_KEEPIDLE, &idle, sizeof(idle));
::setsockopt(socket->descriptor, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
} else {
fail(call, wsaeopnotsupp);
return;
}
if (call[6])
call.store<uint32_t>(call[6], code == fionread ? 4 : code == sio_get_extension_function || (code & 0xfffffff0U) == 0x48000010U ||
code == sio_base_handle ? uint32_t(call.pointer_width()) : 0);
call.ret(0);
});
// select: an fd_set is a count followed by that many socket handles, each as wide as the program's pointers.
const auto set_at = [](ApiCall &call) { return call.pointer_width() == 4 ? size_t(4) : size_t(8); };
registry.add("select", 5, [socket_of, fail, set_at, &threads](ApiCall &call) {
struct Entry {
uint64_t handle;
int descriptor;
};
std::vector<Entry> sets[3];
const size_t first = set_at(call), width = call.pointer_width();
for (int kind = 0; kind < 3; ++kind) {
const auto address = call[1 + size_t(kind)];
if (!address)
continue;
const uint32_t count = std::min<uint32_t>(call.load<uint32_t>(address), 64);
for (uint32_t index = 0; index < count; ++index) {
const auto handle = call.load_pointer(address + first + index * width);
const auto socket = socket_of(handle);
if (!socket) {
fail(call, wsaenotsock);
return;
}
sets[kind].push_back({handle, socket->descriptor});
}
}
std::optional<std::chrono::steady_clock::time_point> wake;
if (call[4]) {
const auto seconds = call.load<int32_t>(call[4]), micros = call.load<int32_t>(call[4] + 4);
wake = std::chrono::steady_clock::now() + std::chrono::seconds(std::max(seconds, 0)) + std::chrono::microseconds(std::max(micros, 0));
}
static constexpr short events[3] = {POLLIN, POLLOUT, POLLPRI};
const auto poll_once = [&](std::vector<pollfd> &entries) {
entries.clear();
for (int kind = 0; kind < 3; ++kind)
for (const auto &item : sets[kind])
entries.push_back({item.descriptor, events[kind], 0});
return entries.empty() ? 0 : ::poll(entries.data(), nfds_t(entries.size()), 0);
};
std::vector<pollfd> entries;
int ready = poll_once(entries);
if (ready <= 0 && !(call[4] && wake && *wake <= std::chrono::steady_clock::now())) {
HostWait waiting;
threads.block([&] {
std::vector<pollfd> again;
return poll_once(again) > 0;
}, wake);
ready = poll_once(entries);
}
if (ready < 0) {
fail(call, wsa_error(errno));
return;
}
size_t position = 0;
uint32_t total = 0;
for (int kind = 0; kind < 3; ++kind) {
const auto address = call[1 + size_t(kind)];
std::vector<uint64_t> kept;
for (const auto &item : sets[kind]) {
const auto revents = entries[position++].revents;
const auto socket = socket_of(item.handle);
const bool connecting = socket && socket->connecting;
const bool failed = (revents & (POLLERR | POLLHUP)) != 0;
bool hit;
if (connecting && kind == 1) {
hit = (revents & POLLOUT) != 0 && !failed;
} else if (connecting && kind == 2) {
hit = failed;
} else {
hit = kind == 0 ? (revents & (POLLIN | POLLHUP | POLLERR)) != 0 : kind == 1 ? (revents & (POLLOUT | POLLERR)) != 0 : (revents & POLLPRI) != 0;
}
if (hit && connecting && kind != 0)
socket->connecting = false;
if (hit)
kept.push_back(item.handle);
}
total += uint32_t(kept.size());
if (!address)
continue;
call.store<uint32_t>(address, uint32_t(kept.size()));
for (size_t index = 0; index < kept.size(); ++index) {
if (width == 4)
call.store<uint32_t>(address + first + index * width, uint32_t(kept[index]));
else
call.store<uint64_t>(address + first + index * width, kept[index]);
}
}
call.ret(total);
});
registry.add("__WSAFDIsSet", 2, [set_at](ApiCall &call) {
const auto address = call[1];
if (!address) {
call.ret(0);
return;
}
const size_t first = set_at(call), width = call.pointer_width();
const uint32_t count = std::min<uint32_t>(call.load<uint32_t>(address), 64);
for (uint32_t index = 0; index < count; ++index)
if (call.load_pointer(address + first + index * width) == (width == 4 ? (call[0] & 0xffffffffULL) : call[0])) {
call.ret(1);
return;
}
call.ret(0);
});
// Names: the answers come from the host resolver, and the result is one block of guest memory that freeaddrinfo gives back.
// getaddrinfo and GetAddrInfoW: the host resolver's answer as a chain of ADDRINFOA or ADDRINFOW records in one block the program
// frees with freeaddrinfo or FreeAddrInfoW. The two records differ only in the width of the canonical name.
const auto resolve_names = [&context](ApiCall &call, bool wide) {
const size_t width = call.pointer_width();
// ADDRINFOA: four ints, the address length, then the canonical name, the address and the next entry.
const size_t record = 16 + 4 * width;
const size_t length_at = 16, canon_at = 16 + width, address_at = 16 + 2 * width, next_at = 16 + 3 * width;
addrinfo hints{};
const addrinfo *use = nullptr;
if (call[2]) {
hints.ai_flags = call.load<int32_t>(call[2]) & (AI_PASSIVE | AI_CANONNAME | AI_NUMERICHOST);
hints.ai_family = host_family(call.load<int32_t>(call[2] + 4));
hints.ai_socktype = call.load<int32_t>(call[2] + 8);
hints.ai_protocol = call.load<int32_t>(call[2] + 12);
use = &hints;
}
const auto text = [&](uint64_t address, size_t limit) { return wide ? to_utf8(call.wide(address, limit)) : call.narrow(address, limit); };
const auto node = call[0] ? text(call[0], 1025) : std::string();
const auto service = call[1] ? text(call[1], 256) : std::string();
addrinfo *found = nullptr;
const int status = ::getaddrinfo(call[0] ? node.c_str() : nullptr, call[1] ? service.c_str() : nullptr, use, &found);
if (status) {
const uint32_t error = status == EAI_NONAME || status == EAI_NODATA ? wsahost_not_found : status == EAI_AGAIN ? 11002 :
status == EAI_FAIL ? 11003 : status == EAI_SERVICE ? 10109 : status == EAI_FAMILY ? wsaeafnosupport : status == EAI_SOCKTYPE ? wsaesocktnosupport : wsaeinval;
call.fail(error, error);
return;
}
size_t count = 0, size = 0;
for (const auto *entry = found; entry; entry = entry->ai_next) {
++count;
size += record + 32 + (entry->ai_canonname ? (std::strlen(entry->ai_canonname) + 1) * (wide ? 2 : 1) : 0);
}
const auto block = context.allocator.allocate(size, true);
if (!block) {
::freeaddrinfo(found);
call.fail(wsaenobufs, wsaenobufs);
return;
}
uint64_t cursor = block, previous = 0;
for (const auto *entry = found; entry; entry = entry->ai_next) {
const uint64_t self = cursor, address = self + record, canonical = address + 32;
cursor = canonical + (entry->ai_canonname ? (std::strlen(entry->ai_canonname) + 1) * (wide ? 2 : 1) : 0);
call.store<int32_t>(self, entry->ai_flags);
call.store<int32_t>(self + 4, windows_family(entry->ai_family));
call.store<int32_t>(self + 8, entry->ai_socktype);
call.store<int32_t>(self + 12, entry->ai_protocol);
if (width == 4)
call.store<uint32_t>(self + length_at, entry->ai_addrlen);
else
call.store<uint64_t>(self + length_at, entry->ai_addrlen);
std::vector<uint8_t> bytes(reinterpret_cast<const uint8_t *>(entry->ai_addr), reinterpret_cast<const uint8_t *>(entry->ai_addr) + entry->ai_addrlen);
if (bytes.size() >= 2) {
uint16_t family;
std::memcpy(&family, bytes.data(), 2);
family = uint16_t(windows_family(family));
std::memcpy(bytes.data(), &family, 2);
}
call.write(address, bytes);
call.store_pointer(self + address_at, address);
if (entry->ai_canonname) {
const std::string name(entry->ai_canonname);
if (wide)
call.put_wide(canonical, from_utf8(name), name.size() + 1);
else
call.write(canonical, std::span<const uint8_t>(reinterpret_cast<const uint8_t *>(name.c_str()), name.size() + 1));
call.store_pointer(self + canon_at, canonical);
}
if (previous)
call.store_pointer(previous + next_at, self);
previous = self;
}
::freeaddrinfo(found);
call.store_pointer(call[3], block);
call.ret(0);
(void)count;
};
registry.add("getaddrinfo", 4, [resolve_names](ApiCall &call) { resolve_names(call, false); });
registry.add("GetAddrInfoW", 4, [resolve_names](ApiCall &call) { resolve_names(call, true); });
// getnameinfo and GetNameInfoW: the host and service of an address, by the host resolver, with Winsock's NI_ flags.
const auto name_of_address = [](ApiCall &call, bool wide) {
const auto address = host_address(call, call[0], size_t(call.i32(1)));
const uint32_t flags = call.u32(6);
const int host_flags = (flags & 0x01 ? NI_NOFQDN : 0) | (flags & 0x02 ? NI_NUMERICHOST : 0) | (flags & 0x04 ? NI_NAMEREQD : 0) |
(flags & 0x08 ? NI_NUMERICSERV : 0) | (flags & 0x10 ? NI_DGRAM : 0);
char host[1025] = {}, service[32] = {};
const int status = ::getnameinfo(reinterpret_cast<const sockaddr *>(address.data()), socklen_t(address.size()), call[2] ? host : nullptr,
call[2] ? sizeof host : 0, call[4] ? service : nullptr, call[4] ? sizeof service : 0, host_flags);
if (status) {
const uint32_t error = status == EAI_NONAME ? wsahost_not_found : status == EAI_AGAIN ? 11002 : status == EAI_FAMILY ? wsaeafnosupport : wsaeinval;
call.fail(error, error);
return;
}
const auto put = [&](uint64_t buffer, uint32_t capacity, const char *text) {
const size_t length = std::strlen(text);
if (!buffer)
return true;
if (capacity <= length)
return false;
if (wide)
call.put_wide(buffer, from_utf8(text), capacity);
else
call.put_narrow(buffer, text, capacity);
return true;
};
if (!put(call[2], call.u32(3), host) || !put(call[4], call.u32(5), service)) {
call.fail(wsaefault, wsaefault);
return;
}
call.ret(0);
};
registry.add("getnameinfo", 7, [name_of_address](ApiCall &call) { name_of_address(call, false); });
registry.add("GetNameInfoW", 7, [name_of_address](ApiCall &call) { name_of_address(call, true); });
for (const char *name : {"freeaddrinfo", "FreeAddrInfoW"})
registry.add(name, 1, [&context](ApiCall &call) {
if (call[0])
context.allocator.release(call[0]);
});
registry.add("gethostname", 2, [](ApiCall &call) {
char name[256] = {};
::gethostname(name, sizeof(name) - 1);
const std::string text(name);
if (!call[0] || call.i32(1) <= int(text.size())) {
call.fail(call[0] ? wsaefault : wsaefault, UINT64_MAX);
return;
}
call.put_narrow(call[0], text, size_t(call.i32(1)));
call.ret(0);
});
registry.add("inet_addr", 1, [](ApiCall &call) {
const auto text = call.narrow(call[0], 64);
in_addr address{};
call.ret(::inet_aton(text.c_str(), &address) ? address.s_addr : 0xffffffffU);
});
auto text_block = std::make_shared<uint64_t>(0);
registry.add("inet_ntoa", 1, [&context, text_block](ApiCall &call) {
if (!*text_block)
*text_block = context.allocator.allocate(32, true);
in_addr address{};
address.s_addr = call.u32(0);
const std::string text = ::inet_ntoa(address);
call.put_narrow(*text_block, text, 32);
call.ret(*text_block);
});
// getservbyname and getservbyport answer from the services database into one SERVENT the process reuses, as Winsock's per-thread
// one is reused; the 64-bit layout keeps the protocol before the port.
auto service_block = std::make_shared<uint64_t>(0);
const auto service = [&context, service_block](ApiCall &call, const servent *found) {
if (!found) {
call.fail(11004, 0);
return;
}
if (!*service_block)
*service_block = context.allocator.allocate(256, true);
const uint64_t block = *service_block, aliases = block + 32, name = block + 48, protocol = block + 160;
const bool narrow = call.pointer_width() == 4;
call.put_narrow(name, std::string(found->s_name).substr(0, 111), 112);
call.put_narrow(protocol, std::string(found->s_proto).substr(0, 31), 32);
call.store_pointer(aliases, 0);
call.store_pointer(block, name);
call.store_pointer(block + (narrow ? 4 : 8), aliases);
call.store<uint16_t>(block + (narrow ? 8 : 24), uint16_t(found->s_port));
call.store_pointer(block + (narrow ? 12 : 16), protocol);
call.ret(block);
};
registry.add("getservbyname", 2, [service](ApiCall &call) {
const auto name = call.narrow(call[0], 256);
const auto protocol = call[1] ? call.narrow(call[1], 32) : std::string();
servent entry{}, *found = nullptr;
std::array<char, 1024> buffer{};
getservbyname_r(name.c_str(), call[1] ? protocol.c_str() : nullptr, &entry, buffer.data(), buffer.size(), &found);
service(call, found);
});
registry.add("getservbyport", 2, [service](ApiCall &call) {
const auto protocol = call[1] ? call.narrow(call[1], 32) : std::string();
servent entry{}, *found = nullptr;
std::array<char, 1024> buffer{};
getservbyport_r(int(uint16_t(call.u32(0))), call[1] ? protocol.c_str() : nullptr, &entry, buffer.data(), buffer.size(), &found);
service(call, found);
});
// getprotobyname and getprotobynumber answer from the protocols database into one PROTOENT the process reuses, as the SERVENT above
// is: the name and its aliases are pointers, so the record keeps the 32-bit and 64-bit layouts apart.
auto protocol_block = std::make_shared<uint64_t>(0);
const auto protocol_entry = [&context, protocol_block](ApiCall &call, const protoent *found) {
if (!found) {
call.fail(11004, 0);
return;
}
if (!*protocol_block)
*protocol_block = context.allocator.allocate(256, true);
const uint64_t block = *protocol_block;
const bool narrow = call.pointer_width() == 4;
// The name sits past the record and the empty list of aliases it points at.
const uint64_t aliases = block + (narrow ? 12 : 24), name = aliases + (narrow ? 4 : 8);
call.put_narrow(name, std::string(found->p_name).substr(0, 111), 112);
call.store_pointer(block, name);
call.store_pointer(block + (narrow ? 4 : 8), aliases);
call.store_pointer(aliases, 0);
call.store<uint16_t>(block + (narrow ? 8 : 16), uint16_t(found->p_proto));
call.ret(block);
};
registry.add("getprotobyname", 1, [protocol_entry](ApiCall &call) {
const auto name = call.narrow(call[0], 256);
protoent entry{}, *found = nullptr;
std::array<char, 1024> buffer{};
getprotobyname_r(name.c_str(), &entry, buffer.data(), buffer.size(), &found);
protocol_entry(call, found);
});
registry.add("getprotobynumber", 1, [protocol_entry](ApiCall &call) {
protoent entry{}, *found = nullptr;
std::array<char, 1024> buffer{};
getprotobynumber_r(int(call.u32(0)), &entry, buffer.data(), buffer.size(), &found);
protocol_entry(call, found);
});
registry.add("htons", 1, [](ApiCall &call) { call.ret(uint16_t(call.u32(0) << 8 | (call.u32(0) >> 8 & 0xff))); });
registry.add("ntohs", 1, [](ApiCall &call) { call.ret(uint16_t(call.u32(0) << 8 | (call.u32(0) >> 8 & 0xff))); });
registry.add("htonl", 1, [](ApiCall &call) { call.ret(__builtin_bswap32(call.u32(0))); });
registry.add("ntohl", 1, [](ApiCall &call) { call.ret(__builtin_bswap32(call.u32(0))); });
// WSADATA differs in field order: the version words come first in both, the rest follows the program's layout.
// A numeric address with an optional port ("1.2.3.4:80", "[::1]:80" or a bare address) as a SOCKADDR of the family asked for.
for (const bool wide : {true, false})
registry.add(wide ? "WSAStringToAddressW" : "WSAStringToAddressA", 5, [wide](ApiCall &call) {
const auto text = wide ? to_utf8(call.wide(call[0], 256)) : call.narrow(call[0], 256);
const int family = call.i32(1);
if (!call[3] || !call[4] || (family != 2 && family != 23)) {
call.fail(family != 2 && family != 23 ? 10047U : wsaefault, UINT64_MAX);
return;
}
std::string host = text;
unsigned long port = 0;
const auto port_of = [&](const std::string &digits) {
char *end = nullptr;
port = std::strtoul(digits.c_str(), &end, 10);
return !digits.empty() && end && !*end && port <= 65535;
};
bool valid = true;
if (family == 23 && !text.empty() && text[0] == '[') {
const auto close = text.find(']');
valid = close != text.npos && (close + 1 == text.size() || (text[close + 1] == ':' && port_of(text.substr(close + 2))));
host = valid ? text.substr(1, close - 1) : std::string();
} else if (family == 2 && text.find(':') != text.npos) {
const auto colon = text.find(':');
valid = port_of(text.substr(colon + 1));
host = text.substr(0, colon);
}
std::vector<uint8_t> address(family == 2 ? 16 : 28, 0);
const uint16_t kind = uint16_t(family), network_port = htons(uint16_t(port));
std::memcpy(address.data(), &kind, 2);
std::memcpy(address.data() + 2, &network_port, 2);
if (!valid || ::inet_pton(family == 2 ? AF_INET : AF_INET6, host.c_str(), address.data() + (family == 2 ? 4 : 8)) != 1) {
call.fail(wsaeinval, UINT64_MAX);
return;
}
const auto capacity = call.load<int32_t>(call[4]);
call.store<int32_t>(call[4], int32_t(address.size()));
if (capacity < int32_t(address.size())) {
call.fail(wsaefault, UINT64_MAX);
return;
}
call.write(call[3], address);
call.ret(0);
});
// WSAEnumProtocols(protocols, buffer, length): the base TCP and UDP providers of IPv4 and IPv6, each as a WSAPROTOCOL_INFO with
// the service flags, provider identifiers and names the Microsoft provider publishes; a list of protocol numbers narrows them.
const auto enumerate_protocols = [](ApiCall &call, bool wide) {
struct Protocol {
uint32_t service_flags, catalog;
bool version6;
int32_t type, protocol;
uint32_t message_size;
const char *name;
};
static constexpr Protocol protocols[] = {
{0x20066, 1001, false, 1, 6, 0, "MSAFD Tcpip [TCP/IP]"},
{0x20609, 1002, false, 2, 17, 0xfff7, "MSAFD Tcpip [UDP/IP]"},
{0x20066, 1003, true, 1, 6, 0, "MSAFD Tcpip [TCP/IPv6]"},
{0x20609, 1004, true, 2, 17, 0xfff7, "MSAFD Tcpip [UDP/IPv6]"},
};
static constexpr std::array<uint8_t, 16> provider4{0xa0, 0x1a, 0x0f, 0xe7, 0x8b, 0xab, 0xcf, 0x11, 0x8c, 0xa3, 0x00, 0x80, 0x5f, 0x48, 0xa1, 0x92};
static constexpr std::array<uint8_t, 16> provider6{0xc0, 0xb0, 0xea, 0xf9, 0xd4, 0x26, 0xd0, 0x11, 0xbb, 0xbf, 0x00, 0xaa, 0x00, 0x6c, 0x34, 0xe4};
std::vector<int32_t> wanted;
if (call[0])
for (uint64_t at = call[0];; at += 4) {
const auto value = call.load<int32_t>(at);
if (!value || wanted.size() == 64)
break;
wanted.push_back(value);
}
const size_t entry = wide ? 628 : 372;
std::vector<uint8_t> bytes;
size_t count = 0;
for (const auto &protocol : protocols) {
if (call[0] && std::find(wanted.begin(), wanted.end(), protocol.protocol) == wanted.end())
continue;
std::vector<uint8_t> info(entry);
const auto put = [&info](size_t at, uint32_t value) { std::memcpy(info.data() + at, &value, 4); };
put(0, protocol.service_flags);
put(16, 0x8);
std::memcpy(info.data() + 20, (protocol.version6 ? provider6 : provider4).data(), 16);
put(36, protocol.catalog);
put(40, 1);
put(72, 2);
put(76, protocol.version6 ? 23 : 2);
put(80, protocol.version6 ? 28 : 16);
put(84, protocol.version6 ? 28 : 16);
put(88, uint32_t(protocol.type));
put(92, uint32_t(protocol.protocol));
put(108, protocol.message_size);
for (size_t at = 0; protocol.name[at]; ++at) {
if (wide)
info[116 + at * 2] = uint8_t(protocol.name[at]);
else
info[116 + at] = uint8_t(protocol.name[at]);
}
bytes.insert(bytes.end(), info.begin(), info.end());
++count;
}
const uint32_t capacity = call[2] ? call.load<uint32_t>(call[2]) : 0;
if (!call[2]) {
call.fail(wsaefault, UINT64_MAX);
return;
}
if (!call[1] || capacity < bytes.size()) {
call.store<uint32_t>(call[2], uint32_t(bytes.size()));
call.fail(10055, UINT64_MAX);
return;
}
call.write(call[1], bytes);
call.ret(count);
};
registry.add("WSAEnumProtocolsW", 3, [enumerate_protocols](ApiCall &call) { enumerate_protocols(call, true); });
registry.add("WSAEnumProtocolsA", 3, [enumerate_protocols](ApiCall &call) { enumerate_protocols(call, false); });
registry.add("WSAStartup", 2, [](ApiCall &call) {
if (!call[1]) {
call.ret(10014);
return;
}
const uint16_t wanted = uint16_t(call.u32(0));
const uint8_t major = uint8_t(wanted), minor = uint8_t(wanted >> 8);
if (major == 0 || major > 2 || (major == 2 && minor > 2)) {
call.ret(10092);
return;
}
const bool narrow = call.pointer_width() == 4;
std::vector<uint8_t> data(narrow ? 400 : 408);
const uint16_t version = wanted, high = 0x0202;
std::memcpy(data.data(), &version, 2);
std::memcpy(data.data() + 2, &high, 2);
const char description[] = "Raven Winsock 2.2", status[] = "Running";
const uint16_t sockets = 0x7fff, datagram = 65467;
if (narrow) {
std::memcpy(data.data() + 4, description, sizeof(description));
std::memcpy(data.data() + 261, status, sizeof(status));
std::memcpy(data.data() + 390, &sockets, 2);
std::memcpy(data.data() + 392, &datagram, 2);
} else {
std::memcpy(data.data() + 4, &sockets, 2);
std::memcpy(data.data() + 6, &datagram, 2);
std::memcpy(data.data() + 16, description, sizeof(description));
std::memcpy(data.data() + 273, status, sizeof(status));
}
call.write(call[1], data);
call.ret(0);
});
registry.add("WSACleanup", 0, [](ApiCall &call) { call.ret(0); });
registry.add("WSAGetLastError", 0, [](ApiCall &call) { call.ret(call.last_error()); });
registry.add("WSASetLastError", 1, [](ApiCall &call) { call.set_last_error(call.u32(0)); });
}
}