mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-21 13:54:55 -04:00
e9f1fdd77c
* add ntsc-u and ntsc-j support Co-authored-by: DarthMDev <mgracer48@yahoo.com> Co-authored-by: rooklz <rookieb@muzo-works.com> * hash inc files and require project for shards * port remaining stale rr us hooks * drop map report generation * drop one-off region porters --------- Co-authored-by: DarthMDev <mgracer48@yahoo.com> Co-authored-by: rooklz <rookieb@muzo-works.com>
1072 lines
43 KiB
C++
1072 lines
43 KiB
C++
#include "network_internal.h"
|
|
#include "guest_abi/guest_region.h"
|
|
|
|
#include "fiber_manager.h"
|
|
#include "hle/net/network.h"
|
|
#include "ppc_runtime.h"
|
|
#include "runtime_log.h"
|
|
|
|
void OS_HLE_WakeupThreadNoReschedule(CpuContext* ctx, uint32_t waitQueue);
|
|
void NandQueueIosCallback(uint32_t callbackPtr, int32_t result, uint32_t callbackArg);
|
|
|
|
namespace NetworkHle {
|
|
|
|
enum class DeferredDnsKind {
|
|
GetHostByName,
|
|
GetAddrInfo,
|
|
InetAton,
|
|
};
|
|
|
|
static const char* DeferredDnsKindName(DeferredDnsKind kind) {
|
|
switch (kind) {
|
|
case DeferredDnsKind::GetHostByName:
|
|
return "gethostbyname";
|
|
case DeferredDnsKind::GetAddrInfo:
|
|
return "getaddrinfo";
|
|
case DeferredDnsKind::InetAton:
|
|
return "inet_aton";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
enum class DeferredNetworkCompletionKind {
|
|
SyncWaitQueue,
|
|
AsyncCallback,
|
|
};
|
|
|
|
struct DeferredNetworkRoute {
|
|
DeferredNetworkCompletionKind kind = DeferredNetworkCompletionKind::SyncWaitQueue;
|
|
uint64_t token = 0;
|
|
uint32_t waitQueue = 0;
|
|
uint32_t expectedThread = 0;
|
|
uint32_t callback = 0;
|
|
uint32_t callbackArg = 0;
|
|
};
|
|
|
|
// This is deliberately a host-only value object. Guest strings, vectors and
|
|
// hints are copied by the emulation thread before the worker is launched. The
|
|
// worker may carry guest output addresses as opaque integers, but it has no
|
|
// CpuContext or guest-memory pointer with which to dereference them.
|
|
struct DeferredDnsWork {
|
|
DeferredDnsKind kind = DeferredDnsKind::GetHostByName;
|
|
DeferredNetworkRoute route{};
|
|
std::string node;
|
|
std::string service;
|
|
bool hasHints = false;
|
|
int hintFlags = 0;
|
|
int hintFamily = AF_UNSPEC;
|
|
int hintSockType = 0;
|
|
int hintProtocol = 0;
|
|
uint32_t outAddress = 0;
|
|
uint32_t outSize = 0;
|
|
};
|
|
|
|
struct DeferredDnsAddress {
|
|
int flags = 0;
|
|
uint32_t family = 0;
|
|
int sockType = 0;
|
|
int protocol = 0;
|
|
uint32_t addressLength = 0;
|
|
uint32_t ipv4Address = 0;
|
|
std::array<uint8_t, NetworkDeferredContract::kWiiSockAddrPayloadBytes> socketData{};
|
|
};
|
|
|
|
static std::optional<DeferredDnsAddress> NormalizeDeferredDnsAddress(const addrinfo& native) {
|
|
// This IOS result layout currently owns one complete IPv4 sockaddr. Do not
|
|
// advertise an IPv6 ai_addrlen while retaining only sockaddr::sa_data.
|
|
if (!native.ai_addr ||
|
|
!NetworkDeferredContract::CanCopyIpv4SockAddr(
|
|
native.ai_family, native.ai_addrlen, AF_INET)) {
|
|
return std::nullopt;
|
|
}
|
|
const int wiiFamily = MapNativeAfToWii(native.ai_family);
|
|
if (wiiFamily != kWiiAfInet) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
DeferredDnsAddress normalized{};
|
|
normalized.flags = native.ai_flags;
|
|
normalized.family = static_cast<uint32_t>(wiiFamily);
|
|
normalized.sockType = native.ai_socktype;
|
|
normalized.protocol = native.ai_protocol;
|
|
normalized.addressLength =
|
|
static_cast<uint32_t>(NetworkDeferredContract::kWiiIpv4SockAddrBytes);
|
|
const auto* socketAddress = reinterpret_cast<const sockaddr*>(native.ai_addr);
|
|
std::memcpy(normalized.socketData.data(), socketAddress->sa_data,
|
|
normalized.socketData.size());
|
|
const auto* ipv4 = reinterpret_cast<const sockaddr_in*>(native.ai_addr);
|
|
normalized.ipv4Address = ntohl(ipv4->sin_addr.s_addr);
|
|
return normalized;
|
|
}
|
|
|
|
struct DeferredDnsCompletion {
|
|
DeferredDnsWork work{};
|
|
int32_t result = 0;
|
|
std::vector<DeferredDnsAddress> addresses;
|
|
};
|
|
|
|
struct DeferredPollWork {
|
|
DeferredNetworkRoute route{};
|
|
NetworkPollContract::Timeout timeout{};
|
|
uint32_t outAddress = 0;
|
|
uint32_t outSize = 0;
|
|
std::vector<NetworkPollContract::CopiedDescriptor> descriptors;
|
|
};
|
|
|
|
struct DeferredConnectWork {
|
|
DeferredNetworkRoute route{};
|
|
NetworkPollContract::Timeout timeout{};
|
|
uint32_t wiiFd = 0;
|
|
NativeSocket nativeFd = kInvalidSocket;
|
|
uint64_t socketGeneration = 0;
|
|
sockaddr_in peerAddress{};
|
|
std::optional<int32_t> initialResult;
|
|
};
|
|
|
|
using DeferredDnsPreparation =
|
|
NetworkDeferredContract::Preparation<DeferredDnsWork>;
|
|
using DeferredPollPreparation =
|
|
NetworkDeferredContract::Preparation<DeferredPollWork>;
|
|
using DeferredConnectPreparation =
|
|
NetworkDeferredContract::Preparation<DeferredConnectWork>;
|
|
|
|
struct DeferredNetworkStore {
|
|
std::mutex completedMutex;
|
|
std::deque<DeferredDnsCompletion> completed;
|
|
// Pending polls are submitted and probed only by the emulation scheduler
|
|
// thread, so unlike DNS worker completions they need no cross-thread lock.
|
|
std::vector<DeferredPollWork> pendingPolls;
|
|
std::vector<DeferredConnectWork> pendingConnects;
|
|
std::mutex resultMutex;
|
|
std::unordered_map<uint64_t, int32_t> syncResults;
|
|
std::mutex schedulerThreadMutex;
|
|
std::optional<std::thread::id> schedulerThread;
|
|
std::atomic<uint64_t> nextToken{1};
|
|
};
|
|
|
|
static DeferredNetworkStore& GetDeferredNetworkStore() {
|
|
// DNS workers are detached so the resolver can never block runtime
|
|
// shutdown. Keep their destination alive for the lifetime of the process;
|
|
// there is intentionally no static destructor for this state.
|
|
static auto* store = new DeferredNetworkStore();
|
|
return *store;
|
|
}
|
|
|
|
// pendingConnects is only ever touched from the emulation/scheduler thread, so
|
|
// this needs no lock. Marking the operation resolved makes the next completion
|
|
// pump wake the parked OSThread with the requested result; the connect itself is
|
|
// never re-probed afterwards.
|
|
void AbortPendingDeferredConnects(uint32_t wiiFd, int32_t result) {
|
|
DeferredNetworkStore& store = GetDeferredNetworkStore();
|
|
for (DeferredConnectWork& work : store.pendingConnects) {
|
|
if (work.wiiFd == wiiFd && !work.initialResult) {
|
|
work.initialResult = result;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void NoteDeferredNetworkSchedulerThread(const char* operation) {
|
|
DeferredNetworkStore& store = GetDeferredNetworkStore();
|
|
const std::thread::id current = std::this_thread::get_id();
|
|
std::lock_guard<std::mutex> lock(store.schedulerThreadMutex);
|
|
if (!store.schedulerThread) {
|
|
store.schedulerThread = current;
|
|
return;
|
|
}
|
|
if (*store.schedulerThread != current) {
|
|
RT_LOGF(RT_TAG_NET,
|
|
"deferred network %s ran off the emulation thread\n",
|
|
operation);
|
|
#ifndef NDEBUG
|
|
assert(*store.schedulerThread == current);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static int32_t DeferredDnsFailureResult(DeferredDnsKind kind) {
|
|
switch (kind) {
|
|
case DeferredDnsKind::GetHostByName:
|
|
return -1;
|
|
case DeferredDnsKind::GetAddrInfo:
|
|
return SO_ERROR_HOST_NOT_FOUND;
|
|
case DeferredDnsKind::InetAton:
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void QueueDeferredDnsCompletion(DeferredDnsCompletion completion) {
|
|
DeferredNetworkStore& store = GetDeferredNetworkStore();
|
|
std::lock_guard<std::mutex> lock(store.completedMutex);
|
|
store.completed.push_back(std::move(completion));
|
|
}
|
|
|
|
static DeferredDnsCompletion ResolveDeferredDns(DeferredDnsWork work) {
|
|
DeferredDnsCompletion completion{};
|
|
completion.work = std::move(work);
|
|
|
|
addrinfo hints{};
|
|
addrinfo* hintPtr = nullptr;
|
|
if (completion.work.kind != DeferredDnsKind::GetAddrInfo) {
|
|
hints.ai_family = AF_INET;
|
|
hintPtr = &hints;
|
|
} else {
|
|
if (completion.work.hasHints) {
|
|
#ifdef AF_INET6
|
|
if (completion.work.hintFamily == AF_INET6) {
|
|
// The guest result encoder deliberately owns IPv4 sockaddr
|
|
// payloads only. An IPv6-only query therefore has no valid
|
|
// result rather than a truncated sockaddr_in6.
|
|
completion.result = DeferredDnsFailureResult(completion.work.kind);
|
|
return completion;
|
|
}
|
|
#endif
|
|
hints.ai_flags = completion.work.hintFlags;
|
|
hints.ai_family = completion.work.hintFamily == AF_UNSPEC
|
|
? AF_INET
|
|
: completion.work.hintFamily;
|
|
hints.ai_socktype = completion.work.hintSockType;
|
|
hints.ai_protocol = completion.work.hintProtocol;
|
|
} else {
|
|
hints.ai_family = AF_INET;
|
|
}
|
|
hintPtr = &hints;
|
|
}
|
|
|
|
addrinfo* nativeResults = nullptr;
|
|
const char* node = completion.work.node.empty() ? nullptr : completion.work.node.c_str();
|
|
const char* service = completion.work.service.empty() ? nullptr : completion.work.service.c_str();
|
|
const int gai = getaddrinfo(node, service, hintPtr, &nativeResults);
|
|
if (gai != 0 || !nativeResults) {
|
|
completion.result = DeferredDnsFailureResult(completion.work.kind);
|
|
if (nativeResults) {
|
|
freeaddrinfo(nativeResults);
|
|
}
|
|
// The name the guest asked for is what proves whether the Retro-WFC
|
|
// payload's own resolver hooks rewrote the Nintendo hostname or not.
|
|
NetFail("dns %s '%s'%s%s FAILED gai=%d result=%d",
|
|
DeferredDnsKindName(completion.work.kind), completion.work.node.c_str(),
|
|
completion.work.service.empty() ? "" : " service=",
|
|
completion.work.service.c_str(), gai, completion.result);
|
|
return completion;
|
|
}
|
|
|
|
// The host resolver's linked list and sockaddr pointers are owned by its
|
|
// allocator. Normalize everything to POD before leaving the worker.
|
|
const size_t maxAddresses = completion.work.kind == DeferredDnsKind::GetHostByName
|
|
? 71u
|
|
: (completion.work.kind == DeferredDnsKind::InetAton ? 1u : 256u);
|
|
completion.addresses.reserve(std::min<size_t>(maxAddresses, 16u));
|
|
for (addrinfo* it = nativeResults; it && completion.addresses.size() < maxAddresses; it = it->ai_next) {
|
|
if (auto normalized = NormalizeDeferredDnsAddress(*it)) {
|
|
completion.addresses.push_back(std::move(*normalized));
|
|
}
|
|
}
|
|
freeaddrinfo(nativeResults);
|
|
completion.result = completion.addresses.empty()
|
|
? DeferredDnsFailureResult(completion.work.kind)
|
|
: 0;
|
|
return completion;
|
|
}
|
|
|
|
static bool LaunchDeferredDns(DeferredDnsWork work) {
|
|
std::unique_ptr<std::thread> worker;
|
|
try {
|
|
worker = std::make_unique<std::thread>([work = std::move(work)]() mutable {
|
|
NetworkDeferredContract::RunWorker(
|
|
std::move(work),
|
|
[](const DeferredDnsWork& copiedWork) {
|
|
return ResolveDeferredDns(copiedWork);
|
|
},
|
|
[](DeferredDnsCompletion completion) {
|
|
QueueDeferredDnsCompletion(std::move(completion));
|
|
},
|
|
[](DeferredDnsWork failedWork, std::exception_ptr error) {
|
|
(void)error;
|
|
DeferredDnsCompletion failure{};
|
|
failure.work = std::move(failedWork);
|
|
failure.result = DeferredDnsFailureResult(failure.work.kind);
|
|
try {
|
|
QueueDeferredDnsCompletion(std::move(failure));
|
|
} catch (const std::exception& queueError) {
|
|
RT_LOGF(RT_TAG_NET,
|
|
"failed to queue deferred DNS failure: %s\n",
|
|
queueError.what());
|
|
} catch (...) {
|
|
RT_LOGF(RT_TAG_NET,
|
|
"failed to queue deferred DNS failure\n");
|
|
}
|
|
});
|
|
});
|
|
} catch (const std::exception&) {
|
|
return false;
|
|
}
|
|
|
|
(void)NetworkDeferredContract::DetachOrRelease(
|
|
std::move(worker), [](std::exception_ptr error) noexcept {
|
|
try {
|
|
if (error) {
|
|
std::rethrow_exception(error);
|
|
}
|
|
} catch (const std::exception& detachError) {
|
|
RT_LOGF(RT_TAG_NET,
|
|
"failed to detach deferred DNS worker; "
|
|
"retaining running worker: %s\n",
|
|
detachError.what());
|
|
return;
|
|
} catch (...) {
|
|
}
|
|
RT_LOGF(RT_TAG_NET,
|
|
"failed to detach deferred DNS worker; "
|
|
"retaining running worker\n");
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// Every deferred operation is an /dev/net/ip/top command; anything else must
|
|
// fall straight through to the direct path.
|
|
static bool IsIpTopDevice(uint32_t fd) {
|
|
const std::optional<DeviceKind> kind = GetDeviceKind(fd);
|
|
return kind && *kind == DeviceKind::IpTop;
|
|
}
|
|
|
|
static bool IsIpTopCommand(uint32_t fd, uint32_t cmd, uint32_t expectedCmd) {
|
|
return cmd == expectedCmd && IsIpTopDevice(fd);
|
|
}
|
|
|
|
template <typename Work>
|
|
static bool IsApplicable(const NetworkDeferredContract::Preparation<Work>& prepared) {
|
|
return prepared.State() != NetworkDeferredContract::PreparationState::NotApplicable;
|
|
}
|
|
|
|
static DeferredDnsPreparation PrepareDeferredGetHostByName(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf, uint32_t outLen) {
|
|
if (!IsIpTopCommand(fd, cmd, IOCTL_SO_GETHOSTBYNAME)) {
|
|
return DeferredDnsPreparation::NotApplicable();
|
|
}
|
|
if (!inBuf || !outBuf || outLen < 0x460 || !Memory::Contains(outBuf, outLen)) {
|
|
return DeferredDnsPreparation::Error(-1);
|
|
}
|
|
|
|
const uint32_t maxInput = inLen ? inLen : 256u;
|
|
if (!Memory::Contains(inBuf, maxInput)) {
|
|
return DeferredDnsPreparation::Error(-1);
|
|
}
|
|
|
|
DeferredDnsWork work{};
|
|
work.kind = DeferredDnsKind::GetHostByName;
|
|
work.node = ReadGuestString(inBuf, maxInput);
|
|
if (work.node.empty()) {
|
|
return DeferredDnsPreparation::Error(-1);
|
|
}
|
|
work.outAddress = outBuf;
|
|
work.outSize = outLen;
|
|
return DeferredDnsPreparation::Ready(std::move(work), -1);
|
|
}
|
|
|
|
static DeferredDnsPreparation PrepareDeferredInetAton(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf, uint32_t outLen) {
|
|
if (!IsIpTopCommand(fd, cmd, IOCTL_SO_INETATON)) {
|
|
return DeferredDnsPreparation::NotApplicable();
|
|
}
|
|
|
|
const uint32_t maxInput = inLen ? inLen : 256u;
|
|
if (!inBuf || !outBuf || outLen < 4u ||
|
|
!Memory::Contains(inBuf, maxInput) || !Memory::Contains(outBuf, 4u)) {
|
|
return DeferredDnsPreparation::Error(0);
|
|
}
|
|
|
|
DeferredDnsWork work{};
|
|
work.kind = DeferredDnsKind::InetAton;
|
|
work.node = ReadGuestString(inBuf, maxInput);
|
|
if (work.node.empty()) {
|
|
return DeferredDnsPreparation::Error(0);
|
|
}
|
|
in_addr numericAddress{};
|
|
if (inet_pton(AF_INET, work.node.c_str(), &numericAddress) == 1) {
|
|
// Numeric conversion cannot block; retain the existing immediate path.
|
|
return DeferredDnsPreparation::NotApplicable();
|
|
}
|
|
work.outAddress = outBuf;
|
|
work.outSize = 4u;
|
|
return DeferredDnsPreparation::Ready(std::move(work), 0);
|
|
}
|
|
|
|
static DeferredDnsPreparation PrepareDeferredScalarDns(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf, uint32_t outLen) {
|
|
auto getHostByName =
|
|
PrepareDeferredGetHostByName(fd, cmd, inBuf, inLen, outBuf, outLen);
|
|
if (IsApplicable(getHostByName)) {
|
|
return getHostByName;
|
|
}
|
|
return PrepareDeferredInetAton(fd, cmd, inBuf, inLen, outBuf, outLen);
|
|
}
|
|
|
|
static DeferredPollPreparation PrepareDeferredPoll(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf, uint32_t outLen) {
|
|
if (!IsIpTopCommand(fd, cmd, IOCTL_SO_POLL)) {
|
|
return DeferredPollPreparation::NotApplicable();
|
|
}
|
|
|
|
uint32_t descriptorCount = 0;
|
|
if (!ValidatePollRequest(inBuf, inLen, outBuf, outLen, descriptorCount)) {
|
|
return DeferredPollPreparation::Error(-SO_EINVAL);
|
|
}
|
|
|
|
const int64_t timeoutMilliseconds = static_cast<int64_t>(Memory::Read64(inBuf));
|
|
if (!NetworkPollContract::RequiresSchedulerWait(timeoutMilliseconds)) {
|
|
// A timeout-zero poll is guaranteed not to block and is cheaper to
|
|
// execute directly than to copy, sleep, wake, and copy back.
|
|
return DeferredPollPreparation::NotApplicable();
|
|
}
|
|
|
|
DeferredPollWork work{};
|
|
work.outAddress = outBuf;
|
|
work.outSize = outLen;
|
|
work.timeout = NetworkPollContract::Timeout::FromMilliseconds(timeoutMilliseconds);
|
|
work.descriptors = CopyPollDescriptors(outBuf, descriptorCount);
|
|
return DeferredPollPreparation::Ready(std::move(work), -SO_EINVAL);
|
|
}
|
|
|
|
static DeferredConnectPreparation PrepareDeferredConnect(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen) {
|
|
if (!IsIpTopCommand(fd, cmd, IOCTL_SO_CONNECT)) {
|
|
return DeferredConnectPreparation::NotApplicable();
|
|
}
|
|
if (!inBuf || inLen < 16u || !Memory::Contains(inBuf, 16u)) {
|
|
return DeferredConnectPreparation::Error(-SO_EINVAL);
|
|
}
|
|
|
|
const uint32_t wiiFd = Memory::Read32(inBuf);
|
|
WiiSocket* socket = GetWiiSocket(wiiFd);
|
|
if (!socket) {
|
|
return DeferredConnectPreparation::Error(-SO_EBADF);
|
|
}
|
|
if (socket->nonblocking) {
|
|
// The direct path is already a zero-wait host connect and returns
|
|
// SO_EINPROGRESS to the guest as required.
|
|
return DeferredConnectPreparation::NotApplicable();
|
|
}
|
|
|
|
DeferredConnectWork work{};
|
|
work.wiiFd = wiiFd;
|
|
work.nativeFd = socket->native;
|
|
work.socketGeneration = socket->generation;
|
|
work.peerAddress = ReadWiiSockAddr(inBuf + 8u);
|
|
work.timeout = NetworkPollContract::Timeout::FromMilliseconds(
|
|
NetworkConnectContract::kGuestBlockingTimeoutMilliseconds);
|
|
return DeferredConnectPreparation::Ready(std::move(work), -SO_EINVAL);
|
|
}
|
|
|
|
static DeferredDnsPreparation PrepareDeferredGetAddrInfo(
|
|
uint32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut, uint32_t vectorPtr) {
|
|
if (!IsIpTopCommand(fd, cmd, IOCTLV_SO_GETADDRINFO)) {
|
|
return DeferredDnsPreparation::NotApplicable();
|
|
}
|
|
const uint64_t vectorCount = static_cast<uint64_t>(numIn) + numOut;
|
|
if (vectorCount == 0 || vectorCount > UINT32_MAX || !vectorPtr ||
|
|
!Memory::Contains(vectorPtr, static_cast<size_t>(vectorCount) * 8u)) {
|
|
return DeferredDnsPreparation::Error(-SO_EINVAL);
|
|
}
|
|
|
|
const std::vector<IoVector> vectors = ReadVectors(vectorPtr, static_cast<uint32_t>(vectorCount));
|
|
const std::vector<IoVector> in(vectors.begin(), vectors.begin() + numIn);
|
|
const std::vector<IoVector> out(vectors.begin() + numIn, vectors.end());
|
|
if (out.empty() || !out[0].address || !out[0].size ||
|
|
!Memory::Contains(out[0].address, out[0].size)) {
|
|
return DeferredDnsPreparation::Error(-SO_EINVAL);
|
|
}
|
|
for (const IoVector& vector : vectors) {
|
|
if ((vector.address == 0) != (vector.size == 0) ||
|
|
(vector.address && !Memory::Contains(vector.address, vector.size))) {
|
|
return DeferredDnsPreparation::Error(-SO_EINVAL);
|
|
}
|
|
}
|
|
|
|
DeferredDnsWork work{};
|
|
work.kind = DeferredDnsKind::GetAddrInfo;
|
|
work.outAddress = out[0].address;
|
|
work.outSize = out[0].size;
|
|
if (!in.empty() && in[0].address && in[0].size) {
|
|
if (!Memory::Contains(in[0].address, in[0].size)) {
|
|
return DeferredDnsPreparation::Error(-SO_EINVAL);
|
|
}
|
|
work.node = ReadGuestString(in[0].address, in[0].size);
|
|
}
|
|
if (in.size() > 1 && in[1].address && in[1].size) {
|
|
if (!Memory::Contains(in[1].address, in[1].size)) {
|
|
return DeferredDnsPreparation::Error(-SO_EINVAL);
|
|
}
|
|
work.service = ReadGuestString(in[1].address, in[1].size);
|
|
}
|
|
if (in.size() > 2 && in[2].address) {
|
|
if (in[2].size < 0x10 || !Memory::Contains(in[2].address, 0x10)) {
|
|
return DeferredDnsPreparation::Error(-SO_EINVAL);
|
|
}
|
|
work.hasHints = true;
|
|
work.hintFlags = static_cast<int>(Memory::Read32(in[2].address));
|
|
work.hintFamily = MapWiiAddrInfoFamily(Memory::Read32(in[2].address + 4));
|
|
work.hintSockType = MapWiiAddrInfoSocketType(Memory::Read32(in[2].address + 8));
|
|
work.hintProtocol = static_cast<int>(Memory::Read32(in[2].address + 0x0C));
|
|
}
|
|
if (work.node.empty() && work.service.empty()) {
|
|
return DeferredDnsPreparation::Error(SO_ERROR_HOST_NOT_FOUND);
|
|
}
|
|
return DeferredDnsPreparation::Ready(
|
|
std::move(work), SO_ERROR_HOST_NOT_FOUND);
|
|
}
|
|
|
|
static int32_t ApplyDeferredDnsCompletion(const DeferredDnsCompletion& completion) {
|
|
if (completion.result != 0) {
|
|
return completion.result;
|
|
}
|
|
|
|
const uint32_t outAddress = completion.work.outAddress;
|
|
const uint32_t outSize = completion.work.outSize;
|
|
if (!outAddress || !outSize || !Memory::Contains(outAddress, outSize)) {
|
|
return -SO_EINVAL;
|
|
}
|
|
|
|
if (completion.work.kind == DeferredDnsKind::InetAton) {
|
|
if (completion.addresses.empty()) {
|
|
return 0;
|
|
}
|
|
Memory::Write32(outAddress, completion.addresses.front().ipv4Address);
|
|
return 1;
|
|
}
|
|
|
|
ZeroMemoryRange(outAddress, outSize);
|
|
if (completion.work.kind == DeferredDnsKind::GetHostByName) {
|
|
constexpr uint32_t kStructSize = 0x10;
|
|
constexpr uint32_t kIpListOffset = 0x110;
|
|
constexpr uint32_t kIpPtrListOffset = 0x340;
|
|
const std::string canonical = completion.work.node.size() < 240
|
|
? completion.work.node
|
|
: completion.work.node.substr(0, 239);
|
|
for (uint32_t i = 0; i <= canonical.size(); ++i) {
|
|
Memory::Write8(outAddress + kStructSize + i,
|
|
i < canonical.size() ? static_cast<uint8_t>(canonical[i]) : 0);
|
|
}
|
|
Memory::Write32(outAddress, outAddress + kStructSize);
|
|
Memory::Write16(outAddress + 8, kWiiAfInet);
|
|
Memory::Write16(outAddress + 10, 4);
|
|
Memory::Write32(outAddress + 12, outAddress + kIpPtrListOffset);
|
|
|
|
const uint32_t count = static_cast<uint32_t>(std::min<size_t>(completion.addresses.size(), 71u));
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
Memory::Write32(outAddress + kIpListOffset + i * 4,
|
|
completion.addresses[i].ipv4Address);
|
|
Memory::Write32(outAddress + kIpPtrListOffset + i * 4,
|
|
outAddress + kIpListOffset + i * 4);
|
|
}
|
|
Memory::Write32(outAddress + kIpPtrListOffset + count * 4, 0);
|
|
// Hardware returns an empty aliases list. Point h_aliases at the same
|
|
// terminator that ends h_addr_list, matching IOS/Dolphin exactly.
|
|
Memory::Write32(outAddress + 4, outAddress + kIpPtrListOffset + count * 4);
|
|
return 0;
|
|
}
|
|
|
|
constexpr uint32_t kInfoSize = 0x20;
|
|
constexpr uint32_t kSockAddrOffset = 0x460;
|
|
constexpr uint32_t kSockAddrStride = 0x1C;
|
|
const uint64_t end = static_cast<uint64_t>(outAddress) + outSize;
|
|
const uint64_t infoEnd = std::min<uint64_t>(end, static_cast<uint64_t>(outAddress) + kSockAddrOffset);
|
|
uint32_t infoAddress = outAddress;
|
|
uint32_t socketAddress = outAddress + kSockAddrOffset;
|
|
size_t writeCount = 0;
|
|
while (writeCount < completion.addresses.size() &&
|
|
static_cast<uint64_t>(infoAddress) + kInfoSize <= infoEnd &&
|
|
static_cast<uint64_t>(socketAddress) + kSockAddrStride <= end) {
|
|
++writeCount;
|
|
infoAddress += kInfoSize;
|
|
socketAddress += kSockAddrStride;
|
|
}
|
|
|
|
infoAddress = outAddress;
|
|
socketAddress = outAddress + kSockAddrOffset;
|
|
for (size_t i = 0; i < writeCount; ++i) {
|
|
const DeferredDnsAddress& address = completion.addresses[i];
|
|
if (address.family != kWiiAfInet ||
|
|
address.addressLength != NetworkDeferredContract::kWiiIpv4SockAddrBytes ||
|
|
!NetworkDeferredContract::AdvertisedSockAddrFits(address.addressLength)) {
|
|
return -SO_EINVAL;
|
|
}
|
|
Memory::Write32(infoAddress, static_cast<uint32_t>(address.flags));
|
|
Memory::Write32(infoAddress + 0x04, address.family);
|
|
Memory::Write32(infoAddress + 0x08, MapNativeSocketTypeToWii(address.sockType));
|
|
Memory::Write32(infoAddress + 0x0C, static_cast<uint32_t>(address.protocol));
|
|
Memory::Write32(infoAddress + 0x10, address.addressLength);
|
|
Memory::Write32(infoAddress + 0x14, 0);
|
|
Memory::Write32(infoAddress + 0x18, socketAddress);
|
|
Memory::Write8(socketAddress, static_cast<uint8_t>(address.addressLength & 0xFFu));
|
|
Memory::Write8(socketAddress + 1, static_cast<uint8_t>(address.family & 0xFFu));
|
|
CopyToGuest(socketAddress + 2, address.socketData.data(),
|
|
static_cast<uint32_t>(address.socketData.size()));
|
|
Memory::Write32(infoAddress + 0x1C,
|
|
i + 1 < writeCount ? infoAddress + kInfoSize : 0);
|
|
infoAddress += kInfoSize;
|
|
socketAddress += kSockAddrStride;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
} // namespace NetworkHle
|
|
|
|
using namespace NetworkHle;
|
|
|
|
static bool InitializeDeferredSyncRoute(DeferredNetworkRoute& route, uint32_t waitQueue) {
|
|
if (!waitQueue) {
|
|
return false;
|
|
}
|
|
NoteDeferredNetworkSchedulerThread("submission");
|
|
DeferredNetworkStore& store = GetDeferredNetworkStore();
|
|
uint64_t nextToken = store.nextToken.fetch_add(1, std::memory_order_relaxed);
|
|
if (nextToken == 0) {
|
|
nextToken = store.nextToken.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
// OSSleepThread uses the running OSThread, or the SDK default thread during
|
|
// early scheduler setup. Capture its identity before yielding so a late
|
|
// resolver result cannot wake a stale/reused stack queue after cancellation.
|
|
constexpr uint32_t kOSRunningContextAddr = 0x800000E4u;
|
|
const uint32_t kDefaultThreadContextAddr = MKW_GADDR(80347498);
|
|
uint32_t expectedThread = Memory::Read32(kOSRunningContextAddr);
|
|
if (expectedThread == 0) {
|
|
expectedThread = kDefaultThreadContextAddr;
|
|
}
|
|
if (!Memory::Contains(expectedThread + 0x2C8u, 0x20u) ||
|
|
!Memory::Contains(waitQueue, 16u)) {
|
|
return false;
|
|
}
|
|
route.kind = DeferredNetworkCompletionKind::SyncWaitQueue;
|
|
route.token = nextToken;
|
|
route.waitQueue = waitQueue;
|
|
route.expectedThread = expectedThread;
|
|
// ABA cookie: thread/stack addresses are reused after cancellation.
|
|
Memory::Write64(waitQueue + 8u, nextToken);
|
|
return true;
|
|
}
|
|
|
|
static void InitializeDeferredAsyncRoute(DeferredNetworkRoute& route, uint32_t callback,
|
|
uint32_t callbackArg) {
|
|
NoteDeferredNetworkSchedulerThread("submission");
|
|
route.kind = DeferredNetworkCompletionKind::AsyncCallback;
|
|
route.callback = callback;
|
|
route.callbackArg = callbackArg;
|
|
}
|
|
|
|
using DeferredStartOutcome = NetworkDeferredContract::StartOutcome;
|
|
|
|
// Install a prepared operation on either completion route. The sync/async delta
|
|
// is entirely in `initializeRoute`: the sync form allocates the token that
|
|
// Network_HLE_TakeSyncResult later redeems, the async form leaves route.token at
|
|
// zero, which StartPrepared documents as valid for a callback route.
|
|
template <typename Work, typename InitializeRoute, typename Install>
|
|
static DeferredStartOutcome StartDeferred(NetworkDeferredContract::Preparation<Work> prepared,
|
|
InitializeRoute&& initializeRoute, Install&& install) {
|
|
return NetworkDeferredContract::StartPrepared(
|
|
std::move(prepared),
|
|
[&initializeRoute, &install](Work work) -> std::optional<uint64_t> {
|
|
if (!EnsureSocketRuntime() || !initializeRoute(work.route)) {
|
|
return std::nullopt;
|
|
}
|
|
const uint64_t token = work.route.token;
|
|
if (!install(std::move(work))) {
|
|
return std::nullopt;
|
|
}
|
|
return token;
|
|
});
|
|
}
|
|
|
|
static bool DeferredConnectSocketIsStillValid(const DeferredConnectWork& work) {
|
|
return SocketIdentityIsCurrent(work.wiiFd, work.nativeFd, work.socketGeneration);
|
|
}
|
|
|
|
static bool InstallDeferredDns(DeferredDnsWork work) {
|
|
return LaunchDeferredDns(std::move(work));
|
|
}
|
|
|
|
static bool InstallDeferredPoll(DeferredPollWork work) {
|
|
GetDeferredNetworkStore().pendingPolls.push_back(std::move(work));
|
|
return true;
|
|
}
|
|
|
|
// A blocking connect issues the host connect() at submission time, because the
|
|
// SDK's own retry loop depends on seeing the immediate outcome; only a genuinely
|
|
// in-flight connect is parked for the completion pump to probe.
|
|
static bool InstallDeferredConnect(DeferredConnectWork work) {
|
|
if (!DeferredConnectSocketIsStillValid(work)) {
|
|
work.initialResult = -SO_EBADF;
|
|
} else {
|
|
const int ret = connect(work.nativeFd, reinterpret_cast<sockaddr*>(&work.peerAddress),
|
|
sizeof(work.peerAddress));
|
|
if (ret == 0) {
|
|
work.initialResult = 0;
|
|
} else {
|
|
const int nativeError = NormalizeConnectError(NativeLastError());
|
|
if (!IsWouldBlockError(nativeError)) {
|
|
const int32_t translated = TranslateSocketError(nativeError, false);
|
|
// Dolphin rewrites -SO_EISCONN to SO_SUCCESS here (IOS/Network/
|
|
// Socket.cpp:356-359): the SDK detects a completed blocking
|
|
// connect by re-issuing connect() on it, so reporting EISCONN
|
|
// as an error aborted session setup.
|
|
work.initialResult = translated == -SO_EISCONN ? 0 : translated;
|
|
}
|
|
}
|
|
}
|
|
GetDeferredNetworkStore().pendingConnects.push_back(std::move(work));
|
|
return true;
|
|
}
|
|
|
|
static auto SyncRoute(uint32_t waitQueue) {
|
|
return [waitQueue](DeferredNetworkRoute& route) {
|
|
return InitializeDeferredSyncRoute(route, waitQueue);
|
|
};
|
|
}
|
|
|
|
static auto AsyncRoute(uint32_t callback, uint32_t callbackArg) {
|
|
return [callback, callbackArg](DeferredNetworkRoute& route) {
|
|
InitializeDeferredAsyncRoute(route, callback, callbackArg);
|
|
return true;
|
|
};
|
|
}
|
|
|
|
static std::optional<int32_t> ScalarDeferredFailureResult(uint32_t fd, uint32_t cmd) {
|
|
if (!IsIpTopDevice(fd)) {
|
|
return std::nullopt;
|
|
}
|
|
switch (cmd) {
|
|
case IOCTL_SO_CONNECT:
|
|
return -SO_EINVAL;
|
|
case IOCTL_SO_POLL:
|
|
return -SO_EINVAL;
|
|
case IOCTL_SO_GETHOSTBYNAME:
|
|
return -1;
|
|
case IOCTL_SO_INETATON:
|
|
return 0;
|
|
default:
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
// Guest-input marshalling faults must never escape into the IOS bridge. Note
|
|
// that the two failure results are NOT the same for the ioctlv entry points: a
|
|
// faulted descriptor array is a malformed request (-SO_EINVAL) while any other
|
|
// failure is reported to the resolver's caller as a lookup miss.
|
|
template <typename Body>
|
|
static DeferredStartOutcome RunDeferredEntry(int32_t accessViolationResult,
|
|
int32_t exceptionResult, Body&& body) {
|
|
try {
|
|
return body();
|
|
} catch (const Memory::AccessViolation&) {
|
|
return DeferredStartOutcome::Immediate(accessViolationResult);
|
|
} catch (const std::bad_alloc&) {
|
|
return DeferredStartOutcome::Immediate(-SO_ENOMEM);
|
|
} catch (const std::exception&) {
|
|
return DeferredStartOutcome::Immediate(exceptionResult);
|
|
}
|
|
}
|
|
|
|
// SO_CONNECT, SO_POLL and the scalar DNS commands all arrive through ioctl and
|
|
// are distinguished by which Prepare* claims them.
|
|
template <typename InitializeRoute>
|
|
static DeferredStartOutcome StartScalarDeferredIoctl(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen, uint32_t outBuf, uint32_t outLen,
|
|
InitializeRoute initializeRoute) {
|
|
auto connect = PrepareDeferredConnect(fd, cmd, inBuf, inLen);
|
|
if (IsApplicable(connect)) {
|
|
return StartDeferred(std::move(connect), initializeRoute, InstallDeferredConnect);
|
|
}
|
|
auto poll = PrepareDeferredPoll(fd, cmd, inBuf, inLen, outBuf, outLen);
|
|
if (IsApplicable(poll)) {
|
|
return StartDeferred(std::move(poll), initializeRoute, InstallDeferredPoll);
|
|
}
|
|
return StartDeferred(PrepareDeferredScalarDns(fd, cmd, inBuf, inLen, outBuf, outLen),
|
|
initializeRoute, InstallDeferredDns);
|
|
}
|
|
|
|
NetworkDeferredContract::StartOutcome Network_HLE_StartIoctlSync(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen,
|
|
uint32_t outBuf, uint32_t outLen, uint32_t waitQueue) {
|
|
const std::optional<int32_t> failureResult = ScalarDeferredFailureResult(fd, cmd);
|
|
if (!failureResult) {
|
|
return DeferredStartOutcome::NotApplicable();
|
|
}
|
|
return RunDeferredEntry(*failureResult, *failureResult, [&] {
|
|
return StartScalarDeferredIoctl(fd, cmd, inBuf, inLen, outBuf, outLen,
|
|
SyncRoute(waitQueue));
|
|
});
|
|
}
|
|
|
|
NetworkDeferredContract::StartOutcome Network_HLE_StartIoctlvSync(
|
|
uint32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut,
|
|
uint32_t vectorPtr, uint32_t waitQueue) {
|
|
if (!IsIpTopCommand(fd, cmd, IOCTLV_SO_GETADDRINFO)) {
|
|
return DeferredStartOutcome::NotApplicable();
|
|
}
|
|
return RunDeferredEntry(-SO_EINVAL, SO_ERROR_HOST_NOT_FOUND, [&] {
|
|
return StartDeferred(PrepareDeferredGetAddrInfo(fd, cmd, numIn, numOut, vectorPtr),
|
|
SyncRoute(waitQueue), InstallDeferredDns);
|
|
});
|
|
}
|
|
|
|
NetworkDeferredContract::StartOutcome Network_HLE_StartIoctlAsync(
|
|
uint32_t fd, uint32_t cmd, uint32_t inBuf, uint32_t inLen,
|
|
uint32_t outBuf, uint32_t outLen, uint32_t callback, uint32_t callbackArg) {
|
|
const std::optional<int32_t> failureResult = ScalarDeferredFailureResult(fd, cmd);
|
|
if (!failureResult) {
|
|
return DeferredStartOutcome::NotApplicable();
|
|
}
|
|
return RunDeferredEntry(*failureResult, *failureResult, [&] {
|
|
return StartScalarDeferredIoctl(fd, cmd, inBuf, inLen, outBuf, outLen,
|
|
AsyncRoute(callback, callbackArg));
|
|
});
|
|
}
|
|
|
|
NetworkDeferredContract::StartOutcome Network_HLE_StartIoctlvAsync(
|
|
uint32_t fd, uint32_t cmd, uint32_t numIn, uint32_t numOut,
|
|
uint32_t vectorPtr, uint32_t callback, uint32_t callbackArg) {
|
|
if (!IsIpTopCommand(fd, cmd, IOCTLV_SO_GETADDRINFO)) {
|
|
return DeferredStartOutcome::NotApplicable();
|
|
}
|
|
return RunDeferredEntry(-SO_EINVAL, SO_ERROR_HOST_NOT_FOUND, [&] {
|
|
return StartDeferred(PrepareDeferredGetAddrInfo(fd, cmd, numIn, numOut, vectorPtr),
|
|
AsyncRoute(callback, callbackArg), InstallDeferredDns);
|
|
});
|
|
}
|
|
|
|
bool Network_HLE_TakeSyncResult(uint64_t token, int32_t* result) {
|
|
if (!token || !result) {
|
|
return false;
|
|
}
|
|
NoteDeferredNetworkSchedulerThread("result retrieval");
|
|
DeferredNetworkStore& store = GetDeferredNetworkStore();
|
|
std::lock_guard<std::mutex> lock(store.resultMutex);
|
|
const auto it = store.syncResults.find(token);
|
|
if (it == store.syncResults.end()) {
|
|
return false;
|
|
}
|
|
*result = it->second;
|
|
store.syncResults.erase(it);
|
|
return true;
|
|
}
|
|
|
|
static bool DeferredNetworkWaiterIsValid(const DeferredNetworkRoute& route) {
|
|
if (!route.waitQueue || !route.expectedThread ||
|
|
!Memory::Contains(route.waitQueue, 16u) ||
|
|
!Memory::Contains(route.expectedThread + 0x2C8u, 0x20u) ||
|
|
Fiber::GuestFiberManager::IsTerminated(route.expectedThread)) {
|
|
return false;
|
|
}
|
|
|
|
constexpr uint16_t kThreadStateWaiting = 4;
|
|
constexpr uint32_t kThreadStateOffset = 0x2C8u;
|
|
constexpr uint32_t kThreadQueueOffset = 0x2DCu;
|
|
constexpr uint32_t kThreadNextOffset = 0x2E0u;
|
|
constexpr uint32_t kThreadPrevOffset = 0x2E4u;
|
|
try {
|
|
return Memory::Read16(route.expectedThread + kThreadStateOffset) == kThreadStateWaiting &&
|
|
Memory::Read32(route.expectedThread + kThreadQueueOffset) == route.waitQueue &&
|
|
Memory::Read32(route.waitQueue) == route.expectedThread &&
|
|
Memory::Read32(route.waitQueue + 4u) == route.expectedThread &&
|
|
Memory::Read64(route.waitQueue + 8u) == route.token &&
|
|
Memory::Read32(route.expectedThread + kThreadNextOffset) == 0 &&
|
|
Memory::Read32(route.expectedThread + kThreadPrevOffset) == 0;
|
|
} catch (const Memory::AccessViolation&) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static void DeliverDeferredNetworkResult(CpuContext* cpu, DeferredNetworkStore& store,
|
|
const DeferredNetworkRoute& route, int32_t result) {
|
|
if (route.kind == DeferredNetworkCompletionKind::SyncWaitQueue) {
|
|
{
|
|
std::lock_guard<std::mutex> lock(store.resultMutex);
|
|
const auto [it, inserted] = store.syncResults.emplace(route.token, result);
|
|
if (!inserted) {
|
|
RT_LOGF(RT_TAG_NET,
|
|
"duplicate deferred network token %" PRIu64 "\n",
|
|
route.token);
|
|
it->second = result;
|
|
}
|
|
}
|
|
// Output and result are committed before the waiter becomes READY.
|
|
OS_HLE_WakeupThreadNoReschedule(cpu, route.waitQueue);
|
|
} else {
|
|
// NandProcessPendingCallbacks runs immediately after this pump, on
|
|
// this same emulation/scheduler host thread.
|
|
NandQueueIosCallback(route.callback, result, route.callbackArg);
|
|
}
|
|
}
|
|
|
|
static std::optional<int32_t> ProbeDeferredPoll(
|
|
DeferredPollWork& work, NetworkPollContract::Timeout::TimePoint now) {
|
|
const int nativeResult = NetworkPollContract::ProbeNow(
|
|
work.descriptors, NetworkHle::CopiedPollSocketIsStillValid);
|
|
const int32_t result = nativeResult < 0 ? SocketResult(nativeResult, false) : nativeResult;
|
|
if (work.timeout.ShouldRemainPending(nativeResult, now)) {
|
|
return std::nullopt;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static int32_t ApplyDeferredPollCompletion(const DeferredPollWork& work, int32_t result) {
|
|
const size_t outputBytes = work.descriptors.size() * 12u;
|
|
if (!work.outAddress || outputBytes == 0 || outputBytes > work.outSize ||
|
|
!Memory::Contains(work.outAddress, outputBytes)) {
|
|
return -SO_EINVAL;
|
|
}
|
|
WritePollResults(work.outAddress, work.descriptors);
|
|
return result;
|
|
}
|
|
|
|
static std::optional<int32_t> ProbeDeferredConnect(
|
|
DeferredConnectWork& work, NetworkPollContract::Timeout::TimePoint now) {
|
|
if (work.initialResult) {
|
|
return *work.initialResult;
|
|
}
|
|
|
|
const bool identityIsCurrent = DeferredConnectSocketIsStillValid(work);
|
|
int pollResult = 0;
|
|
if (identityIsCurrent) {
|
|
pollResult = ProbeConnectSettled(work.nativeFd, 0);
|
|
}
|
|
|
|
using NetworkConnectContract::ProbeDisposition;
|
|
switch (NetworkConnectContract::ClassifyProbe(
|
|
identityIsCurrent, pollResult, work.timeout.IsExpired(now))) {
|
|
case ProbeDisposition::StaleSocket:
|
|
return -SO_EBADF;
|
|
case ProbeDisposition::PollError:
|
|
return TranslateSocketError(NativeLastError(), false);
|
|
case ProbeDisposition::TimedOut:
|
|
// Dolphin's blocking-connect timeout reports -SO_ENETUNREACH
|
|
// (IOS/Network/Socket.cpp:352-356). -SO_ETIMEDOUT is not reachable
|
|
// through its error table at all, so the SDK has no handler for it.
|
|
return -SO_ENETUNREACH;
|
|
case ProbeDisposition::Pending:
|
|
return std::nullopt;
|
|
case ProbeDisposition::SocketReady:
|
|
break;
|
|
}
|
|
|
|
return ClassifySettledConnect(work.nativeFd);
|
|
}
|
|
|
|
bool Network_HLE_ProcessCompletions(CpuContext* cpu) {
|
|
if (!cpu) {
|
|
return false;
|
|
}
|
|
NoteDeferredNetworkSchedulerThread("completion pump");
|
|
|
|
std::deque<DeferredDnsCompletion> ready;
|
|
DeferredNetworkStore& store = GetDeferredNetworkStore();
|
|
{
|
|
std::lock_guard<std::mutex> lock(store.completedMutex);
|
|
ready.swap(store.completed);
|
|
}
|
|
bool handledAny = false;
|
|
|
|
while (!ready.empty()) {
|
|
DeferredDnsCompletion completion = std::move(ready.front());
|
|
ready.pop_front();
|
|
|
|
const DeferredNetworkRoute& route = completion.work.route;
|
|
if (route.kind == DeferredNetworkCompletionKind::SyncWaitQueue &&
|
|
!DeferredNetworkWaiterIsValid(route)) {
|
|
continue;
|
|
}
|
|
|
|
int32_t result = completion.result;
|
|
if (result == 0) {
|
|
try {
|
|
result = ApplyDeferredDnsCompletion(completion);
|
|
} catch (const Memory::AccessViolation& error) {
|
|
const std::string_view reason = error.reason();
|
|
NetFail("dns %s '%s' result write faulted: %.*s -> wii=%d",
|
|
DeferredDnsKindName(completion.work.kind), completion.work.node.c_str(),
|
|
static_cast<int>(reason.size()), reason.data(), -SO_EINVAL);
|
|
result = -SO_EINVAL;
|
|
}
|
|
}
|
|
DeliverDeferredNetworkResult(cpu, store, route, result);
|
|
handledAny = true;
|
|
}
|
|
|
|
const auto now = NetworkPollContract::Timeout::Clock::now();
|
|
for (auto it = store.pendingPolls.begin(); it != store.pendingPolls.end();) {
|
|
DeferredPollWork& work = *it;
|
|
const DeferredNetworkRoute route = work.route;
|
|
if (route.kind == DeferredNetworkCompletionKind::SyncWaitQueue &&
|
|
!DeferredNetworkWaiterIsValid(route)) {
|
|
it = store.pendingPolls.erase(it);
|
|
continue;
|
|
}
|
|
|
|
const std::optional<int32_t> probedResult = ProbeDeferredPoll(work, now);
|
|
if (!probedResult) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
int32_t result = *probedResult;
|
|
const size_t descriptorCount = work.descriptors.size();
|
|
try {
|
|
result = ApplyDeferredPollCompletion(work, result);
|
|
} catch (const Memory::AccessViolation& error) {
|
|
const std::string_view reason = error.reason();
|
|
NetFail("SO_POLL(wait) result write faulted for %llu descriptor(s): %.*s -> wii=%d",
|
|
static_cast<unsigned long long>(descriptorCount),
|
|
static_cast<int>(reason.size()), reason.data(), -SO_EINVAL);
|
|
result = -SO_EINVAL;
|
|
}
|
|
it = store.pendingPolls.erase(it);
|
|
DeliverDeferredNetworkResult(cpu, store, route, result);
|
|
handledAny = true;
|
|
}
|
|
|
|
for (auto it = store.pendingConnects.begin();
|
|
it != store.pendingConnects.end();) {
|
|
DeferredConnectWork& work = *it;
|
|
const DeferredNetworkRoute route = work.route;
|
|
if (route.kind == DeferredNetworkCompletionKind::SyncWaitQueue &&
|
|
!DeferredNetworkWaiterIsValid(route)) {
|
|
it = store.pendingConnects.erase(it);
|
|
continue;
|
|
}
|
|
|
|
const std::optional<int32_t> probedResult =
|
|
ProbeDeferredConnect(work, now);
|
|
if (!probedResult) {
|
|
++it;
|
|
continue;
|
|
}
|
|
|
|
int32_t result = *probedResult;
|
|
if (result == 0) {
|
|
if (DeferredConnectSocketIsStillValid(work)) {
|
|
WiiSocket& socket = g_sockets[work.wiiFd];
|
|
socket.peerPort = ntohs(work.peerAddress.sin_port);
|
|
socket.peerAddr = work.peerAddress;
|
|
socket.hasPeerAddr = true;
|
|
} else {
|
|
result = -SO_EBADF;
|
|
}
|
|
}
|
|
g_lastSocketError = result;
|
|
// Every blocking connect settles here exactly once, whether it was
|
|
// resolved at submission, probed, timed out or aborted by a close - so
|
|
// this is the single place a failed WFC connect becomes visible.
|
|
if (result != 0) {
|
|
char peer[INET_ADDRSTRLEN]{};
|
|
inet_ntop(AF_INET, &work.peerAddress.sin_addr, peer, sizeof(peer));
|
|
NetFail("SO_CONNECT fd=%u -> %s:%u FAILED wii=%d", work.wiiFd,
|
|
peer[0] ? peer : "0.0.0.0",
|
|
static_cast<unsigned>(ntohs(work.peerAddress.sin_port)), result);
|
|
}
|
|
it = store.pendingConnects.erase(it);
|
|
DeliverDeferredNetworkResult(cpu, store, route, result);
|
|
handledAny = true;
|
|
}
|
|
return handledAny;
|
|
}
|