Architecture
Architecture
Section titled “Architecture”ryu_ldn_nx registers three IPC services simultaneously from main.cpp:
Service Overview
Section titled “Service Overview”| 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.
Data Flow
Section titled “Data Flow”┌──────────────────────────────────────────────────────────────────┐│ 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 │└──────────────────────────────────────────────────────────────────┘Component Details
Section titled “Component Details”ICommunicationService (ldn:u MITM)
Section titled “ICommunicationService (ldn:u MITM)”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 handlerssysmodule/source/ldn/ldn_session_handler.cpp— session lifecycle managementsysmodule/source/ldn/ldn_proxy_handler.cpp— proxy data routing
BsdMitmService (bsd:u MITM)
Section titled “BsdMitmService (bsd:u MITM)”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 routingsysmodule/source/bsd/ephemeral_port_pool.cpp— dynamic port allocation (49152–65535)
ConfigService (ryu:cfg)
Section titled “ConfigService (ryu:cfg)”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 handlerssysmodule/source/ldn/ldn_shared_state.cpp— shared state between MITM and config services
PIA Protocol and Game Data
Section titled “PIA Protocol and Game Data”Games use Nintendo’s PIA (Protocol Independent Application) library for mesh networking on top of LDN. After CreateNetwork/Connect establishes the LDN session:
- Game calls
GetIpv4Address()→ gets virtual LDN IP (e.g.,10.114.0.1) - Game calls
GetNetworkInfo()→ getsNetworkInfowith all nodes’ IPs/MACs - Game identifies its own node by matching
GetIpv4Address()result againstNetworkInfo.ldn.nodes[].ipv4Address - Game opens UDP sockets via
bsd:u→ MITM interceptsbind/connectto10.114.x.x→ createsProxySocket - PIA sends broadcast UDP packets (mesh discovery) →
RouteIncomingData()delivers to all matching sockets - PIA sends unicast UDP/TCP packets →
ProxySocket→ProxyDataheader → TCP tunnel → server → peer
Broadcast Delivery
Section titled “Broadcast Delivery”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.
IP Address Convention
Section titled “IP Address Convention”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.
Packet Signaling
Section titled “Packet Signaling”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.
Connection Resilience
Section titled “Connection Resilience”- 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)beforeclose() - Auto-reconnect: Configurable via
max_reconnect_attempts;0disables auto-reconnect entirely
Threading Model
Section titled “Threading Model”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 renewalState transitions are signaled cross-thread via os::Event (AutoClear for response/scan/reject, ManualClear for error).
Memory Budget
Section titled “Memory Budget”- 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