Skip to content

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

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

feat(network): Self-healing NAT state#3010
githubawn wants to merge 9 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

This PR replaces persisted firewall state with a session-owned background NAT detector and updates multiplayer setup to consume its in-memory classification.

Confidence Score: 4/5

The PR is not yet safe to merge because peers can retain an UNKNOWN NAT classification even after the local attach-time refresh obtains the completed result.

The local traversal fallback does not update or redistribute NAT behavior that was published before detection completed, so remote peers can select the wrong port strategy and fail connection establishment.

Files Needing Attention: Core/GameEngine/Source/GameNetwork/NAT.cpp and the staging/quick-match NAT publication paths

Important Files Changed

Filename Overview
Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Moves firewall classification and restart state into the helper and fully resets active probe resources before redetection.
Core/GameEngine/Source/GameNetwork/NAT.cpp Restarts detection after negotiation failures and refreshes local traversal behavior, but the refresh does not correct NAT state already distributed to peers.
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp Publishes the helper's best-known NAT behavior when entering a staging room.
Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp Supplies the helper's best-known NAT behavior to quick matchmaking.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp Mirrors the Generals staging-room NAT publication changes for Zero Hour.
GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp Mirrors the Generals quick-match NAT publication changes for Zero Hour.

Sequence Diagram

sequenceDiagram
    participant Probe as FirewallHelper
    participant Lobby as GameSpy Lobby
    participant Peer as Remote Peer
    participant NAT as NAT Traversal
    Probe->>Probe: Start background detection
    Lobby->>Probe: Request best-known behavior
    Probe-->>Lobby: Classification or UNKNOWN
    Lobby->>Peer: Distribute slot NAT values
    NAT->>Probe: Refresh local behavior at traversal
    NAT->>NAT: Use completed local classification
    Peer->>Peer: Retain distributed slot classification
Loading
Prompt To Fix All With AI
### Issue 1
Core/GameEngine/Source/GameNetwork/NAT.cpp:600-610
**Remote peers retain UNKNOWN NAT**

When a player enters staging or quick match before firewall detection completes, the lobby distributes UNKNOWN as that player's NAT behavior. This fallback refreshes only the local traversal logic and does not update the slot state already distributed to remote peers, causing them to use the raw source port and time out behind a port-mangling NAT.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (12): Last reviewed commit: "review feedback" | Re-trigger Greptile

Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp Outdated
@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 Outdated
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 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
Comment thread Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp
@githubawn

Copy link
Copy Markdown
Author

@greptile

Comment on lines +600 to +610
FirewallHelperClass::FirewallBehaviorType NAT::getLocalNATBehavior() {
FirewallHelperClass::FirewallBehaviorType published =
m_slotList[m_connectionNodes[m_localNodeNumber].m_slotIndex]->getNATBehavior();

FirewallHelperClass::FirewallBehaviorType detected = FirewallHelperClass::getBestKnownFirewallBehavior();
if (detected != FirewallHelperClass::FIREWALL_TYPE_UNKNOWN) {
return detected;
}

return published;
}

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 Remote peers retain UNKNOWN NAT

When a player enters staging or quick match before firewall detection completes, the lobby distributes UNKNOWN as that player's NAT behavior. This fallback refreshes only the local traversal logic and does not update the slot state already distributed to remote peers, causing them to use the raw source port and time out behind a port-mangling NAT.

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/NAT.cpp
Line: 600-610

Comment:
**Remote peers retain UNKNOWN NAT**

When a player enters staging or quick match before firewall detection completes, the lobby distributes UNKNOWN as that player's NAT behavior. This fallback refreshes only the local traversal logic and does not update the slot state already distributed to remote peers, causing them to use the raw source port and time out behind a port-mangling NAT.

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

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

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