708 lines
No EOL
19 KiB
C++
708 lines
No EOL
19 KiB
C++
/*
|
|
** 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/***********************************************************************************************
|
|
*** 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<ClientStruct*> ClientQueue;
|
|
|
|
/*
|
|
** List if names waiting to be removed from the client queue.
|
|
*/
|
|
DynamicVectorClass<ClientStruct*> ClientQueueRemoveList;
|
|
|
|
/*
|
|
** Connections we have made so far in this game. This includes people who were in the game and left.
|
|
*/
|
|
DynamicVectorClass<IPAddressClass> ConnectionHistory;
|
|
DynamicVectorClass<unsigned short> 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<ThreadActionClass> 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
|