/*
** Command & Conquer Generals(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) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
/***********************************************************************************************
*** 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:: /RedAlert2/NAT.CPP $*
* *
* $Author:: Steve_t $*
* *
* $Modtime:: 3/15/01 12:00PM $*
* *
* $Revision:: 1 $*
* *
* *
*---------------------------------------------------------------------------------------------*
* *
* *
*---------------------------------------------------------------------------------------------*
* *
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/crc.h"
#include "Common/UserPreferences.h"
#include "GameNetwork/FirewallHelper.h"
#include "GameNetwork/NAT.h"
#include "GameNetwork/udp.h"
#include "GameNetwork/NetworkDefs.h"
#include "GameNetwork/GameSpy/GSConfig.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
FirewallHelperClass *TheFirewallHelper = NULL;
FirewallHelperClass * createFirewallHelper()
{
return NEW FirewallHelperClass();
}
/***********************************************************************************************
* FirewallHelperClass::FirewallHelperClass -- Constructor *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/15/01 5:03PM ST : Created *
*=============================================================================================*/
/* static */ Int FirewallHelperClass::m_sourcePortPool = 4096;
FirewallHelperClass::FirewallHelperClass(void)
{
//Added Sadullah Nader
//Initializations missing and needed
m_currentTry = 0;
m_numManglers = 0;
m_numResponses = 0;
m_packetID = 0;
m_timeoutLength = 0;
m_timeoutStart = 0;
//
m_behavior = FIREWALL_TYPE_UNKNOWN;
m_lastBehavior = FIREWALL_TYPE_UNKNOWN;
m_sourcePortAllocationDelta = 0;
m_lastSourcePortAllocationDelta = 0;
for (Int i = 0; i < MAX_SPARE_SOCKETS; ++i) {
m_spareSockets[i].port = 0;
m_messages[i].length = 0;
m_mangledPorts[i] = 0;
m_sparePorts[i] = 0;
}
for (i = 0; i < MAX_NUM_MANGLERS; i++)
{
m_manglers[i] = 0;
}
m_currentState = DETECTIONSTATE_IDLE;
m_sourcePortPool = 4096 + ((timeGetTime() / 1000) % 1000); // do this to make sure we don't use the same source
// port before a previous connection has had a chance
// to time out.
}
/***********************************************************************************************
* FirewallHelperClass::~FirewallHelperClass -- Destructor *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 4/16/02 BGC : Created *
*=============================================================================================*/
FirewallHelperClass::~FirewallHelperClass()
{
reset();
}
/***********************************************************************************************
* FirewallHelperClass::Reset -- Cleans out the object *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/29/01 1:04AM ST : Created *
*=============================================================================================*/
void FirewallHelperClass::reset(void)
{
closeAllSpareSockets();
m_currentState = DETECTIONSTATE_IDLE;
for (Int i = 0; i < MAX_SPARE_SOCKETS; ++i) {
m_messages[i].length = 0;
}
}
/***********************************************************************************************
* FirewallHelperClass::Detect_Firewall -- See what our firewall is up to *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/15/01 6:47PM ST : Created *
*=============================================================================================*/
Bool FirewallHelperClass::detectFirewall(void)
{
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) {
detectFirewallBehavior();
return FALSE;
} else {
DEBUG_LOG(("FirewallHelperClass::detectFirewall - firewall behavior already specified as %d, port allocation delta is %d, skipping detection.\n", TheWritableGlobalData->m_firewallBehavior, TheWritableGlobalData->m_firewallPortAllocationDelta));
}
return TRUE;
}
Bool FirewallHelperClass::behaviorDetectionUpdate()
{
if (m_currentState == DETECTIONSTATE_IDLE) {
return FALSE;
}
if (m_currentState == DETECTIONSTATE_DONE) {
return TRUE;
}
if (m_currentState == DETECTIONSTATE_BEGIN) {
return detectionBeginUpdate();
}
if (m_currentState == DETECTIONSTATE_TEST1) {
return detectionTest1Update();
}
if (m_currentState == DETECTIONSTATE_TEST2) {
return detectionTest2Update();
}
if (m_currentState == DETECTIONSTATE_TEST3) {
return detectionTest3Update();
}
if (m_currentState == DETECTIONSTATE_TEST3_WAITFORRESPONSES) {
return detectionTest3WaitForResponsesUpdate();
}
if (m_currentState == DETECTIONSTATE_TEST4_1) {
return detectionTest4Stage1Update();
}
if (m_currentState == DETECTIONSTATE_TEST4_2) {
return detectionTest4Stage2Update();
}
if (m_currentState == DETECTIONSTATE_TEST5) {
return detectionTest5Update();
}
return TRUE;
}
/***********************************************************************************************
* FHC::getNextTemporarySourcePort -- Get a throwaway source port for temporary use *
* *
* *
* *
* INPUT: number of ports in sequence to skip *
* *
* OUTPUT: port number *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/15/01 12:06PM ST : Created *
*=============================================================================================*/
UnsignedShort FirewallHelperClass::getNextTemporarySourcePort(Int skip)
{
UnsignedShort return_port = (UnsignedShort) m_sourcePortPool;
/*
** Try max 256 ports until we find one we can bind to a socket.
*/
Int tries = 256;
if (skip == 0) {
skip = 1;
}
while (tries--) {
m_sourcePortPool += skip;
return_port = (UnsignedShort) m_sourcePortPool;
if (m_sourcePortPool > 65535) {
m_sourcePortPool = 2048;
}
/*
** Validate the port by trying to bind it to a socket.
*/
Bool result = openSpareSocket(return_port);
if (result) {
closeSpareSocket(return_port);
return(return_port);
} else {
DEBUG_LOG(("FirewallHelperClass::getNextTemporarySourcePort - failed to open socket on port %d\n"));
}
}
return(return_port);
}
/***********************************************************************************************
* FHC::sendToManglerFromPort -- Send to the mangler from the specified port *
* *
* *
* *
* INPUT: Address of mangler server *
* Source port to send *from* *
* *
* OUTPUT: True if sent OK *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/15/01 12:47PM ST : Created *
*=============================================================================================*/
Bool FirewallHelperClass::sendToManglerFromPort(UnsignedInt address, UnsignedShort port, UnsignedShort packetID, Bool blitzme)
{
DEBUG_LOG(("sizeof(ManglerMessage) == %d, sizeof(ManglerData) == %d\n",
sizeof(ManglerMessage), sizeof(ManglerData)));
/*
** Build the packet to send out.
*/
ManglerMessage packet;
memset(&(packet.data),0x44, sizeof(ManglerData));
packet.data.NetCommandType = 12; // mangler request.
packet.data.PacketID = packetID;
if (blitzme) {
packet.data.BlitzMe = 1;
} else {
packet.data.BlitzMe = 0;
}
packet.data.magic = GENERALS_MAGIC_NUMBER;
packet.data.OriginalPortNumber = port;
/*
DEBUG_LOG(("Pre-Adjust Buffer = "));
for (Int i = 0; i < sizeof(ManglerData); ++i) {
DEBUG_LOG(("%02x", *(((unsigned char *)(&(packet.data))) + i)));
}
DEBUG_LOG(("\n"));
*/
byteAdjust(&(packet.data));
/*
DEBUG_LOG(("Pre-CRC Buffer = "));
for (i = 0; i < sizeof(ManglerData); ++i) {
DEBUG_LOG(("%02x", *(((unsigned char *)(&(packet.data))) + i)));
}
DEBUG_LOG(("\n"));
*/
CRC crc;
crc.computeCRC((unsigned char *)(&(packet.data.magic)), sizeof(ManglerData) - sizeof(unsigned int));
packet.data.CRC = htonl(crc.get());
packet.length = sizeof(ManglerData);
DEBUG_LOG(("FirewallHelperClass::sendToManglerFromPort - Sending from port %d to %d.%d.%d.%d:%d\n", (UnsignedInt)port,
(address & 0xFF000000) >> 24,
(address & 0xFF0000) >> 16,
(address & 0xFF00) >> 8,
(address & 0xFF), MANGLER_PORT));
/*
DEBUG_LOG(("Buffer = "));
for (i = 0; i < sizeof(ManglerData); ++i) {
DEBUG_LOG(("%02x", *(((unsigned char *)(&(packet.data))) + i)));
}
DEBUG_LOG(("\n"));
*/
SpareSocketStruct *spareSocket = findSpareSocketByPort(port);
// DEBUG_LOG(("PacketID = %u\n", packetID));
// DEBUG_LOG(("OriginalPortNumber = %u\n", port));
if (spareSocket == NULL) {
DEBUG_ASSERTCRASH(spareSocket != NULL, ("Could not find spare socket for send."));
DEBUG_LOG(("FirewallHelperClass::sendToManglerFromPort - failed to find the spare socket for port %d\n", port));
return FALSE;
}
spareSocket->udp->Write((UnsignedByte *) &packet, sizeof(ManglerData), address, MANGLER_PORT);
return(TRUE);
}
SpareSocketStruct * FirewallHelperClass::findSpareSocketByPort(UnsignedShort port) {
DEBUG_LOG(("FirewallHelperClass::findSpareSocketByPort - trying to find spare socket with port %d\n", port));
for (Int i = 0; i < MAX_SPARE_SOCKETS; ++i) {
if (m_spareSockets[i].port == port) {
DEBUG_LOG(("FirewallHelperClass::findSpareSocketByPort - found it!\n"));
return &(m_spareSockets[i]);
}
}
DEBUG_LOG(("FirewallHelperClass::findSpareSocketByPort - didn't find it\n"));
return NULL;
}
ManglerMessage * FirewallHelperClass::findEmptyMessage() {
for (Int i = 0; i < MAX_SPARE_SOCKETS; ++i) {
if (m_messages[i].length == 0) {
return &(m_messages[i]);
}
}
return NULL;
}
void FirewallHelperClass::byteAdjust(ManglerData *data) {
// for (Int i = 0; i < len/4; ++i) {
// *buf = htonl(*buf);
// ++buf;
// }
data->CRC = htonl(data->CRC);
data->magic = htons(data->magic);
data->MyMangledPortNumber = htons(data->MyMangledPortNumber);
data->OriginalPortNumber = htons(data->OriginalPortNumber);
data->PacketID = htons(data->PacketID);
}
/***********************************************************************************************
* FHC::Get_Mangler_Response_On_Port -- Get the manglers response to a specific query *
* *
* *
* *
* INPUT: Packet id of packet we are looking for *
* *
* OUTPUT: Port the mangler saw this packet come from. *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/15/01 12:51PM ST : Created *
*=============================================================================================*/
UnsignedShort FirewallHelperClass::getManglerResponse(UnsignedShort packetID, Int time)
{
ManglerMessage *msg = NULL;
// SpareSocketStruct *spareSocket = NULL;
sockaddr_in addr;
for (Int i = 0; i < MAX_SPARE_SOCKETS; ++i) {
if (m_spareSockets[i].udp != NULL) {
ManglerMessage *message = findEmptyMessage();
if (message == NULL) {
break;
}
Int retval = m_spareSockets[i].udp->Read((unsigned char *)message, sizeof(ManglerData), &addr);
if (retval > 0) {
CRC crc;
crc.computeCRC((unsigned char *)(&(message->data.magic)), sizeof(ManglerData) - sizeof(unsigned int));
if (crc.get() != htonl(message->data.CRC)) {
DEBUG_LOG(("FirewallHelperClass::getManglerResponse - Saw message, CRC mismatch. Expected CRC %u, computed CRC %u\n", message->data.CRC, crc.get()));
continue;
}
byteAdjust(&(message->data));
message->length = retval;
DEBUG_LOG(("FirewallHelperClass::getManglerResponse - Saw message of %d bytes from mangler %d on port %u\n", retval, i, m_spareSockets[i].port));
DEBUG_LOG(("FirewallHelperClass::getManglerResponse - Message has packet ID %d 0x%08X, looking for packet id %d 0x%08X\n", message->data.PacketID, message->data.PacketID, packetID, packetID));
if (message->data.PacketID == packetID) {
DEBUG_LOG(("FirewallHelperClass::getManglerResponse - packet ID's match, returning message\n"));
msg = message;
message->length = 0;
}
if (ntohs(message->data.PacketID) == packetID) {
DEBUG_LOG(("FirewallHelperClass::getManglerResponse - NETWORK BYTE ORDER packet ID's match, returning message\n"));
msg = message;
message->length = 0;
}
}
}
}
// See if we have already received it and saved it.
if (msg == NULL) {
for (i = 0; i < MAX_SPARE_SOCKETS; ++i) {
if ((m_messages[i].length != 0) && (m_messages[i].data.PacketID == packetID)) {
msg = &(m_messages[i]);
msg->length = 0;
}
}
}
if (msg == NULL) {
return 0;
}
UnsignedShort mangled_port = msg->data.MyMangledPortNumber;
DEBUG_LOG(("Mangler is seeing packets from port %d as coming from port %d\n", (UnsignedInt)msg->data.OriginalPortNumber, (UnsignedInt)mangled_port));
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(void)
{
OptionPreferences pref;
char num[16];
num[0] = 0;
itoa(TheGlobalData->m_firewallBehavior, num, 10);
AsciiString numstr;
numstr = num;
(pref)["FirewallBehavior"] = numstr;
TheWritableGlobalData->m_firewallPortAllocationDelta = TheFirewallHelper->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. *
* *
* *
* *
* INPUT: flag - whether or not to refresh...munkee *
* *
* OUTPUT: Nothing *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 2/19/03 4:30PM BGC : Created *
*=============================================================================================*/
void FirewallHelperClass::flagNeedToRefresh(Bool flag)
{
OptionPreferences pref;
(pref)["FirewallNeedToRefresh"] = flag ? AsciiString("TRUE") : AsciiString("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(void)
{
#if (0)
m_lastBehavior = (FirewallBehaviorType) ConfigINI.Get_Int("MultiPlayer", "FirewallSettings", FIREWALL_UNKNOWN);
m_lastSourcePortAllocationDelta = ConfigINI.Get_Int("MultiPlayer", "FirewallDelta", 1);
#endif //(0)
}
/***********************************************************************************************
* FHC::detectFirewallBehavior -- What is that wacky firewall doing to our packet headers? *
* *
* *
* *
* INPUT: Nothing *
* *
* OUTPUT: Firewall behavior *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/15/01 12:30PM ST : Created *
*=============================================================================================*/
void FirewallHelperClass::detectFirewallBehavior(/*Bool &canRecord*/)
{
m_behavior = FIREWALL_TYPE_SIMPLE;
m_currentState = DETECTIONSTATE_BEGIN;
}
FirewallHelperClass::FirewallBehaviorType FirewallHelperClass::getFirewallBehavior() {
m_currentState = DETECTIONSTATE_IDLE;
return m_behavior;
}
Short FirewallHelperClass::getSourcePortAllocationDelta() {
return m_sourcePortAllocationDelta;
}
/* static */ void FirewallHelperClass::getManglerName(Int manglerIndex, Char *nameBuf)
{
AsciiString host;
UnsignedShort port;
TheGameSpyConfig->getManglerLocation(manglerIndex, host, port);
strcpy(nameBuf, host.str());
}
Bool FirewallHelperClass::detectionBeginUpdate() {
// UnsignedShort mangler_port = MANGLER_PORT;
m_packetID = 0x7f00;
//int current_mangler = 0;
/*
** Well, we are going to need some manglers.
*/
UnsignedByte mangler_addresses[4][4];
// Int delta = 0;
/*
** If the user specified a particular port to use then we act as if there is no firewall.
*/
if (TheWritableGlobalData->m_firewallPortOverride != 0) {
m_behavior = FIREWALL_TYPE_SIMPLE;
DEBUG_LOG(("Source port %d specified by user\n", TheGlobalData->m_firewallPortOverride));
if (TheGlobalData->m_firewallSendDelay) {
UnsignedInt addbehavior = FIREWALL_TYPE_NETGEAR_BUG;
addbehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType) addbehavior;
DEBUG_LOG(("Netgear bug specified by command line or SendDelay flag\n"));
}
m_currentState = DETECTIONSTATE_DONE;
return TRUE;
}
m_timeoutStart = timeGetTime();
m_timeoutLength = 5000;
DEBUG_LOG(("About to call gethostbyname for the mangler address\n"));
int namenum = 0;
do {
AsciiString host;
UnsignedShort port;
TheGameSpyConfig->getManglerLocation(namenum, host, port);
const char *mangler_name_ptr = host.str();
DEBUG_LOG(("Looking at %s:%d", host.str(), port));
/*
** Use the wolapi supplied mangler info if available.
*/
// if (NumManglerServers > namenum) {
// mangler_name_ptr = &ManglerServerAddress[namenum][0];
// mangler_port = ManglerServerPort[namenum];
//current_mangler = CurrentManglerServer;
// DEBUG_LOG(("Using mangler from servserv\n"));
// }
namenum++;
if (strlen(mangler_name_ptr) == 0) {
break;
}
/*
** Do the lookup.
*/
char temp_name[256];
strcpy(temp_name, mangler_name_ptr);
struct hostent *host_info = gethostbyname(temp_name);
if (!host_info) {
DEBUG_LOG(("gethostbyname failed! Error code %d\n", WSAGetLastError()));
break;
}
/*
** See if we already have that address in the list.
*/
Bool found = FALSE;
for (Int i=0 ; ih_addr_list[0][0], 4) == 0) {
found = TRUE;
break;
}
}
/*
** Add the address in if we didn't find it.
*/
if (!found) {
Int m = m_numManglers++;
memcpy(&mangler_addresses[m][0], &host_info->h_addr_list[0][0], 4);
ntohl((UnsignedInt)mangler_addresses[m]);
DEBUG_LOG(("Found mangler address at %d.%d.%d.%d\n", mangler_addresses[m][0], mangler_addresses[m][1], mangler_addresses[m][2], mangler_addresses[m][3]));
}
} while ((m_numManglers < MAX_NUM_MANGLERS) && ((timeGetTime() - m_timeoutStart) < m_timeoutLength));
DEBUG_ASSERTCRASH(m_numManglers > 2, ("not enough mangler addresses found."));
if (m_numManglers < 3) {
m_currentState = DETECTIONSTATE_DONE;
return TRUE;
}
for (Int i=0 ; im_firewallSendDelay) {
UnsignedInt addbehavior = FIREWALL_TYPE_NETGEAR_BUG;
addbehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType) addbehavior;
DEBUG_LOG(("FirewallHelperClass::detectionBeginUpdate - Netgear bug specified by command line or SendDelay flag\n"));
} else {
DEBUG_LOG(("FirewallHelperClass::detectionBeginUpdate - Netgear bug not specified\n"));
}
/*
** OK, we have our manglers.
**
** First test, see if there is any port mangling at all.
**
**
**
*/
DEBUG_LOG(("About to start mangler test 1\n"));
/*
** Get a spare port number and create a new socket to bind it to.
*/
m_sparePorts[0] = getNextTemporarySourcePort(0);
if (!openSpareSocket(m_sparePorts[0])) {
m_currentState = DETECTIONSTATE_DONE;
return TRUE;
}
/*
** Send to the mangler from this port until we get a response.
*/
m_timeoutStart = timeGetTime();
m_timeoutLength = 6000;
sendToManglerFromPort(m_manglers[0], m_sparePorts[0], m_packetID);
m_currentState = DETECTIONSTATE_TEST1;
return FALSE;
}
Bool FirewallHelperClass::detectionTest1Update() {
m_mangledPorts[0] = getManglerResponse(m_packetID);
/*
** See if we got no response or a non-mangled response.
*/
if (m_mangledPorts[0] == 0 || m_mangledPorts[0] == m_sparePorts[0]) {
if (m_mangledPorts[0] == m_sparePorts[0]) {
m_sourcePortAllocationDelta = 0;
DEBUG_LOG(("FirewallHelperClass::detectionTest1Update - Non-mangled response from mangler, quitting test.\n"));
}
if ((m_mangledPorts[0] == 0) && ((timeGetTime() - m_timeoutStart) < m_timeoutLength)) {
// we are still waiting for a response and haven't timed out yet.
DEBUG_LOG(("FirewallHelperClass::detectionTest1Update - waiting for response from mangler.\n"));
return FALSE;
}
if ((m_mangledPorts[0] == 0) && ((timeGetTime() - m_timeoutStart) >= m_timeoutLength)) {
// we are still waiting for a response and we timed out.
DEBUG_LOG(("FirewallHelperClass::detectionTest1Update - timed out waiting for response from mangler.\n"));
}
// either we have received a non-mangled response or we timed out waiting for a response.
closeSpareSocket(m_sparePorts[0]);
m_currentState = DETECTIONSTATE_DONE;
return TRUE;
}
DEBUG_LOG(("FirewallHelperClass::detectionTest1Update - test 1 complete\n"));
/*
** Test one completed, time to start up the second test.
**
** Second test. See if the ports are mangled differently for different destination IPs.
**
** We can use the spare socket from the last test and send to a different mangler.
**
*/
/*
** Send to the mangler from this port until we get a response.
*/
m_timeoutStart = timeGetTime();
m_timeoutLength = 6000;
m_mangledPorts[1] = 0;
sendToManglerFromPort(m_manglers[1], m_sparePorts[0], m_packetID+1);
m_currentState = DETECTIONSTATE_TEST2;
return FALSE;
}
Bool FirewallHelperClass::detectionTest2Update() {
m_mangledPorts[1] = getManglerResponse(m_packetID+1);
if (m_mangledPorts[1] == 0) {
if ((timeGetTime() - m_timeoutStart) <= m_timeoutLength) {
return FALSE;
}
DEBUG_LOG(("FirewallHelperClass::detectionTest2Update - timed out waiting for mangler response\n"));
m_currentState = DETECTIONSTATE_DONE;
return TRUE;
}
/*
** We are done with this socket/port
*/
closeSpareSocket(m_sparePorts[0]);
/*
** See if we got no response or a non-mangled response.
*/
if (m_mangledPorts[1] == 0 || m_mangledPorts[1] == m_sparePorts[0]) {
m_currentState = DETECTIONSTATE_DONE;
UnsignedInt addBehavior = (UnsignedInt)FIREWALL_TYPE_SIMPLE;
addBehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType)addBehavior;
if (m_mangledPorts[1] == 0) {
DEBUG_LOG(("FirewallHelperClass::detectionTest2Update - got no response from mangler\n"));
} else {
DEBUG_LOG(("FirewallHelperClass::detectionTest2Update - got a mangler response, no port mangling\n"));
}
DEBUG_LOG(("FirewallHelperClass::detectionTest2Update - Setting behavior to SIMPLE, done testing\n"));
return TRUE;
}
if (m_mangledPorts[0] == m_mangledPorts[1]) {
DEBUG_LOG(("FirewallHelperClass::detectionTest2Update - port mangling doesn't depend on destination IP, setting to DUMB_MANGLING\n"));
UnsignedInt addBehavior = (UnsignedInt)FIREWALL_TYPE_DUMB_MANGLING;
addBehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType)addBehavior;
} else {
DEBUG_LOG(("FirewallHelperClass::detectionTest2Update - port mangling depends on destination IP, setting to SMART_MANGLING\n"));
UnsignedInt addBehavior = (UnsignedInt)FIREWALL_TYPE_SMART_MANGLING;
addBehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType)addBehavior;
}
/*
** Third test.
**
** This test tries to detect a pattern in the ports allocated by the NAT.
** We use several source ports for this one.
**
*/
m_currentTry = 0;
m_packetID = m_packetID + 10;
DEBUG_LOG(("FirewallHelperClass::detectionTest2Update - moving on to 3rd test\n"));
m_currentState = DETECTIONSTATE_TEST3;
return FALSE;
}
Bool FirewallHelperClass::detectionTest3Update() {
/*
** Try this whole thing a max of 3 times.
*/
if (m_currentTry < 3) {
memset(m_sparePorts, 0, sizeof(m_sparePorts));
memset(m_mangledPorts, 0, sizeof(m_mangledPorts));
/*
** Open a socket for each source port.
** We should use a non-linear set of source ports so we can detect the NAT32 relative offset
** case.
*/
for (Int i=0 ; i m_timeoutLength) {
/*
** Close down those sockets - we are finished with them.
*/
for (Int j=0 ; j (int)FIREWALL_TYPE_SIMPLE) {
/*
** If the delta we got last time we played looks good then use that.
*/
m_sourcePortAllocationDelta = m_lastSourcePortAllocationDelta;
}
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - didn't get enough responses, using %d as the source port allocation delta, finished test\n"));
m_currentState = DETECTIONSTATE_DONE;
return TRUE;
}
Bool relative_delta = FALSE;
Bool looks_good = FALSE;
Int delta = getNATPortAllocationScheme(m_numResponses, m_sparePorts, m_mangledPorts, relative_delta, looks_good);
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - getNATPortAllocationScheme returned %d\n", delta));
if (delta) {
/*
** Hey, we got it!
*/
UnsignedInt addbehavior = 0;
if (relative_delta) {
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - detected RELATIVE PORT ALLOCATION\n"));
addbehavior = (UnsignedInt)FIREWALL_TYPE_RELATIVE_PORT_ALLOCATION;
} else {
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - detected SIMPLE PORT ALLOCATION\n"));
addbehavior = (UnsignedInt)FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION;
}
addbehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType) addbehavior;
m_sourcePortAllocationDelta = delta;
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - setting source port delta to %d\n", delta));
} else {
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - didn't get a delta value\n"));
if (m_lastSourcePortAllocationDelta != 0 && (Int)m_lastBehavior > (Int)FIREWALL_TYPE_SIMPLE) {
/*
** If the delta we got last time we played looks good then use that.
*/
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - using the port allocation delta we have from before which is %d\n", m_lastSourcePortAllocationDelta));
m_sourcePortAllocationDelta = m_lastSourcePortAllocationDelta;
}
++m_currentTry;
m_currentState = DETECTIONSTATE_TEST3;
return FALSE;
}
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForRepsonsesUpdate - starting 4th test\n"));
/*
** Fourth test.
**
** Test to see if the NAT mangles differently per destination port at the same IP.
*/
if ((m_behavior & FIREWALL_TYPE_SMART_MANGLING) != 0) {
if ((m_behavior & FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION) != 0) {
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForRepsonsesUpdate - simple port allocation, Testing to see if the NAT mangles differently per destination port at the same IP\n"));
/*
** We need 2 source ports for this.
*/
m_sparePorts[0] = getNextTemporarySourcePort(0);
if (!openSpareSocket(m_sparePorts[0])) {
m_currentState = DETECTIONSTATE_DONE;
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForRepsonsesUpdate - Failed to open first spare port, bailing\n"));
return TRUE;
}
m_sparePorts[1] = getNextTemporarySourcePort(0);
if (!openSpareSocket(m_sparePorts[1])) {
closeSpareSocket(m_sparePorts[0]);
m_currentState = DETECTIONSTATE_DONE;
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForRepsonsesUpdate - Failed to open second spare port, bailing\n"));
return TRUE;
}
/*
** Get a reference port.
*/
m_timeoutStart = timeGetTime();
m_timeoutLength = 4000;
m_mangledPorts[0] = 0;
m_packetID += 10;
/*
** Wait for a response.
*/
sendToManglerFromPort(m_manglers[0], m_sparePorts[0], m_packetID);
m_currentState = DETECTIONSTATE_TEST4_1;
return FALSE;
} else {
/*
** NAT32 uses different mangled source ports for different destination ports.
*/
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForRepsonsesUpdate - relative port allocation, NAT32 right?\n"));
UnsignedInt addbehavior = 0;
addbehavior = (UnsignedInt)FIREWALL_TYPE_DESTINATION_PORT_DELTA;
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForRepsonsesUpdate - adding DESTINATION PORT DELTA to behavior\n"));
addbehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType) addbehavior;
}
} else {
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForResponsesUpdate - We don't have smart mangling, skipping test 4, entering test 5\n"));
}
DEBUG_LOG(("FirewallHelperClass::detectionTest3WaitForRepsonsesUpdate - entering test 5\n"));
m_currentState = DETECTIONSTATE_TEST5;
return FALSE;
}
Bool FirewallHelperClass::detectionTest4Stage1Update() {
m_mangledPorts[0] = getManglerResponse(m_packetID);
if (m_mangledPorts[0] == 0) {
if ((timeGetTime() - m_timeoutStart) > m_timeoutLength) {
closeSpareSocket(m_sparePorts[0]);
closeSpareSocket(m_sparePorts[1]);
m_currentState = DETECTIONSTATE_DONE;
DEBUG_LOG(("FirewallHelperClass::detectionTest4Stage1Update - timed out waiting for mangler response, quitting\n"));
return TRUE;
}
return FALSE;
}
/*
** Send out to a different port at that IP.
** We won't get a response for this.
*/
UnsignedInt addr = m_manglers[0];
UnsignedShort port1 = m_sparePorts[0] + 1;
sendToManglerFromPort(addr, port1, m_packetID);
sendToManglerFromPort(addr, port1, m_packetID);
sendToManglerFromPort(addr, port1, m_packetID);
/*
** We can't get a response from a different destination port so the only way to detect
** this behavior is to check the next mangled port allocation to see if it's double
** what we would normally expect.
*/
m_packetID++;
m_timeoutStart = timeGetTime();
m_timeoutLength = 4000;
sendToManglerFromPort(m_manglers[0], m_sparePorts[1], m_packetID);
m_currentState = DETECTIONSTATE_TEST4_2;
return FALSE;
}
Bool FirewallHelperClass::detectionTest4Stage2Update() {
m_mangledPorts[1] = getManglerResponse(m_packetID);
if (m_mangledPorts[1] == 0) {
if ((timeGetTime() - m_timeoutStart) > m_timeoutLength) {
closeSpareSocket(m_sparePorts[0]);
closeSpareSocket(m_sparePorts[1]);
m_currentState = DETECTIONSTATE_DONE;
DEBUG_LOG(("FirewallHelperClass::detectionTest4Stage2Update - timed out waiting for the second mangler response, quitting\n"));
return TRUE;
}
return FALSE;
}
if (m_mangledPorts[1] != m_mangledPorts[0] + m_sourcePortAllocationDelta) {
DEBUG_LOG(("FirewallHelperClass::detectionTest4Stage2Update - NAT uses different source ports for different destination ports\n"));
UnsignedInt addbehavior = 0;
addbehavior = (UnsignedInt)FIREWALL_TYPE_DESTINATION_PORT_DELTA;
addbehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType) addbehavior;
} else {
DEBUG_ASSERTCRASH(m_mangledPorts[1] == m_mangledPorts[0] + m_sourcePortAllocationDelta, ("Problem getting the source port deltas."));
if (m_mangledPorts[1] == m_mangledPorts[0] + m_sourcePortAllocationDelta) {
DEBUG_LOG(("FirewallHelperClass::detectionTest4Stage2Update - NAT uses the same source port for different destination ports\n"));
} else {
DEBUG_LOG(("FirewallHelperClass::detectionTest4Stage2Update - Unable to complete destination port mangling test\n"));
DEBUG_CRASH(("Unable to complete destination port mangling test\n"));
}
}
m_currentState = DETECTIONSTATE_TEST5;
return detectionTest5Update();
}
Bool FirewallHelperClass::detectionTest5Update() {
/*
** We have done all the tests we *have* to. There's other info that it would be nice to know though.
**
** Test for the netgear bug behavior.
*/
#if (0)
// moved to before test 1. Moved because this flag could be specified for another firewall
// for testing purposes and never get this far because it has behavior that doesn't require
// all the tests to be performed.
// BGC 10/1/02
DEBUG_LOG(("FirewallHelperClass::detectionTest5Update - Testing for Netgear bug\n"));
/*
** See if the user specified a netgear firewall - that will save us the trouble.
*/
if (TheGlobalData->m_firewallSendDelay) {
UnsignedInt addbehavior = FIREWALL_TYPE_NETGEAR_BUG;
addbehavior |= (UnsignedInt)m_behavior;
m_behavior = (FirewallBehaviorType) addbehavior;
DEBUG_LOG(("FirewallHelperClass::detectionTest5Update - Netgear bug specified by command line or SendDelay flag\n"));
} else {
DEBUG_LOG(("FirewallHelperClass::detectionTest5Update - Netgear bug not specified\n"));
}
#endif // #if (0)
DEBUG_LOG(("FirewallHelperClass::detectionTest5Update - All done, behavior is: "));
if ((m_behavior & FIREWALL_TYPE_SIMPLE) != 0) {
DEBUG_LOG((" FIREWALL_TYPE_SIMPLE "));
}
if ((m_behavior & FIREWALL_TYPE_DUMB_MANGLING) != 0) {
DEBUG_LOG((" FIREWALL_TYPE_DUMB_MANGLING "));
}
if ((m_behavior & FIREWALL_TYPE_SMART_MANGLING) != 0) {
DEBUG_LOG((" FIREWALL_TYPE_SMART_MANGLING "));
}
if ((m_behavior & FIREWALL_TYPE_NETGEAR_BUG) != 0) {
DEBUG_LOG((" FIREWALL_TYPE_NETGEAR_BUG "));
}
if ((m_behavior & FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION) != 0) {
DEBUG_LOG((" FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION "));
}
if ((m_behavior & FIREWALL_TYPE_RELATIVE_PORT_ALLOCATION) != 0) {
DEBUG_LOG((" FIREWALL_TYPE_RELATIVE_PORT_ALLOCATION "));
}
if ((m_behavior & FIREWALL_TYPE_DESTINATION_PORT_DELTA) != 0) {
DEBUG_LOG((" FIREWALL_TYPE_DESTINATION_PORT_DELTA "));
}
DEBUG_LOG(("\n"));
m_currentState = DETECTIONSTATE_DONE;
return TRUE;
}
/***********************************************************************************************
* FHC::Get_NAT_Port_Allocation_Scheme -- Find out how a NAT is allocating ports *
* *
* *
* *
* INPUT: Number of ports we should analyze *
* List of original port numbers *
* List of mangled port numbers *
* relative_delta (out) Is the delta relative to the original port number? *
* looks_good (out) Do all the values point to the same delta? *
* *
* OUTPUT: Port allocation delta *
* *
* WARNINGS: None *
* *
* HISTORY: *
* 3/15/01 4:45PM ST : Created *
*=============================================================================================*/
Int FirewallHelperClass::getNATPortAllocationScheme(Int numPorts, UnsignedShort *originalPorts, UnsignedShort *mangledPorts, Bool &relativeDelta, Bool &looksGood)
{
DEBUG_ASSERTCRASH(numPorts > 3, ("numPorts too small"));
DEBUG_LOG(("Looking for port allocation pattern in originalPorts %d, %d, %d, %d\n", originalPorts[0], originalPorts[1], originalPorts[2], originalPorts[3]));
/*
** Sort the mangled ports into order - should be easier to detect patterns.
** Stupid bubble sort will do. original_ports may be out of oder after the sort.
*/
for (Int x=0 ; x mangledPorts[y+1]) {
Int temp = mangledPorts[y];
mangledPorts[y] = mangledPorts[y+1];
mangledPorts[y+1] = temp;
temp = originalPorts[y];
originalPorts[y] = originalPorts[y+1];
originalPorts[y+1] = temp;
}
}
}
/*
** Now start looking for patterns in the port numbers. Possible patterns include.
**
** Incremental. Port numbers are allocated incrementally.
** Every 'n' ports. NAT adds 'n' port numbers when allocating ports.
**
** Also, schemes may be absolute or relative to the original port number.
*/
/*
** 1. Check for absolute sequential allocation.
*/
if (mangledPorts[1] - mangledPorts[0] == 1) {
if (mangledPorts[2] - mangledPorts[1] == 1) {
if (mangledPorts[3] - mangledPorts[2] == 1) {
DEBUG_LOG(("Incremental port allocation detected\n"));
relativeDelta = FALSE;
looksGood = TRUE;
return(1);
}
}
}
/*
** 2. Check for semi sequential.
*/
if (mangledPorts[1] - mangledPorts[0] == 2) {
if (mangledPorts[2] - mangledPorts[1] == 2) {
if (mangledPorts[3] - mangledPorts[2] == 2) {
DEBUG_LOG(("Semi-incremental port allocation detected\n"));
relativeDelta = FALSE;
looksGood = TRUE;
return(2);
}
}
}
Int diff1 = mangledPorts[1] - mangledPorts[0];
Int diff2 = mangledPorts[2] - mangledPorts[1];
Int diff3 = mangledPorts[3] - mangledPorts[2];
/*
** 3. Check for absolute scheme skipping 'n' ports.
*/
if (diff1 == diff2 && diff2 == diff3) {
DEBUG_LOG(("Looks good for absolute allocation sequence delta of %d\n", diff1));
relativeDelta = FALSE;
looksGood = TRUE;
return(diff1);
}
if (diff1 == diff2) {
DEBUG_LOG(("Probable absolute allocation sequence delta of %d\n", diff1));
relativeDelta = FALSE;
looksGood = FALSE;
return(diff1);
}
if (diff2 == diff3) {
DEBUG_LOG(("Probable absolute allocation sequence delta of %d\n", diff2));
relativeDelta = FALSE;
looksGood = FALSE;
return(diff2);
}
/*
** Insert more tests here if we can think of any!!!!!
*/
/*
** 4. Check for relative scheme skipping 'n' ports. NAT32 behaves this way, it skips 100 ports
** each time.
*/
for (Int i=0 ; iBind((UnsignedInt)0, port) != 0) {
DEBUG_CRASH(("FirewallHelperClass::openSpareSocket - Failed to init spare socket"));
return FALSE;
}
m_spareSockets[i].port = port;
DEBUG_LOG(("FirewallHelperClass::openSpareSocket - port %d is open for send\n", port));
return TRUE;
}
/*
* closeSpareSocket - closes a socket at a specific port.
*/
void FirewallHelperClass::closeSpareSocket(UnsignedShort port) {
for (Int i = 0; i < MAX_SPARE_SOCKETS; ++i) {
if (m_spareSockets[i].port == port) {
if (m_spareSockets[i].udp != NULL) {
delete m_spareSockets[i].udp;
m_spareSockets[i].udp = NULL;
}
m_spareSockets[i].port = 0;
break;
}
}
}
/*
* closeAllSpareSockets - closes all spare sockets, duh.
*/
void FirewallHelperClass::closeAllSpareSockets() {
for (Int i = 0; i < MAX_SPARE_SOCKETS; ++i) {
if (m_spareSockets[i].port != 0) {
m_spareSockets[i].port = 0;
}
if (m_spareSockets[i].udp != NULL) {
delete (m_spareSockets[i].udp);
m_spareSockets[i].udp = NULL;
}
}
}