feat(network): Self-healing NAT state - #3010
Conversation
|
| 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]
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
c5806f1 to
79e5c8e
Compare
### 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`.
79e5c8e to
8113c09
Compare
placed nat detection earlier in the online Code some more cleanup
xezon
left a comment
There was a problem hiding this comment.
How can this change be tested? Did you test it? Does it work?
| 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 |
There was a problem hiding this comment.
Please avoid using "we", "i". Ideally comments are person-less.
|
|
||
| if (TheFirewallHelper != nullptr) | ||
| { | ||
| TheFirewallHelper->behaviorDetectionUpdate(); |
There was a problem hiding this comment.
Why does the WOL Login need a firewall helper update? Isn't the firewall helper needed for peer to peer connections?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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);} |
|
|
||
| TheGameSpyConfig = GameSpyConfigInterface::create(configBuffer); | ||
|
|
||
| if (TheFirewallHelper == nullptr) |
There was a problem hiding this comment.
Can this condition ever be false at this point? If not, can it be replaced with an assert?
| return helper; | ||
| } | ||
|
|
||
| FirewallHelperClass::FirewallBehaviorType getBestKnownFirewallBehavior() |
There was a problem hiding this comment.
All it does is interact with FirewallHelperClass. Can this be member of FirewallHelperClass?
| return FirewallHelperClass::FIREWALL_TYPE_UNKNOWN; | ||
| } | ||
|
|
||
| Short getBestKnownSourcePortAllocationDelta() |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
This flag argument looks very useless. Can be removed. "Munkee" text above needs updating then.
| if (TheFirewallHelper->getLastFirewallBehavior() != FirewallHelperClass::FIREWALL_TYPE_UNKNOWN) | ||
| { | ||
| return TheFirewallHelper->getLastFirewallBehavior(); | ||
| } |
There was a problem hiding this 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
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.
What this code did:
The
ButtonFirewallRefreshbutton inOptionsMenu.cppandFirewallNeedToRefreshinFirewallHelper.cpp/OptionPreferences.cpp:FirewallNeedToRefreshandLastFirewallIPboolean flags to/fromOptions.ini.GlobalData(TheWritableGlobalData->m_firewallBehavior) with disk-persisted firewall state across process restarts.How the new code works:
m_behavior) is managed 100% in memory withinFirewallHelperClass.WOLWelcomeMenu), caching the classified result in RAM for the remainder of the game session.NAT.cpp) or the user selects a new IP address in Options (OptionPreferences.cpp),TheFirewallHelper->flagNeedToRefresh(TRUE)resetsm_behavior = FIREWALL_TYPE_UNKNOWNin RAM to seamlessly trigger a fresh background probe.OptionsMenu.wnd:ButtonFirewallRefreshviawinHide(TRUE)in C++ without breaking custom or legacy.wndlayout files.Background & Reason for Removal:
FirewallNeedToRefreshandLastFirewallIPfromOptions.ini, preventing stale or corrupted flags from persisting across game crashes or process restarts.FirewallHelperClassin memory, removing direct mutations toTheWritableGlobalData->m_firewallBehavior.