Skip to content

feat(network): Self-healing NAT state - #3010

Open
githubawn wants to merge 5 commits into
TheSuperHackers:mainfrom
githubawn:refactor/remove-refresh-nat
Open

feat(network): Self-healing NAT state#3010
githubawn wants to merge 5 commits into
TheSuperHackers:mainfrom
githubawn:refactor/remove-refresh-nat

Conversation

@githubawn

@githubawn githubawn commented Jul 23, 2026

Copy link
Copy Markdown

What this code did:

The ButtonFirewallRefresh button in OptionsMenu.cpp and FirewallNeedToRefresh in FirewallHelper.cpp / OptionPreferences.cpp:

  1. Saved/read FirewallNeedToRefresh and LastFirewallIP boolean flags to/from Options.ini.
  2. Required players to manually click a "Refresh NAT" button in the Options GUI when changing network interfaces or encountering P2P negotiation failures.
  3. Contaminated GlobalData (TheWritableGlobalData->m_firewallBehavior) with disk-persisted firewall state across process restarts.

How the new system works:

  1. Fully Asynchronous STUN Probing: The UDP NAT probe is now non-blocking. Players can enter the multiplayer lobby immediately while the probe resolves in the background.
  2. Dynamic State Broadcasting: Introduced republishNATBehaviorIfChanged() to dynamically monitor the background thread. The instant the probe successfully classifies the network, it broadcasts the true NAT behavior over the network via UTM/SL/ packets, updating peer clients seamlessly.
  3. Robust QuickMatch Guards: QuickMatch now explicitly validates that the UDP probe is both complete and successfully classified.
    • If the user clicks "Start" before the probe finishes, they receive a localized "Detecting Network Settings" message and are safely blocked from proceeding with an UNKNOWN state.
    • If the probe fundamentally fails (e.g., Strict NAT or DNS failure), it automatically triggers a fresh detectFirewallBehavior() retry in the background to ensure the session isn't permanently bricked.

Background & Reason for Removal:

  • Obsolete Manual Workaround: Manual NAT refresh buttons are a legacy 2003 workaround; modern network stacks handle NAT re-detection automatically in-engine.
  • Elimination of Disk I/O: Completely removes FirewallNeedToRefresh and LastFirewallIP from Options.ini, preventing stale or corrupted flags from persisting across game crashes or process restarts.
  • Architectural Decoupling: Fully encapsulates STUN probing state inside FirewallHelperClass in memory, removing direct mutations to TheWritableGlobalData->m_firewallBehavior.

@githubawn githubawn changed the title feat(network): make NAT state self-healing feat(network): Self-healing NAT state Jul 23, 2026
@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

Updates NAT detection to use an asynchronous, session-scoped firewall helper.

  • Removes persisted firewall behavior and manual refresh handling.
  • Retains detected NAT state for the GameSpy session and retries failed probes.
  • Republishes completed NAT classifications through staging-room slot updates.
  • Blocks Quick Match submission until classification succeeds.

Confidence Score: 5/5

The PR appears safe to merge with no remaining blocking failures related to the previous review threads.

No blocking failure remains after the revised helper lifecycle, retry behavior, and staging-room NAT republication changes.

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Moves firewall classification into resettable in-memory state with asynchronous detection and retry throttling.
Core/GameEngine/Source/GameNetwork/GameSpy/PeerDefs.cpp Establishes GameSpy-session ownership for the firewall helper.
Core/GameEngine/Source/GameNetwork/NAT.cpp Reuses live firewall state during traversal and restarts detection after negotiation failure.
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp Republishes completed NAT classifications through host-mediated staging-room slot broadcasts.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp Mirrors the staging-room NAT republication flow for Zero Hour.
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp Prevents Quick Match submission until NAT detection completes successfully.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp Mirrors the successful-classification guard for Zero Hour Quick Match.

Sequence Diagram

sequenceDiagram
    participant Menu as Multiplayer Menu
    participant Helper as FirewallHelper
    participant Mangler as STUN/Mangler Service
    participant Host as Staging Host
    participant Peers as Room Peers
    Menu->>Helper: behaviorDetectionUpdate()
    Helper->>Mangler: asynchronous NAT probes
    Mangler-->>Helper: mapped-port responses
    Helper->>Helper: classify NAT behavior
    alt Local player is host
        Menu->>Host: update local slot
        Host->>Peers: broadcast SL/ slot list
    else Local player is client
        Menu->>Host: "send REQ/ NAT=value"
        Host->>Peers: broadcast corrected SL/ slot list
    end
