feat(matchmaking): let trusted servers override advertised address for NAT/tunnels - #249
feat(matchmaking): let trusted servers override advertised address for NAT/tunnels#249itpick wants to merge 3 commits into
Conversation
…r NAT/tunnels
Game servers register via POST /ut/api/matchmaking/session, and
CreateGameServer unconditionally overwrites the declared serverAddress with
the request's source IP. For a server behind NAT or a tunnel (playit.gg,
Cloudflare Spectrum, home servers behind CGNAT), that source IP is the egress
address, not the ingress address players must connect to, so the browser lists
an unreachable server.
For TRUSTED servers only, allow an operator to opt in (both mechanisms default
OFF, so upstream behavior is unchanged) to advertising the real public address
via a new top-level "Trusted" config section:
- Trusted:AllowDeclaredAddress (bool) -- honor the serverAddress the server
declared in its request body, when it is a valid non-"0.0.0.0" IP.
- Trusted:AddressOverrides (dict sourceIP -> publicIP) -- map the request
source IP to a configured public IP.
The declared value is captured before the source-IP overwrite. An override is
logged at Info level (server id, from -> to, reason). Untrusted-server
behavior is intentionally left unchanged.
There was a problem hiding this comment.
Pull request overview
Adds configuration-gated support for trusted UT4 dedicated servers to advertise a different serverAddress than the request source IP, enabling NAT/tunnel deployments to show a reachable ingress address while keeping untrusted behavior unchanged.
Changes:
- Registers a new
Trustedconfiguration section bound toTrustedGameServerSettings. - Captures the server-declared address before overwriting with source IP, and applies trusted-only overrides (declared address or source→public mapping).
- Introduces
TrustedGameServerSettingsto model the opt-in override mechanisms.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| UT4MasterServer/Program.cs | Binds new top-level Trusted settings section into DI options. |
| UT4MasterServer/Controllers/UT/MatchmakingController.cs | Applies trusted-only server address override logic during session registration. |
| UT4MasterServer.Models/Settings/TrustedGameServerSettings.cs | Defines settings for allowing declared addresses and source-IP→public-IP overrides. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- trim declared/mapped addresses so a stray-whitespace value (e.g. " 0.0.0.0") cannot slip past the "0.0.0.0" guard while IPAddress.TryParse still accepts it - validate the config-driven AddressOverrides value the same way as the declared address (non-empty, != 0.0.0.0, parseable IP) so a misconfigured entry cannot make the master advertise an invalid/unreachable address
|
@timiimit - please merge and update master server with this so that I can connect my hub. Thanks! |
|
@Saibamen - would be awesome if you can see this one getting merged in! Thanks :) |
|
@timiimit - you alive? |
… IPv6 key (Copilot review) TryGetValue could throw if AddressOverrides is bound null (JSON "AddressOverrides": null); and an IPv4-mapped IPv6 source (::ffff:1.2.3.4) never matched plain-IPv4 config keys. Guard the dictionary and look it up by a normalized IPv4 key.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (2)
UT4MasterServer/Controllers/UT/MatchmakingController.cs:134
- The config-driven override validates against "0.0.0.0" but would still allow the IPv6 unspecified address ("::") (or any value that parses to IPAddress.Any/IPAddress.IPv6Any). It’s safer to reject unspecified addresses based on the parsed IP rather than string matching.
&& addressOverrides.TryGetValue(overrideKey, out var mappedAddress)
&& mappedAddress?.Trim() is { Length: > 0 } trimmedMapped
&& trimmedMapped != "0.0.0.0"
&& IPAddress.TryParse(trimmedMapped, out _))
{
UT4MasterServer/Controllers/UT/MatchmakingController.cs:118
- The declared-address override blocks only the IPv4 unspecified address ("0.0.0.0") but would still accept the IPv6 unspecified address ("::") (and any other value that parses to IPAddress.Any/IPAddress.IPv6Any). That would cause the master to advertise an explicitly non-routable address even when overrides are enabled.
This issue also appears on line 130 of the same file.
if (overrides.AllowDeclaredAddress
&& !string.IsNullOrWhiteSpace(declaredAddress)
&& declaredAddress != "0.0.0.0"
&& IPAddress.TryParse(declaredAddress, out _))
{
Problem
UT4 dedicated servers register with the master via
POST /ut/api/matchmaking/session(MatchmakingController.CreateGameServer). The handler unconditionally overwrites the server's declaredserverAddresswith the source IP of the registration request:That is correct for a server on a routable public IP. But for a server behind NAT or a tunnel — playit.gg, Cloudflare Spectrum, or a home server behind CGNAT — the source IP the master sees is the server's egress address, which is not the ingress address players must connect to. The result is that the server browser lists an address nobody can reach.
Change
For trusted servers only (
trust != Untrusted, using the existingTrustedGameServerService), an operator can opt in to advertising the real public address. Both mechanisms are config-gated and default OFF, so upstream behavior is unchanged unless explicitly enabled. Untrusted-server behavior is left exactly as-is.A new top-level
Trustedconfig section (bound viaIOptions<TrustedGameServerSettings>, matching the existing settings pattern) provides two opt-ins:Trusted:AllowDeclaredAddress(bool) — when true, honor theserverAddressthe server declared in its request body, provided it is a valid, non-empty, non-"0.0.0.0"IP. The declared value is captured before the source-IP overwrite.Trusted:AddressOverrides(dictionarysourceIP -> publicIP) — otherwise, if the request's source IP is a key in this map, advertise the mapped public IP instead.When an override is applied, it is logged at Info level (server id, from → to, and reason).
Example config (environment variables)
Notes
ServerAddressfor trusted servers can differ.dotnet publish -c Release(the repo Dockerfile) — compiles clean.🤖 Generated with Claude Code