ams::mitm::p2p::P2pProxyServer
ams::mitm::p2p::P2pProxyServer
Section titled “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
Members
Section titled “Members”PRIVATE_PORT_BASE
Section titled “PRIVATE_PORT_BASE”Type: constexpr uint16_t
PRIVATE_PORT_RANGE
Section titled “PRIVATE_PORT_RANGE”Type: constexpr int
PUBLIC_PORT_BASE
Section titled “PUBLIC_PORT_BASE”Type: constexpr uint16_t
PUBLIC_PORT_RANGE
Section titled “PUBLIC_PORT_RANGE”Type: constexpr int
PORT_LEASE_LENGTH
Section titled “PORT_LEASE_LENGTH”Type: constexpr int
PORT_LEASE_RENEW
Section titled “PORT_LEASE_RENEW”Type: constexpr int
AUTH_WAIT_SECONDS
Section titled “AUTH_WAIT_SECONDS”Type: constexpr int
MAX_PLAYERS
Section titled “MAX_PLAYERS”Type: constexpr int
Members
Section titled “Members”m_mutex
Section titled “m_mutex”Type: os::Mutex
m_listen_fd
Section titled “m_listen_fd”Type: int
m_private_port
Section titled “m_private_port”Type: uint16_t
m_public_port
Section titled “m_public_port”Type: uint16_t
m_running
Section titled “m_running”Type: std::atomic< bool >
m_disposed
Section titled “m_disposed”Type: bool
m_accept_thread
Section titled “m_accept_thread”Type: os::ThreadType
m_lease_thread
Section titled “m_lease_thread”Type: os::ThreadType
m_lease_thread_running
Section titled “m_lease_thread_running”Type: bool
m_sessions
Section titled “m_sessions”Type: std::unique_ptr<>
m_session_count
Section titled “m_session_count”Type: int
m_zombie_sessions
Section titled “m_zombie_sessions”Type: std::unique_ptr<>
m_zombie_session_count
Section titled “m_zombie_session_count”Type: int
m_waiting_tokens
Section titled “m_waiting_tokens”Type: ryu_ldn::protocol::ExternalProxyToken
m_waiting_token_count
Section titled “m_waiting_token_count”Type: int
m_token_cv
Section titled “m_token_cv”Type: os::ConditionVariable
m_broadcast_address
Section titled “m_broadcast_address”Type: uint32_t
m_master_callback
Section titled “m_master_callback”Type: MasterSendCallback
m_callback_user_data
Section titled “m_callback_user_data”Type: void *
m_host_virtual_ip
Section titled “m_host_virtual_ip”Type: uint32_t
m_host_data_callback
Section titled “m_host_data_callback”Type: HostProxyDataCallback
m_host_data_user_data
Section titled “m_host_data_user_data”Type: void *
Members
Section titled “Members”MAX_ZOMBIE_SESSIONS
Section titled “MAX_ZOMBIE_SESSIONS”Type: constexpr int
MAX_WAITING_TOKENS
Section titled “MAX_WAITING_TOKENS”Type: constexpr int
Methods
Section titled “Methods”P2pProxyServer
Section titled “P2pProxyServer”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 *)
~P2pProxyServer
Section titled “~P2pProxyServer”void ~P2pProxyServer()Destructor - stops server and cleans up.
Destructor - cleanup all resources.Ensures clean shutdown:
P2pProxyServer
Section titled “P2pProxyServer”void P2pProxyServer(const&)Parameters:
param(const&)
operator=
Section titled “operator=”& 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.
IsRunning
Section titled “IsRunning”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
GetPrivatePort
Section titled “GetPrivatePort”uint16_t GetPrivatePort()Get the private (local) port.
Returns: uint16_t
GetPublicPort
Section titled “GetPublicPort”uint16_t GetPublicPort()Get the public (UPnP) port.
Returns: uint16_t
NatPunch
Section titled “NatPunch”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
ReleaseNatPunch
Section titled “ReleaseNatPunch”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.
AddWaitingToken
Section titled “AddWaitingToken”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&)
TryRegisterUser
Section titled “TryRegisterUser”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
HandleExternalProxyStateChange
Section titled “HandleExternalProxyStateChange”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)
ReapZombieSessions
Section titled “ReapZombieSessions”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:
Configure
Section titled “Configure”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&)
SetHostVirtualIp
Section titled “SetHostVirtualIp”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)
SetHostDataCallback
Section titled “SetHostDataCallback”void SetHostDataCallback(HostProxyDataCallback callback, void * user_data)Register the in-process inbound sink. Pass nullptr to detach.
Parameters:
callback(HostProxyDataCallback)user_data(void *)
BroadcastFromHost
Section titled “BroadcastFromHost”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
HandleProxyData
Section titled “HandleProxyData”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)
HandleProxyConnect
Section titled “HandleProxyConnect”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 &)
HandleProxyConnectReply
Section titled “HandleProxyConnectReply”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 &)
HandleProxyDisconnect
Section titled “HandleProxyDisconnect”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 &)
OnSessionDisconnected
Section titled “OnSessionDisconnected”void OnSessionDisconnected(* session)Called when a session disconnects.
@gdb{tag=“P2P:SESSION”, msg=“Session disconnected event”}
Parameters:
session(*)
Methods
Section titled “Methods”AcceptLoop
Section titled “AcceptLoop”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.
RouteMessage
Section titled “RouteMessage”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)
NotifyMasterDisconnect
Section titled “NotifyMasterDisconnect”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)
LeaseRenewalLoop
Section titled “LeaseRenewalLoop”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.
StartLeaseRenewal
Section titled “StartLeaseRenewal”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.