Lobby: honour transport "ice" launches (same-NAT peers no longer dial themselves)

The lobby server starts a 2-player room the host relay cannot carry on ICE
(recomp-net-server start_transport, launch transport "ice"). This client
knew only "host" and the legacy server relay. It inferred a relay from
equal host/guest endpoints -- which is exactly what two peers behind one NAT
advertise -- and each peer dialled its own public address: the match stayed
at "linking" forever.

- psx_lobby_launch_transport.h: one pure decision (host / ice / relay /
  direct) with a unit test covering ICE, same-NAT endpoints, host relay and
  the legacy relay cases.
- PsxLobbyJoinInfo.transport_ice; the launch handler records it and never
  turns it into a relay; launch fill and resolve_use_ice start ICE p2p
  (signalled over the lobby) for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-09 18:22:15 -07:00
parent 3434424c0e
commit d9ea46249f
7 changed files with 130 additions and 14 deletions
+3
View File
@@ -53,6 +53,9 @@ if(BUILD_TESTING)
target_include_directories(netplay_sim_pad_cache_test PRIVATE include)
target_compile_definitions(netplay_sim_pad_cache_test PRIVATE PSX_MAX_PLAYERS=4)
add_test(NAME netplay_sim_pad_cache_test COMMAND netplay_sim_pad_cache_test)
add_executable(lobby_launch_transport_test tests/test_lobby_launch_transport.c)
target_include_directories(lobby_launch_transport_test PRIVATE include)
add_test(NAME lobby_launch_transport_test COMMAND lobby_launch_transport_test)
add_executable(netplay_local_view_test tests/test_netplay_local_view.c)
target_include_directories(netplay_local_view_test PRIVATE include)
add_test(NAME netplay_local_view_test COMMAND netplay_local_view_test)
+4
View File
@@ -272,6 +272,10 @@ typedef struct PsxLobbyJoinInfo {
* force_input_relay is 0. The host binds bind_hostport and accepts the
* guest (2 seats) or hubs (3+). */
int transport_host;
/* Launch: transport "ice" -- a 2-player room started on ICE
* (peer-to-peer, signalled over the lobby). No endpoint is dialled and
* force_input_relay is 0 (psx_lobby_launch_transport.h). */
int transport_ice;
/* A server code (need_password | bad_password | …) or, from
* psx_lobby_set_last_error, one sentence the launcher shows as is. */
char last_error[192];
@@ -0,0 +1,59 @@
/* psx_lobby_launch_transport.h - how a lobby `launch` is carried.
*
* The lobby server picks the transport at `start` (recomp-net-server
* start_transport, WS_LOBBY.md "Host relay"):
* "host" -- the host carries the match on host_endpoint; guests dial it.
* "ice" -- a 2-player room the host relay cannot carry starts on ICE:
* peer-to-peer over STUN/TURN, signalled over the lobby socket.
* The endpoints are not dialled at all.
* a relay_endpoint, or equal host/guest endpoints from a server that
* rewrote both to its relay advertise address -- legacy server SFU.
*
* The last rule used to win over "ice": two peers behind one NAT advertise
* the same public endpoint, so an ICE launch between them looked like a relay
* and each peer dialled its own address -- the match never linked. */
#ifndef PSX_LOBBY_LAUNCH_TRANSPORT_H
#define PSX_LOBBY_LAUNCH_TRANSPORT_H
#include <stdlib.h>
#include <string.h>
enum {
PSX_LAUNCH_DIRECT = 0, /* dial the peer endpoint (legacy p2p UDP) */
PSX_LAUNCH_HOST = 1, /* host relay: host binds, guests dial host_endpoint */
PSX_LAUNCH_ICE = 2, /* ICE peer-to-peer, signalled over the lobby */
PSX_LAUNCH_RELAY = 3 /* server input relay (SFU) */
};
static inline int psx_lobby_endpoint_usable(const char *ep)
{
const char *colon;
if (!ep || !ep[0]) return 0;
colon = strrchr(ep, ':');
if (!colon || !colon[1]) return 0;
return strtoul(colon + 1, NULL, 10) != 0;
}
/* transport: the launch's "transport" field ("" when absent).
* relay_endpoint: the launch's relay_endpoint ("" when absent).
* caps_force_relay: the host's match_caps asked for the server relay.
* my_bind: this peer's own advertised bind ("" when unknown). */
static inline int psx_lobby_launch_transport(const char *transport,
const char *relay_endpoint,
int caps_force_relay,
const char *host_ep,
const char *guest_ep,
const char *my_bind)
{
if (transport && strcmp(transport, "host") == 0) return PSX_LAUNCH_HOST;
if (transport && strcmp(transport, "ice") == 0) return PSX_LAUNCH_ICE;
if (psx_lobby_endpoint_usable(relay_endpoint) || caps_force_relay)
return PSX_LAUNCH_RELAY;
if (psx_lobby_endpoint_usable(host_ep) && psx_lobby_endpoint_usable(guest_ep) &&
strcmp(host_ep, guest_ep) == 0 &&
(!my_bind || !my_bind[0] || strcmp(host_ep, my_bind) != 0))
return PSX_LAUNCH_RELAY;
return PSX_LAUNCH_DIRECT;
}
#endif
+3 -2
View File
@@ -15236,13 +15236,14 @@ namespace {
* §108's "always SFU"). Prefer caps.force_input_relay from the
* relay_endpoint rewrite; also infer when host==guest advertise. */
out->force_input_relay =
(g_lnch_hosting_lan || g_lnch_joined_lan || ji->transport_host)
(g_lnch_hosting_lan || g_lnch_joined_lan || ji->transport_host || ji->transport_ice)
? 0
: (caps->force_input_relay ? 1 : 0);
#if defined(RECOMP_LAUNCHER_HAS_HOST_RELAY)
out->transport_host = ji->transport_host ? 1 : 0;
#endif
if (!out->force_input_relay && !ji->transport_host && !g_lnch_hosting_lan &&
if (!out->force_input_relay && !ji->transport_host && !ji->transport_ice &&
!g_lnch_hosting_lan &&
!g_lnch_joined_lan && ji->host_endpoint[0] &&
ji->guest_endpoint[0] &&
std::strcmp(ji->host_endpoint, ji->guest_endpoint) == 0) {
+17 -12
View File
@@ -1,4 +1,5 @@
#include "psx_lobby_client.h"
#include "psx_lobby_launch_transport.h"
#include "netplay_bios_settle.h"
#include <ctype.h>
@@ -1910,16 +1911,12 @@ static void copy_first_usable_endpoint(char *dst, size_t dst_len, const char *a,
static int using_server_input_relay(const PsxLobbyJoinInfo *j)
{
if (g_lc.match_caps.valid && g_lc.match_caps.force_input_relay)
return 1;
/* Server rewrote both endpoints to the same relay advertise address. */
if (j && j->host_endpoint[0] && j->guest_endpoint[0] &&
!endpoint_port_is_zero(j->host_endpoint) &&
!endpoint_port_is_zero(j->guest_endpoint) &&
strcmp(j->host_endpoint, j->guest_endpoint) == 0 &&
(!g_lc.my_bind[0] || strcmp(j->host_endpoint, g_lc.my_bind) != 0))
return 1;
return 0;
if (!j)
return g_lc.match_caps.valid && g_lc.match_caps.force_input_relay;
return psx_lobby_launch_transport(j->transport_ice ? "ice" : j->transport_host ? "host" : "",
"", g_lc.match_caps.valid && g_lc.match_caps.force_input_relay,
j->host_endpoint, j->guest_endpoint,
g_lc.my_bind) == PSX_LAUNCH_RELAY;
}
static void fill_peer_bind_from_join(void)
@@ -3275,6 +3272,7 @@ static void handle_server_json(const char *json)
transport_kind[0] = '\0';
json_get_str(json, "transport", transport_kind, sizeof(transport_kind));
g_lc.join.transport_host = strcmp(transport_kind, "host") == 0 ? 1 : 0;
g_lc.join.transport_ice = strcmp(transport_kind, "ice") == 0 ? 1 : 0;
rnet_host_relay_release_port(g_host_relay);
g_lc.join.player_count = json_get_int(json, "player_count", g_lc.join.player_count);
g_lc.join.max_slots = json_get_int(json, "max_slots", g_lc.join.max_slots);
@@ -3283,7 +3281,10 @@ static void handle_server_json(const char *json)
/* Prefer explicit relay_endpoint when the server opened input relay.
* Apply after caps ingest: omitted force_input_relay must not leave
* hosts on the hub path while guests dial the relay. */
if (relay_endpoint[0] && !endpoint_port_is_zero(relay_endpoint)) {
if (g_lc.join.transport_ice) {
/* ICE: nothing is dialled; a stale relay ask must not survive. */
if (g_lc.match_caps.valid) g_lc.match_caps.force_input_relay = 0;
} else if (relay_endpoint[0] && !endpoint_port_is_zero(relay_endpoint)) {
char relay_raw[PSX_LOBBY_ENDPOINT_LEN];
strncpy(relay_raw, relay_endpoint, sizeof(relay_raw) - 1);
relay_raw[sizeof(relay_raw) - 1] = '\0';
@@ -3325,7 +3326,11 @@ static void handle_server_json(const char *json)
json_get_str(json, "transport", transport, sizeof(transport));
if (strcmp(transport, "ice_p2p") == 0)
ice_p2p = 1;
if (force_relay) {
if (g_lc.join.transport_ice) {
fprintf(stderr, "psx_lobby: launch transport=ice (peer-to-peer, "
"signalled over the lobby)\n");
fflush(stderr);
} else if (force_relay) {
if (peer_bad) {
strncpy(g_lc.join.last_error, "missing_endpoints",
sizeof(g_lc.join.last_error) - 1);
+13
View File
@@ -3800,6 +3800,19 @@ static int resolve_use_ice(const PsxNetplayConfig *cfg)
fflush(stderr);
return 0;
}
#if defined(PSX_HAS_LOBBY_CLIENT) && defined(RNET_ENABLE_ICE)
/* The server started this room on ICE (2 players the host relay cannot
* carry): peer-to-peer over STUN/TURN, signalled over the lobby socket.
* Same-NAT peers advertise equal endpoints; that is not a relay. */
if (in_motk_room && !cfg->force_input_relay) {
const PsxLobbyJoinInfo *ji = psx_lobby_join_info();
if (ji && ji->transport_ice) {
fprintf(stderr, "psx_netplay: lobby launch transport=ice — ICE peer-to-peer\n");
fflush(stderr);
return 1;
}
}
#endif
if (cfg->force_input_relay || in_motk_room) {
if (!cfg->peer_hostport || !cfg->peer_hostport[0]) {
fprintf(stderr,
@@ -0,0 +1,31 @@
#include "psx_lobby_launch_transport.h"
#undef NDEBUG
#include <assert.h>
int main(void)
{
/* ICE launch between two peers behind one NAT: same public endpoint on
* both sides must stay ICE, not turn into a relay to ourselves. */
assert(psx_lobby_launch_transport("ice", "", 0, "136.25.29.198:7778",
"136.25.29.198:7778", "") == PSX_LAUNCH_ICE);
assert(psx_lobby_launch_transport("ice", "", 0, "1.2.3.4:7777", "5.6.7.8:7777", "")
== PSX_LAUNCH_ICE);
/* Host relay is unchanged, even with equal endpoints. */
assert(psx_lobby_launch_transport("host", "", 0, "1.2.3.4:7777", "1.2.3.4:7777", "")
== PSX_LAUNCH_HOST);
/* Legacy server relay: explicit relay_endpoint, caps, or equal rewritten
* endpoints with no transport field. */
assert(psx_lobby_launch_transport("", "9.9.9.9:8777", 0, "", "", "") == PSX_LAUNCH_RELAY);
assert(psx_lobby_launch_transport("", "", 1, "1.2.3.4:7777", "5.6.7.8:7777", "")
== PSX_LAUNCH_RELAY);
assert(psx_lobby_launch_transport("", "", 0, "9.9.9.9:8777", "9.9.9.9:8777", "")
== PSX_LAUNCH_RELAY);
/* ...but not our own bind echoed back, and not unusable ports. */
assert(psx_lobby_launch_transport("", "", 0, "1.2.3.4:7777", "1.2.3.4:7777",
"1.2.3.4:7777") == PSX_LAUNCH_DIRECT);
assert(psx_lobby_launch_transport("", "", 0, "1.2.3.4:0", "1.2.3.4:0", "")
== PSX_LAUNCH_DIRECT);
assert(psx_lobby_launch_transport("", "", 0, "1.2.3.4:7777", "5.6.7.8:7777", "")
== PSX_LAUNCH_DIRECT);
return 0;
}