Skip to content

feat(libSceNet): virtual LAN for datagram sockets behind the net bridge (T41) - #43

Open
AlxcBTW wants to merge 4 commits into
mainfrom
feat/virtual-lan
Open

AlxcBTW wants to merge 4 commits into
mainfrom
feat/virtual-lan

Conversation

@AlxcBTW

@AlxcBTW AlxcBTW commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

Handed over to @hypermoist for T41; built, fixed and tested locally (below).

What

The engine side of the virtual LAN (bridge protocol, Broadcasts and the virtual LAN). Everything is behind the bridge's virtual address (state.address != 0 and a connected net service); with no session the host path is exactly as before.

  • Interface. sceNetCtlGetInfo shows the virtual address (code 14), 255.254.0.0 (15), no gateway (16) and MTU 1200 (3) while a session is active. The virtual interface is also reported when the host has none.
  • Sockets. A plain AF_INET SOCK_DGRAM socket bound to 0.0.0.0, the virtual address or a broadcast address, or one that sends/connects before a bind, is carried through the net service. Per virtual port one loopback group socket is registered with bind; sends leave through it with the 8-byte header, and a small forwarder thread copies each delivered datagram to every game socket on that port (so SO_REUSEADDR/SO_REUSEPORT sharing binds once and delivers to all). Port 0 takes a free virtual port, and getsockname shows the game's port.
  • Out. 255.255.255.255 and 198.19.255.255 need SO_BROADCAST (else EACCES) and go out as broadcast frames; 198.18.0.0/15 unicast goes to that peer; multicast is dropped without an error; every other destination fails at once with ENETUNREACH. A socket that stays on the host during a session (bound to a real or loopback address, or bound before the session) does not send to a broadcast or virtual address either: such a send or connect fails with ENETUNREACH, and its multicast is dropped. Nothing is ever sent to the host network. connect/send/getpeername work on these sockets and a connected socket only hears its peer.
  • In. Frames arrive with the peer's virtual address and the sender's port (recv, recvfrom, recvmsg).
  • Ports are data. No port numbers or title knowledge in code; the tests use arbitrary ports.
  • Tests: net_bridge_lan against FakeNetService covers broadcast out (both addresses, no SO_BROADCAST, multicast, off-subnet), broadcast in, unicast, an inactive session (before and after), the interface answers, bind to the virtual address, ephemeral and implicit binds, connect, shared ports with SO_REUSEADDR and SO_REUSEPORT, a cleared reuse option, host sockets during a session, two first sends at once on one unbound socket, and sceNetRecvmsg (vectors, MSG_TRUNC, sender name).
  • Docs: new entry in docs/dev/debt/libSceNet.md.

Spec questions this design settles

  • Answer by broadcast or by unicast? Both work: a unicast reply to the sender's address and port is an ordinary datagram, and a reply to a broadcast address is carried as a broadcast.
  • Limited or subnet broadcast? Both are carried; the game's own computed subnet broadcast equals 198.19.255.255 because the interface netmask is 255.254.0.0.
  • Which interface call? sceNetCtlGetInfo (the call seen at boot) is answered. sceNetGetIfList* and sceNetConfigGetIfaddr still throw (not implemented today) and stay open.

Still open

  • Multicast (mDNS): dropped, not carried, as the spec says; group joins are not implemented.
  • Interface list/ifaddr calls (broadcast address of an interface).
  • Sockets bound to a real address, to 127.0.0.0/8 or before the session started stay on the host (their broadcast and virtual sends fail, see Out); IPv6 sockets are untouched.
  • A socket keeps its mode when the session ends (sends are then dropped by the net service).
  • Nothing measured on a PS5 or with a game; no game was started.
  • Overlap: feat(bridge): observe the network libraries through the import thunks, not in every export #35 touches the same exports in libSceNet/Export.cpp (observation hook); whichever lands second merges main and resolves it.

Tested

Built and run locally on Linux and on Windows (MinGW cross build, run under Wine), commit 5ede8a54d, main merged in.

  • Linux (Release): ctest -R 'net|bridge|^public_headers' --timeout 120 passes, 124 of 124 that run; bridge_e2e_game skips as designed without a live net service. net_bridge_lan also passed 30 runs in a row and 16 runs 8 at a time.
  • Windows under Wine (Release): libSceNet.prx, libSceNetCtl.prx and every net/bridge test built. net_bridge_lan, net_bridge_p2p, net_bridge, guest_net_ctl and the rest pass (net_bridge_lan passed 50 serial runs). guest_sce_net and net_ipv6only_patched fail under Wine on unchanged main sources too, so they are not from this PR. public_headers_compile_0 hit its 30 s limit on a cold first run and passes when run alone.
  • Mutation checks: the tests fail when any of these is undone: clearing SO_REUSEADDR, the host-socket guard, the fix for two first sends at once, MSG_TRUNC in sceNetRecvmsg, the SO_BROADCAST check, and connected-peer filtering.
  • test_public_headers.py --audit-only and tools/check_conventions.py pass.

Fixed while testing: host sockets could still send broadcasts to the real network during a session; two threads sending first on one unbound socket bound it twice and leaked a virtual port (they could also fail with EINVAL); leaving a shared port dropped every membership of that socket; a cleared reuse option stayed set; the bind request held the lock that the forwarder and every send need.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none (hub task T41)
  • GPUs tested: n/a
  • AI-assisted: yes

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

…ge (T41)

While the bridge holds a virtual address, plain AF_INET datagram sockets bound to the wildcard, the virtual address or a broadcast address (or sending before a bind) are carried as virtual LAN datagrams to the net service, and sceNetCtlGetInfo shows the virtual interface. Inactive means the host path as before.
hypermoist added 3 commits October 28, 2026 17:16
…t first binds (T41)

- A socket bound on the host while a session is active no longer sends to
  a broadcast or virtual address on the real network: those sends and
  connects fail with ENETUNREACH and multicast is dropped.
- Two threads sending first on one unbound socket bound it twice and leaked
  a virtual port; the member socket is now bound once and a lost race
  leaves its group again.
- Leaving a shared port removes one membership, not every entry of it.
- Clearing SO_REUSEADDR or SO_REUSEPORT takes effect again.
- The bind request to the net service no longer holds the lock the
  forwarder and every send take.
- Tests: host sockets during a session, cleared reuse options,
  SO_REUSEPORT sharing, concurrent first sends, sceNetRecvmsg vectors,
  truncation and sender name; two waits in the existing tests that raced
  the fake service.
@hypermoist
hypermoist marked this pull request as ready for review October 28, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant