Skip to content

ams::mitm::p2p::P2pProxyServer

P2P Proxy Server.

TCP server that hosts direct P2P connections for LDN multiplayer. When a Switch creates a network (hosts), this server accepts connections from other players who join via P2P instead of through the relay server.Thread SafetyAll public methods are thread-safe. Internal state is protected by mutex. serverStart(port) - begin listening- open UPnP port (optional but recommended)Accept connections, validate tokensRoute proxy messages between sessions- cleanup and close

Type: constexpr uint16_t

Type: constexpr int

Type: constexpr uint16_t

Type: constexpr int

Type: constexpr int

Type: constexpr int

Type: constexpr int

Type: constexpr int

Type: os::Mutex

Type: int

Type: uint16_t

Type: uint16_t

Type: std::atomic< bool >

Type: bool

Type: os::ThreadType

Type: os::ThreadType

Type: bool

Type: std::unique_ptr<>

Type: int

Type: std::unique_ptr<>

Type: int

Type: ryu_ldn::protocol::ExternalProxyToken

Type: int

Type: os::ConditionVariable

Type: uint32_t

Type: MasterSendCallback

Type: void *

Type: uint32_t

Type: HostProxyDataCallback

Type: void *

Type: constexpr int

Type: constexpr int

void P2pProxyServer(MasterSendCallback master_callback, void * user_data)

Constructor.

Constructor - initializes server state.The constructor initializes all member variables to safe defaults:

Parameters:

  • master_callback (MasterSendCallback)
  • user_data (void *)
void ~P2pProxyServer()

Destructor - stops server and cleans up.

Destructor - cleanup all resources.Ensures clean shutdown:

void P2pProxyServer(const&)

Parameters:

  • param (const&)
& operator=(const&)

Parameters:

  • param (const&)

Returns: &

bool Start(uint16_t port)

Start the TCP server.

Start the TCP server and begin accepting connections.portPort to listen on (default: auto-select from range)paramportSpecific port to bind to, or 0 for auto-select from rangeparamtrue if server started successfullyreturnTries ports 39990-39999 if port is 0 @gdb{tag=“P2P:SERVER”, msg=“Server starting”}notetrue if server started successfully, false on errorreturnSocket Creation Flow1::socket(AF_INET,SOCK_STREAM,0)│├─AF_INET=IPv4├─SOCK_STREAM=TCP(reliable,ordered)└─Protocol0=defaultforstream(TCP)normal2setsockopt(SO_REUSEADDR)│└─Allowbindtorecently-closedport(Importantforquickrestartaftercrash)normal3setsockopt(TCP_NODELAY)│└─DisableNagle’salgorithm(Criticalforlow-latencygametraffic)normal4bind(port)│└─Tryports39990-39999untilonesucceedsnormal5listen(backlog=8)│└─Allowupto8pendingconnectionsnormal6Createacceptthread│└─Threadloopscallingaccept()

Parameters:

  • port (uint16_t)

Returns: bool

void Stop()

Stop the server and disconnect all sessions.

Stop the server and disconnect all clients.@gdb{tag=“P2P:SERVER”, msg=“Server stopping”}Performs clean shutdown:Thread-safe: Can be called from any thread.

bool IsRunning()

Check if server is running.

Check if the server is currently running.@gdb{tag=“P2P:SERVER”, msg=“Server state queried”}true if running, false if stoppedreturn

Returns: bool

uint16_t GetPrivatePort()

Get the private (local) port.

Returns: uint16_t

uint16_t GetPublicPort()

Get the public (UPnP) port.

Returns: uint16_t

uint16_t NatPunch()

Open a public port via UPnP.

Open a public port via UPnP for NAT traversal.Attempts to open a port mapping on the router using UPnP. Tries ports 39990-39999 until one succeeds.If successful, starts a lease renewal thread.The public port number if successful, 0 if UPnP failedreturnUPnP Port Mapping FlowSwitchRouter(IGD)Internet││││SSDPDiscovery──────────►│││◄────────IGDResponse────││││││AddPortMapping──────────►│││(39990,TCP,60s)│││◄─────────────────││││││[Nowport39990onrouterforwardstous]││││││◄────TCP───────││◄──────────────────────────│(torouter:39990)││││ Try Multiple Ports?A port might be:By trying 39990-39999, we increase the chance of finding an available port.

Returns: uint16_t

void ReleaseNatPunch()

Release UPnP port mapping.

Release the UPnP port mapping and stop lease renewal.@gdb{tag=“P2P:NAT”, msg=“Releasing NAT punch”}Called during server shutdown to:Important: We clean up our mappings to be a good network citizen. Abandoned mappings waste router resources and can cause issues for other applications.

