-
Notifications
You must be signed in to change notification settings - Fork 229
feat(network): Self-healing NAT state #3010
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8113c09
0a4ada6
fd146ae
6c50745
67eef47
802dd30
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -45,6 +45,7 @@ | |
| #include "GameLogic/ScriptEngine.h" | ||
|
|
||
| #include "GameNetwork/IPEnumeration.h" | ||
| #include "GameNetwork/FirewallHelper.h" | ||
|
|
||
| OptionPreferences::OptionPreferences() | ||
| { | ||
|
|
@@ -140,16 +141,27 @@ UnsignedInt OptionPreferences::getLANIPAddress() | |
| return TheGlobalData->m_defaultIP; | ||
| } | ||
|
|
||
| void OptionPreferences::setIPAddress(AsciiString key, AsciiString IP) | ||
| { | ||
| if ((*this)[key].compareNoCase(IP) == 0) | ||
| return; | ||
|
|
||
| (*this)[key] = IP; | ||
|
|
||
| if (TheFirewallHelper != nullptr) | ||
| TheFirewallHelper->flagNeedToRefresh(TRUE); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||
| } | ||
|
|
||
| void OptionPreferences::setLANIPAddress(AsciiString IP) | ||
| { | ||
| (*this)["IPAddress"] = IP; | ||
| setIPAddress("IPAddress", IP); | ||
| } | ||
|
|
||
| void OptionPreferences::setLANIPAddress(UnsignedInt IP) | ||
| { | ||
| AsciiString tmp; | ||
| tmp.format("%d.%d.%d.%d", PRINTF_IP_AS_4_INTS(IP)); | ||
| (*this)["IPAddress"] = tmp; | ||
| AsciiString ipString; | ||
| ipString.format("%d.%d.%d.%d", PRINTF_IP_AS_4_INTS(IP)); | ||
| setLANIPAddress(ipString); | ||
| } | ||
|
|
||
| UnsignedInt OptionPreferences::getOnlineIPAddress() | ||
|
|
@@ -170,14 +182,14 @@ UnsignedInt OptionPreferences::getOnlineIPAddress() | |
|
|
||
| void OptionPreferences::setOnlineIPAddress(AsciiString IP) | ||
| { | ||
| (*this)["GameSpyIPAddress"] = IP; | ||
| setIPAddress("GameSpyIPAddress", IP); | ||
| } | ||
|
|
||
| void OptionPreferences::setOnlineIPAddress(UnsignedInt IP) | ||
| { | ||
| AsciiString tmp; | ||
| tmp.format("%d.%d.%d.%d", PRINTF_IP_AS_4_INTS(IP)); | ||
| (*this)["GameSpyIPAddress"] = tmp; | ||
| AsciiString ipString; | ||
| ipString.format("%d.%d.%d.%d", PRINTF_IP_AS_4_INTS(IP)); | ||
| setOnlineIPAddress(ipString); | ||
| } | ||
|
|
||
| Bool OptionPreferences::getArchiveReplaysEnabled() const | ||
|
|
@@ -463,31 +475,6 @@ Bool OptionPreferences::getSendDelay() | |
| return FALSE; | ||
| } | ||
|
|
||
| Int OptionPreferences::getFirewallBehavior() | ||
| { | ||
| OptionPreferences::const_iterator it = find("FirewallBehavior"); | ||
| if (it == end()) | ||
| return TheGlobalData->m_firewallBehavior; | ||
|
|
||
| Int behavior = atoi(it->second.str()); | ||
| if (behavior < 0) | ||
| { | ||
| behavior = 0; | ||
| } | ||
| return behavior; | ||
| } | ||
|
|
||
| Short OptionPreferences::getFirewallPortAllocationDelta() | ||
| { | ||
| OptionPreferences::const_iterator it = find("FirewallPortAllocationDelta"); | ||
| if (it == end()) { | ||
| return TheGlobalData->m_firewallPortAllocationDelta; | ||
| } | ||
|
|
||
| Short delta = atoi(it->second.str()); | ||
| return delta; | ||
| } | ||
|
|
||
| UnsignedShort OptionPreferences::getFirewallPortOverride() | ||
| { | ||
| OptionPreferences::const_iterator it = find("FirewallPortOverride"); | ||
|
|
@@ -501,21 +488,6 @@ UnsignedShort OptionPreferences::getFirewallPortOverride() | |
| return portOverride; | ||
| } | ||
|
|
||
| Bool OptionPreferences::getFirewallNeedToRefresh() | ||
| { | ||
| OptionPreferences::const_iterator it = find("FirewallNeedToRefresh"); | ||
| if (it == end()) { | ||
| return FALSE; | ||
| } | ||
|
|
||
| Bool retval = FALSE; | ||
| AsciiString str = it->second; | ||
| if (str.compareNoCase("TRUE") == 0) { | ||
| retval = TRUE; | ||
| } | ||
| return retval; | ||
| } | ||
|
|
||
| AsciiString OptionPreferences::getPreferred3DProvider() | ||
| { | ||
| OptionPreferences::const_iterator it = find("3DAudioProvider"); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,14 +54,55 @@ | |
| #include "GameNetwork/NAT.h" | ||
| #include "GameNetwork/udp.h" | ||
| #include "GameNetwork/NetworkDefs.h" | ||
| #include "GameNetwork/IPEnumeration.h" | ||
| #include "GameNetwork/GameSpy/GSConfig.h" | ||
|
|
||
|
|
||
| FirewallHelperClass *TheFirewallHelper = nullptr; | ||
|
|
||
| FirewallHelperClass * createFirewallHelper() | ||
| { | ||
| return NEW FirewallHelperClass(); | ||
| FirewallHelperClass *helper = NEW FirewallHelperClass(); | ||
|
githubawn marked this conversation as resolved.
|
||
| helper->detectFirewallBehavior(); | ||
| return helper; | ||
| } | ||
|
|
||
| FirewallHelperClass::FirewallBehaviorType getBestKnownFirewallBehavior() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All it does is interact with |
||
| { | ||
| if (TheFirewallHelper != nullptr) | ||
| { | ||
| if (TheFirewallHelper->isBehaviorDetectionComplete() | ||
| && TheFirewallHelper->getFirewallBehavior() != FirewallHelperClass::FIREWALL_TYPE_UNKNOWN) | ||
| { | ||
| return TheFirewallHelper->getFirewallBehavior(); | ||
| } | ||
|
|
||
| if (TheFirewallHelper->getLastFirewallBehavior() != FirewallHelperClass::FIREWALL_TYPE_UNKNOWN) | ||
| { | ||
| return TheFirewallHelper->getLastFirewallBehavior(); | ||
| } | ||
|
Comment on lines
+80
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 AIThis 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. |
||
| } | ||
|
|
||
| return FirewallHelperClass::FIREWALL_TYPE_UNKNOWN; | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| } | ||
|
|
||
| Short getBestKnownSourcePortAllocationDelta() | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All it does is interact with |
||
| { | ||
| if (TheFirewallHelper != nullptr) | ||
| { | ||
| if (TheFirewallHelper->isBehaviorDetectionComplete() | ||
| && TheFirewallHelper->getFirewallBehavior() != FirewallHelperClass::FIREWALL_TYPE_UNKNOWN) | ||
| { | ||
| return TheFirewallHelper->getSourcePortAllocationDelta(); | ||
| } | ||
|
|
||
| if (TheFirewallHelper->getLastFirewallBehavior() != FirewallHelperClass::FIREWALL_TYPE_UNKNOWN) | ||
| { | ||
| return TheFirewallHelper->getLastSourcePortAllocationDelta(); | ||
| } | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -176,22 +217,12 @@ void FirewallHelperClass::reset() | |
| *=============================================================================================*/ | ||
| Bool FirewallHelperClass::detectFirewall() | ||
| { | ||
| OptionPreferences pref; | ||
|
|
||
| OptionPreferences::const_iterator it = pref.find("FirewallNeedToRefresh"); | ||
| if (it != pref.end()) { | ||
| AsciiString str = it->second; | ||
| if (str.compareNoCase("TRUE") == 0) { | ||
| TheWritableGlobalData->m_firewallBehavior = FIREWALL_TYPE_UNKNOWN; | ||
| } | ||
| } | ||
|
|
||
| if (TheWritableGlobalData->m_firewallBehavior == FIREWALL_TYPE_UNKNOWN) { | ||
| if (m_behavior == FIREWALL_TYPE_UNKNOWN) { | ||
| detectFirewallBehavior(); | ||
|
|
||
| return FALSE; | ||
| } else { | ||
| DEBUG_LOG(("FirewallHelperClass::detectFirewall - firewall behavior already specified as %d, port allocation delta is %d, skipping detection.", TheWritableGlobalData->m_firewallBehavior, TheWritableGlobalData->m_firewallPortAllocationDelta)); | ||
| DEBUG_LOG(("FirewallHelperClass::detectFirewall - firewall behavior already specified as %d, port allocation delta is %d, skipping detection.", m_behavior, m_sourcePortAllocationDelta)); | ||
| } | ||
|
|
||
| return TRUE; | ||
|
|
@@ -480,44 +511,6 @@ UnsignedShort FirewallHelperClass::getManglerResponse(UnsignedShort packetID, In | |
| return mangled_port; | ||
| } | ||
|
|
||
|
|
||
|
|
||
|
|
||
| /*********************************************************************************************** | ||
| * FirewallHelperClass::Write_Firewall_Settings -- Save out firewall settings. * | ||
| * * | ||
| * * | ||
| * * | ||
| * INPUT: Nothing * | ||
| * * | ||
| * OUTPUT: Nothing * | ||
| * * | ||
| * WARNINGS: None * | ||
| * * | ||
| * HISTORY: * | ||
| * 3/22/01 10:23PM ST : Created * | ||
| *=============================================================================================*/ | ||
| void FirewallHelperClass::writeFirewallBehavior() | ||
| { | ||
| OptionPreferences pref; | ||
|
|
||
| char num[16]; | ||
| num[0] = 0; | ||
| itoa(TheGlobalData->m_firewallBehavior, num, 10); | ||
| AsciiString numstr; | ||
| numstr = num; | ||
| (pref)["FirewallBehavior"] = numstr; | ||
|
|
||
| TheWritableGlobalData->m_firewallPortAllocationDelta = getSourcePortAllocationDelta(); | ||
| num[0] = 0; | ||
| itoa(TheGlobalData->m_firewallPortAllocationDelta, num, 10); | ||
| numstr = num; | ||
| (pref)["FirewallPortAllocationDelta"] = numstr; | ||
|
|
||
| pref.write(); | ||
| } | ||
|
|
||
|
|
||
| /*********************************************************************************************** | ||
| * FirewallHelperClass::flagNeedToRefresh -- Flag that the next time we log in we need to * | ||
| * refresh our firewall settings. * | ||
|
|
@@ -535,38 +528,11 @@ void FirewallHelperClass::writeFirewallBehavior() | |
| *=============================================================================================*/ | ||
| void FirewallHelperClass::flagNeedToRefresh(Bool flag) | ||
| { | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| OptionPreferences pref; | ||
|
|
||
| (pref)["FirewallNeedToRefresh"] = flag ? "TRUE" : "FALSE"; | ||
|
|
||
| pref.write(); | ||
| } | ||
|
|
||
|
|
||
| /*********************************************************************************************** | ||
| * FirewallHelperClass::Read_Firewall_Behavior -- Read in old firewall settings * | ||
| * * | ||
| * * | ||
| * * | ||
| * INPUT: Nothing * | ||
| * * | ||
| * OUTPUT: Nothing * | ||
| * * | ||
| * WARNINGS: None * | ||
| * * | ||
| * HISTORY: * | ||
| * 3/22/01 10:25PM ST : Created * | ||
| *=============================================================================================*/ | ||
| void FirewallHelperClass::readFirewallBehavior() | ||
| { | ||
| #if (0) | ||
| 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. Choose a reason for hiding this commentThe 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. |
||
| detectFirewallBehavior(); | ||
| } | ||
| } | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
|
|
||
| /*********************************************************************************************** | ||
| * FHC::detectFirewallBehavior -- What is that wacky firewall doing to our packet headers? * | ||
| * * | ||
|
|
@@ -581,15 +547,28 @@ void FirewallHelperClass::readFirewallBehavior() | |
| * HISTORY: * | ||
| * 3/15/01 12:30PM ST : Created * | ||
| *=============================================================================================*/ | ||
| void FirewallHelperClass::detectFirewallBehavior(/*Bool &canRecord*/) | ||
| void FirewallHelperClass::detectFirewallBehavior() | ||
| { | ||
| m_behavior = FIREWALL_TYPE_SIMPLE; | ||
| reset(); | ||
| m_currentTry = 0; | ||
| m_numManglers = 0; | ||
| m_numResponses = 0; | ||
| m_packetID = 0; | ||
| m_timeoutLength = 0; | ||
| m_timeoutStart = 0; | ||
|
|
||
| if (m_behavior != FIREWALL_TYPE_UNKNOWN) | ||
| { | ||
| m_lastBehavior = m_behavior; | ||
| m_lastSourcePortAllocationDelta = m_sourcePortAllocationDelta; | ||
| } | ||
|
|
||
| m_sourcePortAllocationDelta = 0; | ||
| m_behavior = FIREWALL_TYPE_UNKNOWN; | ||
| m_currentState = DETECTIONSTATE_BEGIN; | ||
| } | ||
|
|
||
| FirewallHelperClass::FirewallBehaviorType FirewallHelperClass::getFirewallBehavior() { | ||
| m_currentState = DETECTIONSTATE_IDLE; | ||
| return m_behavior; | ||
| } | ||
|
|
||
|
|
@@ -608,6 +587,12 @@ Short FirewallHelperClass::getSourcePortAllocationDelta() { | |
| Bool FirewallHelperClass::detectionBeginUpdate() { | ||
| // UnsignedShort mangler_port = MANGLER_PORT; | ||
| m_packetID = 0x7f00; | ||
|
|
||
| if (TheGameSpyConfig == nullptr) { | ||
| DEBUG_LOG(("FirewallHelperClass::detectionBeginUpdate - no GameSpy config, skipping detection.")); | ||
| m_currentState = DETECTIONSTATE_DONE; | ||
| return TRUE; | ||
| } | ||
| //int current_mangler = 0; | ||
|
|
||
| /* | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can all be const