#include "child_process.h" #include "completion_port.h" #include "kernel_context.h" #include #include "text.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 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(&peer), &size)) { if (peer.ss_family == AF_INET) { const auto &address = reinterpret_cast(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(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::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 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(length_address); std::vector bytes(reinterpret_cast(&storage), reinterpret_cast(&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(bytes.data(), std::min(bytes.size(), capacity))); call.store(length_address, uint32_t(length)); } constexpr uint32_t sol_socket_windows = 0xffff; std::vector 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 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 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(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 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 port; uint64_t key = 0; std::vector> polls; }; std::shared_ptr make_afd_endpoint() { return std::make_shared(); } 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(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(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 socket; }; std::vector 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(at) : call.load(at); const auto socket = context.objects.object_as(handle); if (!socket) { call.ret(0xc0000008U); return true; } watched.push_back({handle, call.load(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(input); const auto now_ticks = [] { using namespace std::chrono; return int64_t(duration_cast(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(input + 12); auto poll = std::make_shared(); 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(reinterpret_cast(&value), size)); }; put(status_block, status_pending, width); put(status_block + width, 0, width); const auto attempt = [=, &process, &objects, endpoint = std::weak_ptr(endpoint)]() { const auto owner = endpoint.lock(); if (poll->done) return true; std::vector 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> 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 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 afd_cancel(KernelContext &context, uint64_t handle, uint64_t status_block) { const auto endpoint = context.objects.object_as(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(handle)) return true; const auto socket = objects.object_as(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 socket_owners() { std::map 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 tcp_entries(bool v6) { std::vector entries; std::ifstream table(v6 ? "/proc/net/tcp6" : "/proc/net/tcp"); std::string line; std::getline(table, line); std::map 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 &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 ®istry, KernelContext &context) { auto &objects = context.objects; auto &threads = context.threads; const auto socket_of = [&objects](uint64_t handle) { return objects.object_as(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 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(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(call[3] + 76); if (type == -1) type = call.load(call[3] + 88); if (protocol == -1) protocol = call.load(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(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); }); // connect, and WSAConnect with it: the connect data and quality of service WSAConnect may name are not something TCP carries, and // the provider below it ignores them as Windows' does. const auto connect_socket = [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(address.data())->sa_family == AF_INET) { const auto *target = reinterpret_cast(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(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("connect", 3, connect_socket); registry.add("WSAConnect", 7, connect_socket); 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(&storage), &length, SOCK_NONBLOCK | SOCK_CLOEXEC); if (accepted >= 0) { const auto made = objects.add_object(std::make_shared(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 &socket, uint64_t buffer, size_t length, uint32_t flags, sockaddr_storage *from, socklen_t *from_length) -> int64_t { std::vector bytes(std::min(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(from), from_length); if (count >= 0) { call.write(buffer, std::span(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 &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(bytes.size(), 600)), reinterpret_cast(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 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(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 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(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 socket; bool receiving = false; std::vector buffers; std::vector outgoing; std::vector control; size_t sent = 0; uint32_t flags = 0; uint64_t from = 0, from_length = 0, flags_out = 0, overlapped = 0; std::vector 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 data(std::min(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(&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(buffer.length, size_t(count) - offset); if (part) process.write_memory(buffer.address, std::span(data.data() + offset, part)); offset += part; } if (transfer.from) { std::vector raw(reinterpret_cast(&from), reinterpret_cast(&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(reinterpret_cast(&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(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 &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 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(bytes_out, uint32_t(bytes)); call.ret(0); return; } // Until it finishes the OVERLAPPED says the operation is pending. { std::vector 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(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(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(call[3], uint32_t(total)); if (call[4]) call.store(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(bytes.size(), 600)), reinterpret_cast(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(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(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(call[3], uint32_t(count)); if (call[4]) call.store(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 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(to.data()), socklen_t(to.size())); if (count < 0) { call.ret(UINT32_MAX); return; } if (call[3]) call.store(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(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(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(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(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, [®istry, 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(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(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{}; 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(address.data()), socklen_t(address.size())) == 0) { const auto bytes = done(0, true); if (sent_out) call.store(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; } const size_t width = call.pointer_width(); const uint64_t overlapped = call[7], receive_length = call.u32(3), local_length = call.u32(4), remote_length = call.u32(5); // With a receive length the first data of the connection comes first in the buffer and the addresses after it; the operation // ends when that data has come, with its size. const uint64_t data = call[2], buffer = call[2] + receive_length; auto connected = std::make_shared(false); const auto receive = [target, &process, data, receive_length]() -> int64_t { std::vector bytes(receive_length); const auto got = ::recv(target->descriptor, bytes.data(), bytes.size(), 0); if (got < 0) return errno == EAGAIN || errno == EWOULDBLOCK ? -1 : -int64_t(wsa_error(errno)) - 2; bytes.resize(size_t(got)); process.write_memory(data, bytes); return got; }; const auto accept_connection = [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(&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 raw(capacity); std::memcpy(raw.data(), &storage, std::min(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(&local), &local_size); write_address(buffer, local_length, local, local_size); write_address(buffer + local_length, remote_length, peer, peer_length); return 0; }; // -1 while the operation waits, otherwise the error it ends with and, through moved, the bytes received. const auto attempt = [accept_connection, receive, connected, receive_length](uint32_t &moved) -> int { if (!*connected) { const int accepted = accept_connection(); if (accepted) return accepted; *connected = true; } if (!receive_length) return 0; const auto got = receive(); if (got == -1) return -1; if (got < -1) return int(-got - 2); moved = uint32_t(got); return 0; }; uint32_t moved = 0; const int first = attempt(moved); if (first == 0) { finish(listener, overlapped, width, 0, moved, true); if (call[6]) call.store(call[6], moved); call.ret(1); return; } if (first > 0) { fail(call, uint32_t(first), 0); return; } // The connection is waited for on the listening socket, its first data on the accepted one. const auto wait = [&reactor, attempt, finish, listener, target, connected, overlapped, width](auto &&self) -> void { reactor.add([attempt, finish, listener, target, connected, overlapped, width, self, &reactor]() { if (listener->closed || (*connected && target->closed)) { finish(listener, overlapped, width, 995, 0, false); return true; } const bool was_connected = *connected; uint32_t moved = 0; const int result = attempt(moved); if (result < 0) { if (!was_connected && *connected) { self(self); return true; } return false; } finish(listener, overlapped, width, uint32_t(result), moved, false); return true; }, *connected ? target->descriptor : listener->descriptor, short(POLLIN)); }; wait(wait); 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(address); return uint32_t(family == windows_inet6 ? 28 : 16); }; if (call[4]) call.store_pointer(call[4], local); if (call[5]) call.store(call[5], length_of(local)); if (call[6]) call.store_pointer(call[6], remote); if (call[7]) call.store(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 port; if (call[1]) { port = objects.object_as(call[1]); if (!port) { call.fail(6, 0); return; } } const bool create = !port; uint64_t handle = call[1]; if (create) { port = std::make_shared(); 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(call[0]); if (const auto endpoint = objects.object_as(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(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 &port, uint32_t milliseconds) -> std::optional { std::optional 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(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(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(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(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(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(&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(&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