void AddWaitingToken(const& token)

Add a waiting token for an expected joiner.

Called when master server notifies us someone is about to connect. @gdb{tag=“P2P:SESSION”, msg=“Token added to waiting list”}tokenToken received from master serverparamToken FlowWhen a player wants to join via P2P, the master server:When the joiner connects, they send the same token in ExternalProxyConfig. We match tokens to validate the connection and assign the virtual IP. Full HandlingIf the token queue is full (MAX_WAITING_TOKENS = 16), we drop the oldest token. This handles edge cases like:

Parameters:

  • token (const&)
bool TryRegisterUser(* session, const ryu_ldn::protocol::ExternalProxyConfig & config, uint32_t remote_ip)

Try to register a connecting user.

Try to authenticate a connecting client.Called bywhen a client sends ExternalProxyConfig. Validates the token, assigns virtual IP, and adds to player list. @gdb{tag=“P2P:SESSION”, msg=“User registration attempt”}sessionTheattempting to authenticateconfigThe ExternalProxyConfig sent by the clientremote_ipThe client’s physical IP address (network byte order)paramtrue if authentication succeeded, false if no matching token foundreturnAuthentication ProcessWait up to AUTH_WAIT_SECONDS (1 second) for a matching tokenFor each waiting token, check:If match found: IP HandlingThe master server sends all-zeros for PhysicalIP if the client has a private IP address (192.168.x.x, 10.x.x.x, etc.). This allows clients behind NAT to connect without IP validation. SafetyUses condition variable (m_token_cv) to efficiently wait for new tokens. The thread sleeps until either:

Parameters:

  • session (*)
  • config (const ryu_ldn::protocol::ExternalProxyConfig &)
  • remote_ip (uint32_t)

Returns: bool

void HandleExternalProxyStateChange(uint32_t virtual_ip, bool connected)

Apply a master-originated ExternalProxyState change.

Mirrors Ryujinx P2pProxyServer.HandleStateChange. When the master tells us a player is no longer connected (because their join didn’t complete or they dropped out without notifying us), we have to remove the matching waiting token AND disconnect the matching session — otherwise the zombie session keeps the recv thread alive and the token sits forever blocking the slot for retries.virtual_ipExternalProxyConnectionState.IpAddressconnectedExternalProxyConnectionState.Connectedparam

Parameters:

  • virtual_ip (uint32_t)
  • connected (bool)
void ReapZombieSessions()

Free disconnected sessions queued by OnSessionDisconnected.

Called when a session disconnects.Walks m_zombie_sessions, joins+destroys the recv thread of any session whose m_thread_done atomic has flipped to true, thens the session and shrinks the array. Called from AcceptLoop on every iteration and fromso disconnected peers don’t leak their 64 KB recv buffer (RECV_BUFFER_SIZE in the header).Handles cleanup when a client disconnects:The master server notification allows it to:

void Configure(const& config)

Configure broadcast address from ProxyConfig.

Calculates the broadcast address for the virtual network. Formula: broadcast = IP | ~maskExample for 10.114.0.1 with /16 mask:

Parameters:

  • config (const&)
void SetHostVirtualIp(uint32_t virtual_ip)

The host’s own virtual IP — packets whose dest matches this value or the configured broadcast address are dispatched to m_host_data_callback.

Parameters:

  • virtual_ip (uint32_t)
void SetHostDataCallback(HostProxyDataCallback callback, void * user_data)

Register the in-process inbound sink. Pass nullptr to detach.

Parameters:

  • callback (HostProxyDataCallback)
  • user_data (void *)
bool BroadcastFromHost(ryu_ldn::protocol::ProxyDataHeader & header, const uint8_t * data, size_t data_len)

Broadcast a ProxyData packet from the host’s local game out to every authenticated P2P session. The header’s source_ipv4 is filled in with the host’s virtual IP if the caller left it at 0 (mirrors the “bound on 0.0.0.0” handling in RouteMessage).

Parameters:

  • header (ryu_ldn::protocol::ProxyDataHeader &)
  • data (const uint8_t *)
  • data_len (size_t)

Returns: bool

void HandleProxyData(* sender, ryu_ldn::protocol::ProxyDataHeader & header, const uint8_t * data, size_t data_len)

Handle ProxyData message from a session.

@gdb{tag=“P2P:ROUTE”, msg=“Proxy data handled (server)”}

Parameters:

  • sender (*)
  • header (ryu_ldn::protocol::ProxyDataHeader &)
  • data (const uint8_t *)
  • data_len (size_t)
