/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Command & Conquer * * * * $Archive:: /Commando/Code/Commando/nat.h $* * * * $Author:: Steve_t $* * * * $Modtime:: 3/26/02 12:16p $* * * * $Revision:: 19 $* * * * * *---------------------------------------------------------------------------------------------* * * * * *---------------------------------------------------------------------------------------------* * * * Functions: * * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ #pragma once #ifndef NAT_H #define NAT_H #include "always.h" #include "win.h" #ifdef WWASSERT #ifndef fw_assert #define fw_assert WWASSERT #endif //fw_assert #else //WWASSERT #define fw_assert assert #endif //WWASSERT #include "nataddr.h" #include "natter.h" #include "wolapi.h" /* ** Number of ports to use when testing port mangling sequences. */ #define NUM_TEST_PORTS 4 #ifndef IPAddressClass class IPAddressClass; #endif //IPAddressClass #ifndef SocketHandlerClass class SocketHandlerClass; #endif //SocketHandlerClass /* ** ** Class to help in dealing with firewalls. ** ** Basically detects firewall type and behavior. ** ** In Renegade it also does the firewall port negotiation between server and client. */ class FirewallHelperClass { public: /* ** Enumeration of firewall behaviors we can detect. ** ** It is assumed that all port mangling firewalls change the mangled source port in response to ** an application source port change. */ typedef enum tFirewallBehaviorType { /* ** Just used as an initialiser. */ FIREWALL_TYPE_UNKNOWN = 0, /* ** This is a simple, non-port translating firewall, or there is no firewall at all. */ FIREWALL_TYPE_SIMPLE = 1, /* ** This is a firewall/NAT with port mangling but it's pretty dumb - it uses the same mangled ** source port regardless of the destination address. */ FIREWALL_TYPE_DUMB_MANGLING = 2, /* ** This is a smarter firewall/NAT with port mangling that uses different mangled source ports ** for different destination IPs. */ FIREWALL_TYPE_SMART_MANGLING = 4, /* ** This is a firewall that exhibits the bug as seen in the Netgear firewalls. A previously good ** source port mapping will change in response to unsolicited traffic from a known IP. */ FIREWALL_TYPE_NETGEAR_BUG = 8, /* ** This firewall has a simple absolute offset port allocation scheme. */ FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION = 16, /* ** This firewall has a relative offset port allocation scheme. For these firewalls, we have to ** subtract the actual source port from the mangled source port to discover the allocation scheme. ** The mangled port number is based in part on the original source port number. */ FIREWALL_TYPE_RELATIVE_PORT_ALLOCATION = 32, /* ** This firewall mangles source ports differently depending on the destination port. */ FIREWALL_TYPE_DESTINATION_PORT_DELTA = 64 } FirewallBehaviorType; /* ** Constructor, destructor. */ FirewallHelperClass(void); ~FirewallHelperClass(void); /* ** Startup, shutdown. */ void Startup(void); void Shutdown(void); /* ** Detection. */ void Detect_Firewall(HANDLE event = INVALID_HANDLE_VALUE); unsigned short Get_Raw_Firewall_Behavior(void); // {return((unsigned short)Behavior);}; short Get_Source_Port_Allocation_Delta(void) {return(SourcePortAllocationDelta);} /* ** Query class for behavior. */ unsigned short Get_Next_Mangled_Source_Port(unsigned short source_port); int Get_Firewall_Hardness(FirewallBehaviorType behavior); int Get_Firewall_Retries(FirewallBehaviorType behavior); void Set_Source_Port_Pool_Start(int port) {SourcePortPool = port;}; int Get_Source_Port_Pool(void) {return(SourcePortPool);}; /* ** Talking to the manglers. */ int Build_Mangler_Packet(unsigned char *buffer, unsigned short port, unsigned long packet_id = 0, bool blitzme = false); /* ** Port management. */ unsigned short Get_Next_Temporary_Source_Port(int skip); bool Get_Reference_Port(void); void Reset_Server(void); unsigned short Get_Client_Bind_Port(void) {return(ClientPort);}; //PlayersFirewallAddress.Get_Port());}; /* ** Firewall info import and export. */ void Set_Firewall_Info(unsigned long last_behavior, int last_delta, unsigned short port_pool, bool send_delay, int confidence); void Get_Firewall_Info(unsigned long &last_behavior, int &last_delta, unsigned short &port_pool, bool &send_delay, int &confidence) const; void Set_Send_Delay(bool send_delay) {SendDelay = send_delay;}; bool Get_Send_Delay(void) {return(SendDelay);}; /* ** Communications functions. */ bool Send_To_Mangler(IPAddressClass *address, SocketHandlerClass *socket_handler, unsigned long packet_id, bool blitzme = false); unsigned short Get_Mangler_Response(unsigned long packet_id, SocketHandlerClass *socket_handler, int time = 0, bool all_service = false); /* ** Server connection negotiation functions. */ void Connected_To_WWOnline_Server(void); void Talk_To_New_Player(WOL::User *user); void Send_My_Port(unsigned short port); void Set_Client_Connect_Event(HANDLE thread_event, HANDLE cancel_event, int *flag_ptr, int *queue_ptr); bool Remove_Player_From_Negotiation_Queue(char *player_name); bool Remove_Player_From_Negotiation_Queue_If_Mutex_Available(char *player_name); void Cleanup_Client_Queue(void); /* ** Get the local chat connection address. */ bool Get_Local_Chat_Connection_Address(void); unsigned long Get_Local_Address(void); IPAddressClass &Get_External_Address(void) {return(ExternalAddress);} void Set_External_Address(IPAddressClass &addr) {ExternalAddress = addr;} /* ** Reset. */ void Reset(void); /* ** Query. */ bool Is_Busy(void) {return((ThreadState != THREAD_IDLE) ? true : false);} /* ** Inline behavior query functions. */ inline bool Is_NAT(void) { if (Behavior == FIREWALL_TYPE_UNKNOWN || (Behavior & FIREWALL_TYPE_SIMPLE) != 0) { return(false); } return(true); }; inline bool Is_NAT(FirewallBehaviorType behavior) { if (behavior == FIREWALL_TYPE_UNKNOWN || (behavior & FIREWALL_TYPE_SIMPLE) != 0) { return(false); } return(true); }; inline bool Is_Netgear(FirewallBehaviorType behavior) { if ((behavior & FIREWALL_TYPE_NETGEAR_BUG) != 0) { return(true); } return(false); }; inline bool Is_Netgear(void) { if ((Behavior & FIREWALL_TYPE_NETGEAR_BUG) != 0) { return(true); } return(false); }; /* ** Exposing the thread ID for the exception handler. */ unsigned long Get_Thread_ID(void) {return(ThreadID);}; /* ** Connection results reported back to the dialog wait object. */ enum { FW_RESULT_UNKNOWN, FW_RESULT_FAILED, FW_RESULT_SUCCEEDED, FW_RESULT_CANCELLED }; private: /************************************************************************************************************************ ** ** Private functions. ** */ /* ** Detection. */ FirewallBehaviorType Detect_Firewall_Behavior(void); int Get_NAT_Port_Allocation_Scheme(int num_ports, unsigned short *original_ports, unsigned short *mangled_ports, bool &relative_delta, bool &looks_good); /* ** Server connection negotiation functions. */ int Negotiate_Port(void); void Send_Connection_Result(int result, unsigned short port); void Set_Client_Success(int success); void Send_Queue_States(void); bool Client_Cancelled(void); bool Remote_Client_Cancelled(void); void Send_Cancel_Notification(void); /* ** Threading. */ static unsigned int __stdcall NAT_Thread_Start(void *param); unsigned long NAT_Thread_Main_Loop(void); void Add_Thread_Action(int thread_action, HANDLE thread_event); void Set_Thread_Event(void); /* ** Game options processing. */ void Process_Game_Options(void); /************************************************************************************************************************ ** ** Private data. ** */ /* ** How does our firewall behave? */ FirewallBehaviorType Behavior; /* ** How did the firewall behave the last time we ran the game. */ FirewallBehaviorType LastBehavior; /* ** What is the delta in our firewalls NAT port allocation scheme. */ int SourcePortAllocationDelta; /* ** What was the delta the last time we ran? */ int LastSourcePortAllocationDelta; /* ** Source ports used only to discover port allocation patterns. */ int SourcePortPool; /* ** Should we delay before sending (for Netgear bug)? */ bool SendDelay; /* ** How well do the remembered settings work. */ int Confidence; /* ** Mangler server info. */ int NumManglerServers; char ManglerServerAddress[64][128]; int ManglerServerPort[64]; int CurrentManglerServer; /* ** Local chat connection address. */ IPAddressClass LocalChatConnectionAddress; /* ** Our address as far as chat knows. */ IPAddressClass ExternalAddress; /************************************************************************************************************************ ** ** Server/Client connection related private data. ** ** */ /* ** Who we are currently trying to talk to. */ char PlayersName[64]; /* ** Local address of the player we are trying to talk to. */ IPAddressClass PlayersLocalAddress; /* ** External (mangler view) address of the player we are trying to talk to. */ IPAddressClass PlayersExternalAddress; /* ** Type of firewall that the other player has. */ FirewallBehaviorType PlayersFirewallType; /* ** Teacks what we expect the other player to have his port number mangled to. */ unsigned short PlayersMangledPort; /* ** Other players in WOL::User struct form. */ WOL::User PlayerAsUser; /* ** Our current best guess at the combined mangled port/ip of the other player. */ IPAddressClass PlayersFirewallAddress; /* ** Connection result reported by other player. Used to find out if our packet got through. See enum below. */ int PlayersConnectionResult; /* ** Port that the other player saw our packet actually come from. */ unsigned short PlayersConnectionResultPort; /* ** Enum for connection results. */ enum { CONNRESULT_UNTRIED = -1, CONNRESULT_FAILED = 0, CONNRESULT_TRY1, CONNRESULT_TRY2, CONNRESULT_TRY3, CONNRESULT_TRY4, CONNRESULT_TRY5, CONNRESULT_TRY6, CONNRESULT_TRY7, CONNRESULT_TRY8, CONNRESULT_TRY9, CONNRESULT_TRY10, CONNRESULT_CONNECTED }; /************************************************************************************************************************ ** ** Server side only data. ** */ /* ** Client queue (for server only). This is a list of clients waiting to negotiate a port. */ typedef struct tClientStruct { char Name[64]; IPAddressClass LocalAddress; IPAddressClass ExternalAddress; FirewallBehaviorType FirewallType; WOL::User User; } ClientStruct; DynamicVectorClass ClientQueue; /* ** List if names waiting to be removed from the client queue. */ DynamicVectorClass ClientQueueRemoveList; /* ** Connections we have made so far in this game. This includes people who were in the game and left. */ DynamicVectorClass ConnectionHistory; DynamicVectorClass MangledPortHistory; /* ** When we last heard from the client. */ unsigned long LastOptionsFromClient; /* ** Name of player who has cancelled out of our game channel before connecting. */ char CancelPlayer[64]; /************************************************************************************************************************ ** ** Client side only data. ** */ /* ** Number of players in the servers queue ahead of us. */ int QueuedPlayers; /* ** Port that the client will use as a basis for negotiation. */ unsigned short ClientPort; /* ** Client connect event notification. */ int *SuccessFlagPtr; HANDLE ClientConnectEvent; HANDLE ClientCancelEvent; int *QueueNotifyPtr; /************************************************************************************************************************ ** ** Threading. ** ** This class runs in it's own thread so that firewall negotiation doesn't stall the server when a new client joins. ** It also means that the clients join dialog is serviced while connecting, allowing him to cancel at any time. ** */ HANDLE ThreadHandle; unsigned long ThreadID; HANDLE NATThreadMutex; HANDLE NATDataMutex; bool ThreadActive; /* ** Thread actions. */ enum { THREAD_IDLE, THREAD_DETECT_FIREWALL, THREAD_DETECT_FIREWALL_DONE, THREAD_CONNECT_FIREWALL, THREAD_CONNECT_FIREWALL_DONE, THREAD_GET_LOCAL_ADDRESS, THREAD_GET_LOCAL_ADDRESS_DONE }; /* ** Thread state i.e. what it's doing. */ int ThreadState; /* ** Event to be signalled when the current thread action completes. */ HANDLE ThreadEvent; /* ** Thread action FIFO */ class ThreadActionClass { public: inline bool operator == (ThreadActionClass const &data) { return(memcmp((void*)this, &data, sizeof(*this)) == 0); }; inline bool operator != (ThreadActionClass const &data) { return(memcmp((void*)this, &data, sizeof(*this)) != 0); }; int ThreadAction; HANDLE ThreadEvent; }; DynamicVectorClass ThreadQueue; /* ** Thread safety. */ class ThreadLockClass { public: /* ** Constructor. Grabs the mutex. */ inline ThreadLockClass(FirewallHelperClass *fwptr, unsigned long timeout = 10 * 1000) { FWPtr = fwptr; /* ** Just test the mutex if timeout is 0. */ WaitResult = WaitForSingleObject(fwptr->NATDataMutex, timeout); if (timeout != 0 && WaitResult == WAIT_TIMEOUT) { WWDEBUG_SAY(("FirewallHelper - Timeout waiting for firewall helper data mutex\n")); fw_assert(WaitResult != WAIT_TIMEOUT); } }; FirewallHelperClass *FWPtr; int WaitResult; /* ** Destructor, releases the mutex. */ inline ~ThreadLockClass(void) { ReleaseMutex(FWPtr->NATDataMutex); }; }; friend ThreadLockClass; /***************************************************************************************************************** ** ** ** C&C Packet format as expected by the mangler. Copied from various places in the RA2 code. ** */ public: /* ** One byte alignment. */ #pragma pack(push, 1) /* ** Misc C&C packet defines. */ #define GLOBAL_MAGICNUM 0x1236 #define PACKET_DATA_NOACK 1 #define COMMAND_AND_CONQUER_RA2 0xaa03 #define NET_MANGLER_REQUEST 43 #define NET_MANGLER_RESPONSE 44 #define MPLAYER_NAME_MAX 20 #define SERIAL_MAX 23 /* ** CommHeaderType - Low level packet wrapper. */ struct CommHeaderType { unsigned short MagicNumber; // in, out = GLOBAL_MAGICNUM = 0x1236. Just preserve the incoming value. char Code; // in, out = PACKET_DATA_NOACK = 1 union { unsigned char ForwardTo; // = 0 unsigned char ForwardFrom; }; unsigned long PacketID; // Dont care. unsigned char ForwardAddress[4]; unsigned short ForwardPort; }; /* ** GlobalHeaderType - Low level packet wrapper for global channel packets. */ struct GlobalHeaderType { CommHeaderType Header; // See above unsigned short ProductID; // in, out = COMMAND_AND_CONQUER_RA2 = 0xaa03. Just return the value from the received packet }; /* ** GlobalPacketType - Payload of global channel packet. */ struct GlobalPacketType { int Command; // in = NET_MANGLER_REQUEST = 43 , out = NET_MANGLER_RESPONSE = 44 char Name[MPLAYER_NAME_MAX]; // in, out = Player name (8 bit) - return same name char Serial[SERIAL_MAX]; // Not used. union { struct { unsigned short MangledPortNumber; unsigned char MangledAddress[4]; unsigned short OriginalPortNumber; unsigned char BlitzMe; } ManglerData; }; }; /* ** CnCPacketType - Header and payload in one structure. */ typedef struct tCnCPacketType { GlobalHeaderType GHeader; GlobalPacketType Packet; } CnCPacketType; #pragma pack(pop) /* ** End C&C packet format. **************************************************************************************************************** */ }; /* ** Single instance of FirewallHelperClass */ extern FirewallHelperClass FirewallHelper; #endif //NAT_H