Loading

Reviews (29): Last reviewed commit: "another grepfile round" | Re-trigger Greptile

Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
@githubawn
githubawn force-pushed the refactor/remove-refresh-nat branch 2 times, most recently from 79e5c8e to 8113c09 Compare July 23, 2026 22:18
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp
Comment thread Core/GameEngine/Include/GameNetwork/FirewallHelper.h Outdated
Comment thread Core/GameEngine/Source/Common/OptionPreferences.cpp Outdated
Comment thread Core/GameEngine/Source/Common/OptionPreferences.cpp Outdated
Comment thread Core/GameEngine/Source/Common/OptionPreferences.cpp Outdated
Comment thread Core/GameEngine/Source/Common/OptionPreferences.cpp
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can this change be tested? Did you test it? Does it work?

Comment thread GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp
Comment thread Core/GameEngine/Source/Common/OptionPreferences.cpp Outdated
Comment thread Core/GameEngine/Include/GameNetwork/FirewallHelper.h Outdated
Comment thread Core/GameEngine/Source/GameNetwork/GameSpy/PeerDefs.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
@xezon xezon added Enhancement Is new feature or request Minor Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers Gen Relates to Generals ZH Relates to Zero Hour labels Jul 26, 2026
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
@githubawn
githubawn marked this pull request as draft July 26, 2026 17:08
@githubawn

githubawn commented Jul 29, 2026

Copy link
Copy Markdown
Author

How can this change be tested? Did you test it? Does it work?

Tested this with a Gamespy emulator and it does follow the intended changes.
GO might want to validate this one manually. I have no clue in how many ways this can break.

Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
Comment thread Core/GameEngine/Source/Common/OptionPreferences.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/NAT.cpp Outdated
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
Comment thread Generals/Code/GameEngine/Source/GameNetwork/GameSpy.cpp Outdated
@githubawn
githubawn marked this pull request as ready for review July 30, 2026 17:26
@githubawn
githubawn force-pushed the refactor/remove-refresh-nat branch from e2d7f2a to 0ec4675 Compare July 30, 2026 19:43
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
@xezon

xezon commented Aug 2, 2026

Copy link
Copy Markdown

The bot sure had a lot to say here

@xezon xezon left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is a bit complicated. Needs more Networking eyes 👀

m_packetID++;
m_timeoutStart = timeGetTime();
m_timeoutLength = 4000;
m_timeoutLength = 3000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are timeout values tweaked?

static const Int MAX_NUM_MANGLERS = 4;
static const UnsignedShort MANGLER_PORT = 4321;

static const UnsignedInt BEHAVIOR_DETECTION_WAIT_TIME = 6000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is unused?


static const UnsignedInt BEHAVIOR_DETECTION_WAIT_TIME = 6000;

class FirewallHelperClass {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest add a comment above this class explaining the new things it does now.

TheFirewallHelper->behaviorDetectionUpdate();
republishNATBehaviorIfChanged();

if (TheFirewallHelper->isBehaviorDetectionComplete() && TheFirewallHelper->getFirewallBehavior() == FirewallHelperClass::FIREWALL_TYPE_UNKNOWN)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if detection is complete it will detect firewall behavior again?

else if (key == "NAT")
{
if ((val >= FirewallHelperClass::FIREWALL_TYPE_SIMPLE) &&
if ((val >= FirewallHelperClass::FIREWALL_TYPE_UNKNOWN) &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this changed?

slReq.UTM.isStagingRoom = TRUE;
slReq.id = "SL/";
slReq.options = GameInfoToAsciiString(game).str();
TheGameSpyPeerMessageQueue->addRequest(slReq);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block exists twice in this file. Consolidate?

m_timeoutLength = 0;
m_timeoutStart = 0;


Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style: 1 excess empty line:

req.nick = aName.str();
options.format("NAT=%d", behavior);
req.options = options.str();
TheGameSpyPeerMessageQueue->addRequest(req);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why these publishing events and requests are needed after firewall behavior detection?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Enhancement Is new feature or request Gen Relates to Generals Minor Severity: Minor < Major < Critical < Blocker Network Anything related to network, servers ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants