Skip to content

Architecture

ryu_ldn_nx registers three IPC services simultaneously from main.cpp:

| Service | Type | Role | |---------|------|------| | ldn:u | MITM | Intercepts Nintendo’s LDN user service | | bsd:u | MITM | Intercepts BSD socket calls for LDN subnet traffic | | ryu:cfg | Custom | Tesla overlay config and status display |

All three share a 384 KB expanded heap (g_heap_memory). new/delete route to this heap via lmem::ExpHeap. Do not grow buffers casually.

┌──────────────────────────────────────────────────────────────────┐
│ Nintendo Switch │
│ │
│ Game (ldn:u IPC) Game (bsd:u IPC) Tesla overlay│
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ICommunicationService BsdMitmService ConfigService │
│ │ │ │
│ ▼ ▼ │
│ LdnStateMachine ProxySocketManager ◀── ProxyData ──┐ │
│ LdnPacketDispatcher │ │ │
│ LdnSessionHandler │ │ │
│ │ │ │ │
│ ▼ ▼ │ │
│ RyuLdnClient ◀── TCP ──▶ Ryujinx LDN Server ──────────────┘ │
│ │ │
│ ▼ │
│ ConnectionState + ReconnectManager │
└──────────────────────────────────────────────────────────────────┘

The core LDN service. Games call its IPC commands (CreateNetwork, Scan, Connect, etc.) which are translated to RyuLdn protocol messages over TCP.

Key files:

  • sysmodule/source/ldn/ldn_icommunication.cpp — main implementation (2100+ lines)
  • sysmodule/source/ldn/ldn_state_machine.cpp — state machine (CommState transitions)
  • sysmodule/source/ldn/ldn_packet_dispatcher.cpp — routes incoming packets to handlers
  • sysmodule/source/ldn/ldn_session_handler.cpp — session lifecycle management
  • sysmodule/source/ldn/ldn_proxy_handler.cpp — proxy data routing

Intercepts BSD socket calls. Sockets targeting the LDN subnet 10.114.x.x become proxy sockets whose traffic is tunneled through ProxyData packets. This is how PIA mesh protocol traffic (UDP broadcasts, unicast game data) reaches other peers without pcap.

Key files:

  • sysmodule/source/bsd/proxy_socket.cpp — proxy socket implementation (bind, connect, sendto, recvfrom)
  • sysmodule/source/bsd/proxy_socket_manager.cpp — socket registry, port allocation, data routing
  • sysmodule/source/bsd/ephemeral_port_pool.cpp — dynamic port allocation (49152–65535)

Custom IPC service for the Tesla overlay. Exposes real-time LDN status, connection info, and live configuration changes.

Key files:

  • sysmodule/source/config/config_ipc_service.cpp — IPC command handlers
  • sysmodule/source/ldn/ldn_shared_state.cpp — shared state between MITM and config services

Games use Nintendo’s PIA (Protocol Independent Application) library for mesh networking on top of LDN. After CreateNetwork/Connect establishes the LDN session:

  1. Game calls GetIpv4Address() → gets virtual LDN IP (e.g., 10.114.0.1)
  2. Game calls GetNetworkInfo() → gets NetworkInfo with all nodes’ IPs/MACs
  3. Game identifies its own node by matching GetIpv4Address() result against NetworkInfo.ldn.nodes[].ipv4Address
  4. Game opens UDP sockets via bsd:u → MITM intercepts bind/connect to 10.114.x.x → creates ProxySocket
  5. PIA sends broadcast UDP packets (mesh discovery) → RouteIncomingData() delivers to all matching sockets
  6. PIA sends unicast UDP/TCP packets → ProxySocketProxyData header → TCP tunnel → server → peer

PIA mesh discovery relies on UDP broadcasts reaching every listening socket on the same port. RouteIncomingData() uses FindAllSocketsByDestination() to fan out broadcast packets to all matching proxy sockets. The previous single-delivery FindSocketByDestination() broke PIA’s mesh protocol in games like Smash Bros.

All LDN IPs use Ryujinx format — big-endian read as uint32 (e.g., 10.114.0.1 = 0x0A720001). BSD sockaddr_in.sin_addr uses network byte order. Never double-convert.

HandleServerPacket signals m_response_event only for actual response packets that WaitForResponse expects:

| Signals event | Packet types | |---|---| | Yes | Connected, ScanReply, ScanReplyEnd, RejectReply, ProxyConnectReply, NetworkError | | No | ProxyConfig, ProxyData, ProxyConnect, ExternalProxy, SyncNetwork, Ping |

This prevents spurious wake-ups when ProxyConfig arrives before Connected. All other packet types (including Initialize, Passphrase, CreateAccessPoint, Reject, Scan, Connect, Disconnect, ProxyDisconnect, SetAcceptPolicy, SetAdvertiseData, ExternalProxyToken, ExternalProxyState) also do not signal the event.

  • Fast first retry: 200ms delay on first failure, then exponential backoff (3s initial from config, 2x multiplier, 30s cap)
  • Jitter: ±10% randomization to prevent thundering herd
  • TCP keepalive: 30s idle / 10s interval / 5 probes (Switch only)
  • Graceful disconnect: shutdown(SHUT_WR) before close()
  • Auto-reconnect: Configurable via max_reconnect_attempts; 0 disables auto-reconnect entirely
MITM Server Thread (2×32KB) Receive Thread (16KB) Config Thread (8KB)
│ │ │
├── ldn:u IPC requests ├── TcpClient.recv() ├── ryu:cfg IPC requests
├── CreateNetwork/Connect ├── HandleServerPacket() ├── Status queries
├── Scan/GetNetworkInfo ├── All packet processing ├── Config changes
└── State transitions └── P2P connect worker └── Force reconnect
Log Thread (4KB)
└── File logger, idle-timeout close
P2P Threads (dynamic, up to 8×16KB):
├── P2P accept (16KB) ─── loopback listener
├── P2P client recv (16KB) ─── per-session receive
└── P2P lease (8KB) ─── UPnP lease renewal

State transitions are signaled cross-thread via os::Event (AutoClear for response/scan/reject, ManualClear for error).

  • Heap: 384 KB expanded heap
  • Malloc buffer: 1 MB for TLS heap central. Sufficient for gameplay traffic.
  • Thread stacks: 2×32 KB (MITM), 16 KB (background receive), 16 KB (P2P connect), 16 KB (P2P accept), 16 KB (P2P client recv), 8 KB (P2P lease), 8 KB (config), 4 KB (log). Total: ~148 KB in permanent stacks, plus up to 8×16 KB (128 KB) P2P session stacks allocated dynamically.
  • BSD sessions: 14 concurrency limit
  • Total sysmodule budget: ~10 MB across all Switch sysmodules