void HandleProxyConnect(* sender, ryu_ldn::protocol::ProxyConnectRequest & request)

Handle ProxyConnect message from a session.

@gdb{tag=“P2P:ROUTE”, msg=“Proxy connect handled (server)“}ProxyConnect initiates a virtual TCP connection between two players. The target responds with ProxyConnectReply (accept/reject).

Parameters:

  • sender (*)
  • request (ryu_ldn::protocol::ProxyConnectRequest &)
void HandleProxyConnectReply(* sender, ryu_ldn::protocol::ProxyConnectResponse & response)

Handle ProxyConnectReply message from a session.

@gdb{tag=“P2P:ROUTE”, msg=“Proxy connect reply handled (server)“}Response to ProxyConnect - indicates whether the connection was accepted.

Parameters:

  • sender (*)
  • response (ryu_ldn::protocol::ProxyConnectResponse &)
void HandleProxyDisconnect(* sender, ryu_ldn::protocol::ProxyDisconnectMessage & message)

Handle ProxyDisconnect message from a session.

@gdb{tag=“P2P:ROUTE”, msg=“Proxy disconnect handled (server)“}Notifies the target that a virtual connection has been closed.

Parameters:

  • sender (*)
  • message (ryu_ldn::protocol::ProxyDisconnectMessage &)
void OnSessionDisconnected(* session)

Called when a session disconnects.

@gdb{tag=“P2P:SESSION”, msg=“Session disconnected event”}

Parameters:

  • session (*)
void AcceptLoop()

Accept loop thread function.

Main loop for accepting incoming TCP connections.@gdb{tag=“P2P:SERVER”, msg=“Accept loop running”}This function runs in a dedicated thread and loops continuously:The loop exits when:Accept BehaviorThe listen socket stays blocking, but every iteration of the loop calls poll(POLLIN, AcceptPollTimeoutMs) before accept() so a flipped m_running can pull us out without depending on close(listen_fd) waking the kernel-side accept (Switch bsd:s does not always do that the way Linux does — close on the listen fd left accept() spinning on errno=113 forever, blocking’s WaitThread).Sémantique externe identique à NetCoreServer.TcpServer.Stop() (TcpServer.cs:254-295):flips a flag, the accept side stops arming new accepts, the listen socket is closed, all sessions are disconnected, the call returns. We just substitute their async Socket.AcceptAsync + IOCP callback with a blocking poll on a BSD-native loop — same observable behaviour, BSD-friendly impl.

void RouteMessage(* sender, ryu_ldn::protocol::ProxyInfo & info, SendFunc send_func)

Route a message to appropriate session(s)

Route a proxy message to appropriate destination(s)senderThe session that sent the messageinfoProxy info with source/dest IPssend_funcFunction to call for each target session @gdb{tag=“P2P:ROUTE”, msg=“Message routed”}paramSendFuncCallable type for sending to a sessiontemplateparamsenderThe session that sent the messageinfoProxyInfo containing source and destination IPssend_funcFunction to call for each target sessionparamRouting LogicFix source IP if zero (unbound socket sends as 0.0.0.0)Reject spoofing (source IP must match sender’s virtual IP)Translate legacy broadcast (0xc0a800ff -> actual broadcast)Route to destination: NoteWe validate that the source IP matches the sender’s virtual IP. This prevents a malicious client from impersonating another player.

Parameters:

  • sender (*)
  • info (ryu_ldn::protocol::ProxyInfo &)
  • send_func (SendFunc)
void NotifyMasterDisconnect(uint32_t virtual_ip)

Notify master server of connection state change.

Notify master server of a client disconnection.@gdb{tag=“P2P:SESSION”, msg=“Master disconnect notification”}Sends ExternalProxyConnectionState with Connected=false to the master server. This allows the server to update its records and notify other players.

Parameters:

  • virtual_ip (uint32_t)
void LeaseRenewalLoop()

Lease renewal thread function.

Lease renewal thread main loop.@gdb{tag=“P2P:NAT”, msg=“Lease renewal loop”}Runs continuously until m_lease_thread_running is set to false. Wakes up every PORT_LEASE_RENEW seconds (50s) to refresh the UPnP port mapping.Why 50 seconds?The lease duration is 60 seconds. By renewing at 50 seconds, we have a 10-second safety margin. If renewal fails, we have time to retry before the mapping expires.This matches Ryujinx’s timing exactly for compatibility.

void StartLeaseRenewal()

Start lease renewal background thread.

Start the UPnP lease renewal background thread.@gdb{tag=“P2P:NAT”, msg=“Starting lease renewal”}Internal helper called after successful. Creates a low-priority thread that periodically refreshes the UPnP port mapping.