feat(network): Self-healing NAT state#3010
Conversation
|
| Filename | Overview |
|---|---|
| Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp | Introduces session-owned detection and previous-result fallback, but the initial-probe fallback still exposes UNKNOWN to active matchmaking paths. |
| Core/GameEngine/Source/GameNetwork/NAT.cpp | Restarts classification after negotiation failures and retains the helper, while continuing to interpret UNKNOWN as the non-mangling path. |
| Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp | Publishes the helper's best-known NAT classification during staging-room initialization, including UNKNOWN before the first probe completes. |
| GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLGameSetupMenu.cpp | Mirrors the staging-room publication behavior for Zero Hour, including the initial UNKNOWN window. |
| Generals/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp | Sources quick-match NAT metadata from the session helper, but can submit UNKNOWN during the initial probe. |
| GeneralsMD/Code/GameEngine/Source/GameClient/GUI/GUICallbacks/Menus/WOLQuickMatchMenu.cpp | Mirrors the quick-match helper integration and its initial UNKNOWN publication path. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Enter online lobby] --> B[Create session FirewallHelper]
B --> C[Run background NAT probe]
C --> D{Probe complete?}
D -->|Yes| E[Publish current classification]
D -->|No, previous result exists| F[Publish previous classification]
D -->|No previous result| G[Publish UNKNOWN]
G --> H[NAT treats peer as non-mangling]
H --> I[Peer negotiation may time out]
J[IP change or connection failure] --> K[Restart probe]
K --> C
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:86
**Initial probe still publishes UNKNOWN**
When a player enters game setup or starts quick match before the session's first firewall probe completes, both the current and previous classifications are UNKNOWN, so this fallback publishes UNKNOWN. NAT traversal treats that value as no port mangling and sends the raw source port, causing peer negotiation to time out for players behind NAT.
Reviews (7): Last reviewed commit: "update after greptile feedback" | 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
| } | ||
| } | ||
|
|
||
| return FirewallHelperClass::FIREWALL_TYPE_UNKNOWN; |
There was a problem hiding this comment.
Initial probe still publishes UNKNOWN
When a player enters game setup or starts quick match before the session's first firewall probe completes, both the current and previous classifications are UNKNOWN, so this fallback publishes UNKNOWN. NAT traversal treats that value as no port mangling and sends the raw source port, causing peer negotiation to time out for players behind 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/FirewallHelper.cpp
Line: 86
Comment:
**Initial probe still publishes UNKNOWN**
When a player enters game setup or starts quick match before the session's first firewall probe completes, both the current and previous classifications are UNKNOWN, so this fallback publishes UNKNOWN. NAT traversal treats that value as no port mangling and sends the raw source port, causing peer negotiation to time out for players behind NAT.
**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.
xezon
left a comment
There was a problem hiding this comment.
How can this change be tested? Did you test it? Does it work?
|
|
||
| { "FirewallBehavior", INI::parseInt, nullptr, offsetof( GlobalData, m_firewallBehavior ) }, | ||
| { "FirewallPortOverride", INI::parseInt, nullptr, offsetof( GlobalData, m_firewallPortOverride ) }, | ||
| { "FirewallPortAllocationDelta",INI::parseInt, nullptr, offsetof( GlobalData, m_firewallPortAllocationDelta) }, |
There was a problem hiding this comment.
Maybe we can deprecate them instead of deleting? Otherwise there may be confusion why some GlobalData.ini settings do not exist in code.
| 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.
| // TheSuperHackers @info 25/07/2026 Refresh button has been hidden, we have migrated this to self-healing | ||
| GameWindow *buttonFirewallRefresh = TheWindowManager->winGetWindowFromId(nullptr, NAMEKEY("OptionsMenu.wnd:ButtonFirewallRefresh")); | ||
| if (buttonFirewallRefresh) | ||
| buttonFirewallRefresh->winHide(TRUE); |
|
|
||
| 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?
| (*this)[key] = IP; | ||
|
|
||
| if (TheFirewallHelper != nullptr) | ||
| TheFirewallHelper->flagNeedToRefresh(TRUE); |
There was a problem hiding this comment.
It looks like this class is not intended the 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.
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.