Skip to content

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

Draft
githubawn wants to merge 6 commits into
TheSuperHackers:mainfrom
githubawn:refactor/remove-refresh-nat
Draft

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

Conversation

@githubawn

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 code works:

  1. Automated In-Engine RAM Lifecycle: NAT state classification (m_behavior) is managed 100% in memory within FirewallHelperClass.
  2. Transparent Background Probing: Non-blocking STUN probing runs in the background during online lobby entry (WOLWelcomeMenu), caching the classified result in RAM for the remainder of the game session.
  3. Self-Healing Re-Detection: If a peer connection times out (NAT.cpp) or the user selects a new IP address in Options (OptionPreferences.cpp), TheFirewallHelper->flagNeedToRefresh(TRUE) resets m_behavior = FIREWALL_TYPE_UNKNOWN in RAM to seamlessly trigger a fresh background probe.
  4. Clean GUI Deprecation: Hides OptionsMenu.wnd:ButtonFirewallRefresh via winHide(TRUE) in C++ without breaking custom or legacy .wnd layout files.

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

Self-healing NAT detection is moved from persisted global/preferences state into the session-owned firewall helper.

  • Restarts asynchronous detection after selected-IP changes and failed NAT negotiation.
  • Retains completed classifications in memory and exposes shared accessors to matchmaking and traversal code.
  • Moves helper ownership to GameSpy setup and teardown and removes completion-time deletion.
  • Removes the manual refresh action and hides its legacy options control in supported GUI builds.

Confidence Score: 4/5

The PR is not yet safe to merge because an IP-triggered refresh can advertise the invalidated previous interface's NAT behavior and allocation delta.

The replacement probe is asynchronous, but its accessors fall back to values captured from the interface whose change initiated the refresh, allowing matchmaking and traversal to act on incorrect NAT data and time out.

Files Needing Attention: Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Introduces the in-memory detection lifecycle and fallback accessors, but the fallback exposes an invalidated previous-interface classification during refresh.
Core/GameEngine/Source/Common/OptionPreferences.cpp Routes LAN and online IP changes through a shared setter that restarts NAT detection when the effective value changes.
Core/GameEngine/Source/GameNetwork/NAT.cpp Preserves helper ownership after negotiation, restarts detection after failure, and consumes the helper-managed port-allocation delta.
Core/GameEngine/Source/GameNetwork/GameSpy/PeerDefs.cpp Aligns firewall-helper ownership with GameSpy setup and teardown.
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp Publishes the best-known in-memory NAT classification when constructing staging-room state.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp Mirrors the staging-room NAT publication change for Zero Hour.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[GameSpy setup] --> B[Create FirewallHelper]
    B --> C[Asynchronous NAT probe]
    C --> D[Completed in-memory classification]
    E[Selected IP changes or NAT fails] --> F[Restart probe]
    D --> F
    F --> C
    D --> G[Matchmaking and NAT traversal]
    F -. previous-interface fallback .-> G
    H[GameSpy teardown] --> I[Delete FirewallHelper]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp:80-83
**Refresh publishes stale NAT state**

When the selected IP changes after an earlier probe completed, this fallback publishes the previous interface's NAT behavior while replacement detection is still running. The matching delta fallback is then consumed by NAT port prediction, causing peers to use the wrong traversal strategy or probe the wrong port until connection timeout.

Reviews (8): Last reviewed commit: "refactor(network): Put Refresh NAT butto..." | Re-trigger Greptile

Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp
@githubawn
githubawn force-pushed the refactor/remove-refresh-nat branch from c5806f1 to 79e5c8e Compare July 23, 2026 22:14
### 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 code works:

1. **Automated In-Engine RAM Lifecycle**: NAT state classification (`m_behavior`) is managed 100% in memory within `FirewallHelperClass`.
2. **Transparent Background Probing**: Non-blocking STUN probing runs in the background during online lobby entry (`WOLWelcomeMenu`), caching the classified result in RAM for the remainder of the game session.
3. **Self-Healing Re-Detection**: If a peer connection times out (`NAT.cpp`) or the user selects a new IP address in Options (`OptionPreferences.cpp`), `TheFirewallHelper->flagNeedToRefresh(TRUE)` resets `m_behavior = FIREWALL_TYPE_UNKNOWN` in RAM to seamlessly trigger a fresh background probe.
4. **Clean GUI Deprecation**: Hides `OptionsMenu.wnd:ButtonFirewallRefresh` via `winHide(TRUE)` in C++ without breaking custom or legacy `.wnd` layout files.

### 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 force-pushed the refactor/remove-refresh-nat branch 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 Outdated
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
placed nat detection earlier in the online Code
some more cleanup
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp

@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
checkSendDelay = TheWindowManager->winGetWindowFromId( nullptr, checkSendDelayID);
buttonFirewallRefreshID = TheNameKeyGenerator->nameToKey( "OptionsMenu.wnd:ButtonFirewallRefresh" );
buttonFirewallRefresh = TheWindowManager->winGetWindowFromId( nullptr, buttonFirewallRefreshID);
// TheSuperHackers @info 25/07/2026 Refresh button has been hidden, we have migrated this to self-healing

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Please avoid using "we", "i". Ideally comments are person-less.


if (TheFirewallHelper != nullptr)
{
TheFirewallHelper->behaviorDetectionUpdate();

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 does the WOL Login need a firewall helper update? Isn't the firewall helper needed for peer to peer connections?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is to move the firewall NAT detection as early as possible to heighten the chance it is known once you join a match.

(*this)[key] = IP;

if (TheFirewallHelper != nullptr)
TheFirewallHelper->flagNeedToRefresh(TRUE);

@xezon xezon Jul 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It looks like this class is not intended to set state elsewhere in the program. Can this be implemented differently to keep the focus of this class for writing options?


Bool isBehaviorDetectionComplete() {return(m_currentState == DETECTIONSTATE_DONE);}
FirewallBehaviorType getLastFirewallBehavior() {return(m_lastBehavior);}
Short getLastSourcePortAllocationDelta() {return((Short)m_lastSourcePortAllocationDelta);}

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 all be const


TheGameSpyConfig = GameSpyConfigInterface::create(configBuffer);

if (TheFirewallHelper == nullptr)

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 this condition ever be false at this point? If not, can it be replaced with an assert?

return helper;
}

FirewallHelperClass::FirewallBehaviorType getBestKnownFirewallBehavior()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All it does is interact with FirewallHelperClass. Can this be member of FirewallHelperClass?

return FirewallHelperClass::FIREWALL_TYPE_UNKNOWN;
}

Short getBestKnownSourcePortAllocationDelta()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All it does is interact with FirewallHelperClass. Can this be member of FirewallHelperClass?

m_lastBehavior = (FirewallBehaviorType) ConfigINI.Get_Int("MultiPlayer", "FirewallSettings", FIREWALL_UNKNOWN);
m_lastSourcePortAllocationDelta = ConfigINI.Get_Int("MultiPlayer", "FirewallDelta", 1);
#endif //(0)
if (flag) {

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 flag argument looks very useless. Can be removed. "Munkee" text above needs updating then.

@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 on lines +80 to +83
if (TheFirewallHelper->getLastFirewallBehavior() != FirewallHelperClass::FIREWALL_TYPE_UNKNOWN)
{
return TheFirewallHelper->getLastFirewallBehavior();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Refresh publishes stale NAT state

When the selected IP changes after an earlier probe completed, this fallback publishes the previous interface's NAT behavior while replacement detection is still running. The matching delta fallback is then consumed by NAT port prediction, causing peers to use the wrong traversal strategy or probe the wrong port until connection timeout.

Knowledge Base Used: GameNetwork: Multiplayer Networking

Prompt To Fix With AI
This is a comment left during a code review.
Path: Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp
Line: 80-83

Comment:
**Refresh publishes stale NAT state**

When the selected IP changes after an earlier probe completed, this fallback publishes the previous interface's NAT behavior while replacement detection is still running. The matching delta fallback is then consumed by NAT port prediction, causing peers to use the wrong traversal strategy or probe the wrong port until connection timeout.

**Knowledge Base Used:** [GameNetwork: Multiplayer Networking](https://app.greptile.com/thesuperhackers/-/custom-context/knowledge-base/thesuperhackers/generalsgamecode/-/docs/gamenetwork-multiplayer.md)

How can I resolve this? If you propose a fix, please make it concise.

@githubawn
githubawn marked this pull request as draft July 26, 2026 17:08
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