Skip to content

Online sockets for gRPC games: RecvMsg/SendMsg, C resolver, game errno, EventFd in a network mode - #2589

Open
eyasbzour wants to merge 51 commits into
seedds:mainfrom
eyasbzour:socket-grpc-resolver-msg
Open

eyasbzour wants to merge 51 commits into
seedds:mainfrom
eyasbzour:socket-grpc-resolver-msg

Conversation

@eyasbzour

@eyasbzour eyasbzour commented Nov 13, 2026 •

Copy link
Copy Markdown

This builds on #961 (RobiiAragon's opt-in online backend), which is still open, so until #961 merges the diff shows its roughly 10k lines too. The commits that are new here:

  • b8e25957 Serve gRPC's name lookup and RecvMsg/SendMsg in a network mode
  • aa03bca5 Set the game's own errno on a failed socket call; RecvMsg/SendMsg edge cases
  • 8aace781 Merge seedds/main (465 commits; notes below)
  • 51c46ece Regenerated docs/agents/symptoms.md
  • f1afbb84 EventFd in a network mode is a slot of the transport's socket table
  • 8a36c156, e3fa880a Merge seedds/main again (no conflicts)
  • e6d91b7f The BCAT title IDs come from the build instead of the host source (test_no_title_ids_in_the_host), and three tests define the clock and wait symbols that main's hle_os.c and hle_thread.c now provide
  • ae3a5f02 iOS online: the game is told the address of the adapter the internet route uses, not the first one listed
  • fb1d0b3e, b8bec5a9, cb5bdacd Tests that broke after merging main: t_oe_handoff stubs what hle_oe.c now calls, t_nextendo_account lets the SDK build the AsyncContext, and t_save_sync compares the device with hw.model (a CI runner is a VirtualMac2,1)

Why

Games whose online code is NPLN (Super Mario Bros. Wonder and other recent titles) talk to their servers over gRPC, which links its own OpenSSL and does TLS itself over the C socket interface. With #961 alone, Wonder's online menu fails with 2321-4992 (gRPC's UNAVAILABLE) at four separate points. Each fix below sits at the SDK level, with no game offsets in the code, so it applies to any game that makes the same calls.

  1. nnsocketGetAddrInfo returned EAI_FAIL whenever an SDK exports both C++ GetAddrInfo overloads, and Wonder's does. The C call takes the plain addrinfo layout. In Wonder 1.2.1's SDK it branches straight to that overload (sdk+0x1481c0), so the C resolver now uses that layout whenever the SDK has it.

  2. gRPC's TCP endpoint reads and writes only through RecvMsg and SendMsg, which were unsupported. They now scatter and gather through one host buffer with Horizon's 0x30-byte msghdr. A message over 16 MiB moves in part on a stream socket, as a real socket may, and fails with EMSGSIZE on a datagram one. A datagram longer than the iovecs is cut and reported with MSG_TRUNC. Ancillary data is refused rather than dropped.

  3. On Horizon, nn::socket's errno is the C library's: detail::SetLastErrno writes *__errno_location(). The host kept only its own copy. gRPC sets EAFNOSUPPORT itself when it falls back from IPv6, then reads errno after a non-blocking connect, so it took that stale value for EINPROGRESS and dropped the address. Every socket hook that fails now also writes the game's errno, and GetLastErrno reads it.

  4. gRPC wakes its pollers with an event descriptor and polls it together with its sockets. In a network mode EventFd failed, and main's offline table numbered its descriptors from 0, the same as the transport's sockets. An event descriptor is now a slot in the transport's socket table, backed by a local socket pair, so Poll and Select see sockets and event descriptors together. Offline nothing changes.

  5. On iOS, net_route has no routing table to read, so the adapter nifm reports was the first IPv4 one getifaddrs lists. With mobile data on, that is usually the cellular one, even while the phone plays over Wi-Fi. Pia gives the address nifm reports to the other players as its local candidate for a direct link. In a Nextendo session the adapter is now the one whose address a UDP socket connected past the local networks gets (connect on UDP sends nothing). If no adapter has that address, any adapter is used, as before. The chosen adapter's name is logged when it changes.

Name lookups and resolver options are logged once, in red when they fail, so the next game that stops before its first socket shows why.

Merging main

Main's offline socket layer (the event descriptor table, Select as Poll over its sets, Star Fox's SDK 21 overloads) now sits under #961's transport, and the SDK 21 overloads go to the transport in a network mode. Main's ssl stubs stay out because #961's hle_ssl.c serves those calls. The SDK 4 GetPeerName is registered once. hle_account.c keeps main's SDK-built AsyncContext: there is no constructor takeover, a context with Nextendo off answers as main's does, and a move constructor copies the vtable. hle_bcat.c drops HLE_FORCE_BIG, since main now runs every takeover on the host stack.

Tested

  • Super Mario Bros. Wonder 1.2.1 on an iPhone over Nextendo, in a fork build with these changes plus Propose opt-in online service backends and BCAT delivery #961: sign-in, the online menu, creating a room and searching for rooms all work, and Nextendo's site counts the player. Two players joining one room (Pia's peer-to-peer link) has not been tried yet.
  • t_net covers the gathered and scattered round trip, EMSGSIZE, MSG_TRUNC, the resolver layout, the game's errno on a failed call and on an empty event read, and mixed Poll and Select over sockets and event descriptors. The tests workflow on the fork: https://github.com/eyasbzour/nxnative/actions/runs/43498950204

Not in this PR: the Nextendo sign-in for the Mac and iPhone, per-game patches, and the launcher. Those stay in the fork.

Replace unconditional failure and shared task completion with a precision-checked kernel NTP query and independent SDK event lifetimes. Preserve real failure results and release the guest core across contended service locks.
Translate the Horizon IPv4 socket ABIs, retain generation-safe descriptor lifetimes and bound local discovery. Publish real guest contexts across blocking hooks using the existing per-thread gate stack. Add LDN discovery, membership and lifecycle plus local play-history registration from privately imported resources. Preserve upstream offline account and TLS boundaries.

Synthetic and real loopback fixtures, ASan/UBSan, the full host suite, the production HLE registry, iPhoneOS compilation and synthesized resource-decoder tests pass. Mario Kart 3.0.5 hardware results are owner reports from the originating fork, documented separately from this rebased branch. No frontend, Nextendo integration or game/firmware data is included.
Keep the test thread heap-owned like production, release its handle after joining, and include it in the local-network test target. Stop socket fixtures at failed prerequisites rather than continuing with invalid descriptors.
Add the system permission description required by the native socket transport. Remove redirect-only resolver bookkeeping from the upstream local-only backend; no routing provider or UI is included.
…y copyout

Kernel recvfrom copyout cannot invoke the guest dirty-page signal hook. Prepare the pending span before each receive attempt, including after readiness waits and partial stream reads, without retrying EFAULT or admitting unrelated invalid memory.

The real dirty-tracker fixture passes 43 checks normally and under ASan/UBSan. Negative controls reproduce missing preparation and the wait boundary. The full host suites, iOS compile check and documentation checks also pass.
…e copyout

The kernel getsockopt copyout cannot invoke the dirty-page fault hook. Prepare the validated result span without changing SetSockOpt inputs, invalid-memory failures, result sizes or errno translation.

The expanded real dirty-tracker fixture reproduces 16 failures in 87 checks before this change and passes all 87 normally and under ASan/UBSan afterward. This synthetic reproduction does not establish a game disconnect cause.
Use owned DNSService queries and cancellation identities instead of leaving resolver work and late completions alive. Retire native references on one serial queue, fence finalization, and retain the offline and synchronous resolver paths.
Use owned DNSService queries and cancellation identities instead of leaving resolver work and late completions alive. Retire native references on one serial queue, fence finalization, and retain the offline and synchronous resolver paths.
Keep per-session read-only snapshots and scoped eligibility checks behind a default-off provider hook. Exercise startup, delivery lifetime and sanitizer failures with original offline fixtures; compose the TLS dependency without replacing upstream filesystem behavior.
Replace unconditional failure and shared task completion with a precision-checked kernel NTP query and independent SDK event lifetimes. Preserve real failure results and release the guest core across contended service locks.
Check Foundation fixture construction and bounded DER reads explicitly, initialize the filesystem fixture handle, and remove a fixed filesystem null-argument baseline entry. No new suppression or baseline expansion.
…tely

Ordinary-buffer TLS, policy and resolver tests use the explicit no-tracker fixture hook. The real socket-copyout fixture keeps its registered dirty tracker and links the NSD backend needed by this stack.
I preserved upstream's readback allocation expectation and the existing explicit serial-table release. The LAN/LDN transport and tracked socket copyout corrections remain unchanged. Local network fixtures, 87 normal and sanitized copyout checks, 61 Metal geometry checks, and the documentation tests pass after the merge.
…stream-online-backend-proposal

# Conflicts:
#	dm/host/Makefile
#	dm/host/hle_net_backend.c
#	dm/host/tests/Makefile
#	docs/agents/symptoms.md
#	docs/reference/services.md
…stream-online-backend-proposal

# Conflicts:
#	dm/host/tests/Makefile
…stream-network-clock-main

# Conflicts:
#	docs/agents/symptoms.md
RobiiAragon and others added 14 commits November 3, 2026 23:52
I port only the two-line renderer correction from seedds/nxnative commit
5ccbedd. Existing key 152 indexed one past
the 152-element g_red_once array. Capacity 153 covers that key, and the
upstream compile-time assertion rejects future keys outside the array.

The source macro fixture reproduces the old global-buffer-overflow under
ASan, accepts key 152 with ASan/UBSan, and rejects key 153 at compile time.
No Python, lint baseline, suppression, shader-chain or profile changes
from the upstream maintenance commit are included.
I merge frozen upstream main a3d85ae
after it overlaps the earlier typed-flow maintenance in this draft.
I reconcile the three parser/test conflicts hunk by hunk to upstream's
canonical implementations and remove the coupled tuple/count remnants.
Those files and their ratchet now match main; the backend host sources
remain byte-identical to the preceding 8f03fe1 head.

79 focused parser/shader-chain/source-cache tests, 17 documentation/Xcode
tests and 286 output-equivalence comparisons pass. The isolated upstream
pinned Ruff/Pyright environment passes 389/389 with zero new/stale findings.

The inherited upstream shader chain is now 0ad559cdbb28. I have not rebuilt
or qualified game outputs for that chain. The draft scope/dependency and
game-qualification limits remain in place.
Super Mario Bros. Wonder's NPLN gRPC failed with 2321-4992 (UNAVAILABLE)
before it opened a socket: nnsocketGetAddrInfo returned EAI_FAIL for
every SDK that exports both C++ GetAddrInfo overloads, and Wonder's
does. In Wonder 1.2.1's sdk, nnsocketGetAddrInfo branches to the
plain-addrinfo overload, and FromBufferInternal<addrinfo> stores the
address at +0x20 (AddrInfo: +0x18), so the C resolver now gets that
layout whenever the SDK has it.

RecvMsg and SendMsg, which gRPC's TCP endpoint reads and writes with,
scatter and gather through one host buffer using Horizon's 0x30-byte
msghdr. Ancillary data is reported as none on receive and refused on
send. Name lookups in a network mode are logged with their result;
resolver options are logged, and a set one is accepted without being
applied. services.md has the entry; t_net covers the round trip and
the resolver layout.
…e cases

On Horizon nn::socket's errno is the C library's: detail::SetLastErrno and
GetLastErrno write and read *__errno_location(). The host kept its own copy
only, so code that reads errno itself saw a stale value. Super Mario Bros.
Wonder's gRPC sets EAFNOSUPPORT when it leaves IPv6 (main+0xb61480), then
reads errno after its non-blocking connect and took that stale value for
the connect's EINPROGRESS (tcp_client_posix.cc:299), so it never sent a
byte. Every socket hook that fails now also writes the game's errno through
its __errno_location, and GetLastErrno reads it in a network mode.

RecvMsg and SendMsg no longer fail a stream message over the 16 MiB buffer:
it moves in part, as a real socket may. A datagram that long fails with
EMSGSIZE. A datagram longer than the iovecs is cut to them and reported
with MSG_TRUNC; the backend's new dm_net_socket_type tells the two apart.
t_net covers the errno write and read, EMSGSIZE and MSG_TRUNC.
Brings the branch (seedds seedds#961 plus the gRPC socket fixes) up to current
main. The conflicts follow how eyasbzour/nxnative's nextendo/wonder
resolved the same ones:

- hle_net.c / hle_offline.c: main's offline layer (the event descriptor
  table, Select as Poll over its sets, Star Fox's SDK 21 overloads) with
  seedds#961's transport in a network mode. The SDK 21 overloads go to the
  transport too. Main's ssl stubs stay out (seedds#961's hle_ssl.c serves
  them), and the SDK 4 GetPeerName is registered once, in hle_net.c.
  EventFd keeps main's offline table and still fails in a network mode.
- hle_account.c: main's SDK-built AsyncContext (no constructor takeover)
  with seedds#961's account requests; with Nextendo off a context answers as
  main's does, and a move constructor copies the vtable.
- hle_fs.c, the Makefiles, build_ipa.sh, gen_build.py, env_switches.tsv,
  main.c, the Mario Kart 8 README: both sides kept.
- hle_bcat.c drops HLE_FORCE_BIG, which main removed (every takeover now
  runs on the host stack), and t_net_eventfd links the socket backend
  and stubs guest_sym, which hle_net.c now calls.
…ket table

gRPC (nn::npln) wakes its pollers with an event descriptor and polls it with its sockets. In a network mode EventFd failed, and the host's own event table numbered from 0 as the transport's sockets do, so a descriptor could have shared a socket's number while Poll went to the transport, which knew nothing of it.

dm_net_eventfd takes a socket-table slot (type NET_EVENT) whose host descriptor is the read end of a local socket pair holding one byte while the counter is above zero. Poll, Select, Close, finalize and the generation checks then cover sockets and event descriptors alike; socket operations on one fail with ENOTSOCK. Reads honour the semaphore and never-block flags, a blocking read waits in the transport's bracketed slices and fails with EBADF if closed meanwhile, and a write that would pass the counter's maximum waits or fails with EAGAIN. Offline the host's counter table is unchanged.

t_net covers mixed Poll and Select, the flags, a read blocked across a write and across a close.

On this branch the hooks also write a failed call to the game's errno, as
every socket hook does here; t_net checks it for the EAGAIN of an empty
never-blocking read.
… clock and wait stubs

hle_bcat_apple.m takes the game's BCAT title IDs from DM_BCAT_TITLES and
DM_BCAT_PLAIN_TITLE (test_no_title_ids_in_the_host); the BCAT fixtures pass
Splatoon 2's. t_hle_wait, t_network_clock and t_net define the clock and
wait symbols main's hle_os.c and hle_thread.c now provide.
@github-actions github-actions Bot added host The Horizon host: SVCs, services, threads, memory (dm/host) packing Building an IPA or a Mac app: the NSP script, extraction, the shader split, the packers ios The iPhone and iPad app (dm/ios) tools Tools and scripts (dm/tools, scripts) docs Documentation ci Continuous integration and repository settings (.github) game: 0100152000022000 Mario Kart 8 Deluxe labels Nov 13, 2026
…e first one listed

iOS has no routing table to read and getifaddrs lists cellular before Wi-Fi,
so in a Nextendo session nifm reported the cellular address while the phone
played over Wi-Fi, and Pia offered it to other players for a direct link. The
first pass now takes the adapter whose address a UDP socket connected past
the local networks gets (connect sends nothing); any adapter if none matches.
The adapter is logged when it changes.
@eyasbzour
eyasbzour marked this pull request as ready for review November 13, 2026 18:37
hle_oe.c's dm_set_opmode calls dm_hid_opmode_changed and oe::ExecuteProgram
calls dm_host_shutdown_then; the test stubs them as t_notify_sched.c does.
…e_account.c expects

hle_account.c keeps main's SDK-built AsyncContext, so there is no constructor
hook to call: the test's constructor writes nothing, as the SDK's writes only
guest memory the host never reads. Added checks: a move constructor writes the
vtable and a null IPC pointer and carries the request; with Nextendo off a
context answers as the offline host does.
…c's too

The check wanted cdevice=Mac..., but a virtual Mac (a CI runner) reports
VirtualMac2,1. It now compares with hw.model, as save_sync.c device_name
reads it.
eyasbzour added a commit to eyasbzour/nxnative that referenced this pull request Nov 13, 2026
…eded

The survey docs still said no PRs go to seedds and that Mush8 code may be ported. The descriptor table, EventFd and NPLN gaps are filled by seedds#2589, the client ID is a build setting, and the Mario Kart menu sites move to a kart branch check.
eyasbzour added a commit to eyasbzour/nxnative that referenced this pull request Nov 13, 2026
…eded

The survey docs still said no PRs go to seedds and that Mush8 code may be ported. The descriptor table, EventFd and NPLN gaps are filled by seedds#2589, the client ID is a build setting, and the Mario Kart menu sites move to a kart branch check.

(cherry picked from commit 500a94b)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Continuous integration and repository settings (.github) docs Documentation game: 0100152000022000 Mario Kart 8 Deluxe host The Horizon host: SVCs, services, threads, memory (dm/host) ios The iPhone and iPad app (dm/ios) packing Building an IPA or a Mac app: the NSP script, extraction, the shader split, the packers tools Tools and scripts (dm/tools, scripts)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants