/*
**	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.cpp                             $*
 *                                                                                             *
 *                      $Author:: Steve_t                                                     $*
 *                                                                                             *
 *                     $Modtime:: 3/26/02 3:04p                                               $*
 *                                                                                             *
 *                    $Revision:: 37                                                          $*
 *                                                                                             *
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 *                                                                                             *
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 *                                                                                             *
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

/*
** Disable warning about exception handling not being enabled.
*/
#pragma warning(disable : 4530)

#include	"always.h"
#include	<windows.h>
#include "systimer.h"
#include <snmp.h>
#include	<stddef.h>
#include <process.h>


#include "crandom.h"
#include "except.h"
#include	"nat.h"
#include	"natsock.h"
#include	"nataddr.h"
#include "slavemaster.h"

#include	"gamedata.h"

/*
** Set this flag in the debugger to cause the client to fail to connect.
*/
//bool CrapFlag = false;

/*
** Single instance of firewall helper class.
*/
FirewallHelperClass FirewallHelper;


/***********************************************************************************************
 * FirewallHelperClass::FirewallHelperClass -- Constructor                                     *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/15/01 5:03PM ST : Created                                                               *
 *=============================================================================================*/
FirewallHelperClass::FirewallHelperClass(void)
{
	Behavior = FIREWALL_TYPE_UNKNOWN;
	LastBehavior = FIREWALL_TYPE_UNKNOWN;
	SourcePortAllocationDelta = 0;
	LastSourcePortAllocationDelta = 0;
	NumManglerServers = 0;
	CurrentManglerServer = -1;
	SourcePortPool = 0;
	ThreadActive = false;
	ThreadState = THREAD_IDLE;
	ThreadEvent = INVALID_HANDLE_VALUE;
	NATThreadMutex = CreateMutex(NULL, false, NULL);
	NATDataMutex = CreateMutex(NULL, false, NULL);
	ThreadHandle = INVALID_HANDLE_VALUE;
	QueueNotifyPtr = NULL;
	SuccessFlagPtr = NULL;
	CancelPlayer[0] = 0;
	SendDelay = false;
	Confidence = 0;
}




/***********************************************************************************************
 * FirewallHelperClass::Startup -- Start up the firewall thread                                *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/31/2001 12:35PM ST : Created                                                            *
 *=============================================================================================*/
void FirewallHelperClass::Startup(void)
{
	/*
	** Start the firewall thread.
	*/
	WWDEBUG_SAY(("FirewallHelper: Starting firewall thread\n"));
	ThreadState = THREAD_IDLE;
	ThreadEvent = INVALID_HANDLE_VALUE;
	ThreadActive = true;
	//ThreadHandle = CreateThread(NULL, 128*1024, &NAT_Thread_Start, this, 0, &ThreadID);
	ThreadHandle = (HANDLE)_beginthreadex(NULL, 128*1024, &NAT_Thread_Start, this, 0, (unsigned int*)&ThreadID);
	fw_assert(ThreadHandle != NULL);
}




/***********************************************************************************************
 * FirewallHelperClass::Shutdown -- Shut down the firewall thread                              *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/31/2001 12:35PM ST : Created                                                            *
 *=============================================================================================*/
void FirewallHelperClass::Shutdown(void)
{
	if (ThreadActive) {
		WWDEBUG_SAY(("FirewallHelper: Stopping firewall thread\n"));

		/*
		** Signal the thread to go away.
		*/
		ThreadActive = false;

		/*
		** Wait for the thread to go away.
		*/
		int deadlock = WaitForSingleObject(NATThreadMutex, 5 * 1000);
		if (deadlock == WAIT_TIMEOUT) {
			WWDEBUG_SAY(("FirewallHelperClass - Timeout waiting for firewall thread mutex\n"));
			fw_assert(deadlock != WAIT_TIMEOUT);
		} else {
			ReleaseMutex(NATThreadMutex);
		}
	}

	// Release the thread
	if (INVALID_HANDLE_VALUE != ThreadHandle) {
		CloseHandle(ThreadHandle);
		ThreadHandle = INVALID_HANDLE_VALUE;
	}
}



/***********************************************************************************************
 * FirewallHelperClass::Set_Firewall_Info -- Set firewall info from the game                   *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    last_behavior - how the firewall behaved last time we tried it                    *
 *           last_delta - the firewalls last known good port delta                             *
 *           port_pool - base port number to use when allocating temporary ports               *
 *           send_delay - value of send delay flag used for netgear firewalls.                 *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/20/2001 12:16PM ST : Created                                                            *
 *=============================================================================================*/
void FirewallHelperClass::Set_Firewall_Info(unsigned long last_behavior, int last_delta, unsigned short port_pool, bool send_delay, int confidence)
{
	if (port_pool) {
		SourcePortPool = port_pool;
	} else {
		if (SourcePortPool == 0) {
			SourcePortPool = PORT_POOL_MIN + FreeRandom.Get_Int(0, 32768);
		}
	}

	LastBehavior = (FirewallBehaviorType) last_behavior;
	LastSourcePortAllocationDelta = last_delta;
	SendDelay = send_delay;
	Confidence = confidence;
}



/***********************************************************************************************
 * FirewallHelperClass::Set_Firewall_Info -- Set firewall info from the game                   *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    last_behavior - how the firewall behaved last time we tried it                    *
 *           last_delta - the firewalls last known good port delta                             *
 *           port_pool - base port number to use when allocating temporary ports               *
 *           send_delay - value of send delay flag used for netgear firewalls.                 *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/20/2001 12:16PM ST : Created                                                            *
 *=============================================================================================*/
void FirewallHelperClass::Get_Firewall_Info(unsigned long &last_behavior, int &last_delta, unsigned short &port_pool, bool &send_delay, int &confidence) const
{
	port_pool = SourcePortPool;
	last_behavior = (unsigned long) LastBehavior;
	last_delta = LastSourcePortAllocationDelta;
	send_delay = SendDelay;
	confidence = Confidence;
}




/***********************************************************************************************
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/7/2001 2:15PM ST : Created                                                              *
 *=============================================================================================*/
FirewallHelperClass::~FirewallHelperClass(void)
{
	Shutdown();

	CloseHandle(NATThreadMutex);
	CloseHandle(NATDataMutex);
}



/***********************************************************************************************
 * FirewallHelperClass::NAT_Thread_Start -- Thread start for NAT thread                        *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    This ptr                                                                          *
 *                                                                                             *
 * OUTPUT:   Undefined                                                                         *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/7/2001 2:39PM ST : Created                                                              *
 *=============================================================================================*/
unsigned int __stdcall FirewallHelperClass::NAT_Thread_Start(void *thisptr)
{
	unsigned int thread_exit_code = 0;

	Register_Thread_ID(GetCurrentThreadId(), "Firewall thread");

	__try {
		thread_exit_code = ((FirewallHelperClass*)thisptr)->NAT_Thread_Main_Loop();
	} __except(Exception_Handler(GetExceptionCode(), GetExceptionInformation())) {};

	Unregister_Thread_ID(GetCurrentThreadId(), "Firewall thread");

	return(thread_exit_code);
}


/***********************************************************************************************
 * FirewallHelperClass::NAT_Thread_Main_Loop -- Main loop for NAT thread                       *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/7/2001 2:42PM ST : Created                                                              *
 *=============================================================================================*/
unsigned long FirewallHelperClass::NAT_Thread_Main_Loop(void)
{
	/*
	** Take ownership of the thread mutex.
	*/
	int deadlock = WaitForSingleObject(NATThreadMutex, 10 * 1000);
	if (deadlock == WAIT_TIMEOUT) {
		WWDEBUG_SAY(("FirewallHelperClass - Timeout waiting for thread mutex\n"));
		fw_assert(deadlock != WAIT_TIMEOUT);
	}

	/*
	** Thread main loop.
	*/
	while (ThreadActive) {

		/*
		** Always do some sleeping here since this thread is a relatively low priority compared to stuff like running the
		** game engine.
		** If we are not doing anything much then sleep for longer.
		*/
		if (ThreadState == THREAD_IDLE && ThreadQueue.Count() == 0) {
			Sleep(50);
		} else {
			Sleep(0);
		}

		/*
		** Check for incoming private game options packets.
		*/
		Process_Game_Options();

		/*
		** Lock data access while we process each loop.
		*/
		ThreadLockClass locker(this);

		switch (ThreadState) {

			/*
			** Idle state, no activity required.
			*/
			case THREAD_IDLE:
				/*
				** See if there is a pending thread action to execute.
				*/
				if (ThreadQueue.Count()) {
					ThreadState = ThreadQueue[0].ThreadAction;
					ThreadEvent = ThreadQueue[0].ThreadEvent;
					//delete ThreadQueue[0];
					ThreadQueue.Delete(0);
				} else {
					if (WOLNATInterface.Am_I_Server()) {
						if (ClientQueue.Count()) {
							WWDEBUG_SAY(("Client with no thread action!\n"));
							Add_Thread_Action(THREAD_CONNECT_FIREWALL, INVALID_HANDLE_VALUE);
						}
					}
				}
				break;

			/*
			** Time to connect to another player.
			*/
			case THREAD_CONNECT_FIREWALL:
			{
				int connected = Negotiate_Port();
				if (connected == FW_RESULT_SUCCEEDED && !WOLNATInterface.Am_I_Server()) {
					WOLNATInterface.Set_Server_Negotiated_Address(&PlayersFirewallAddress);
				}
				Set_Client_Success(connected);
				ThreadState = THREAD_CONNECT_FIREWALL_DONE;
				break;
			}

			/*
			** Connection done, signal as required.
			*/
			case THREAD_CONNECT_FIREWALL_DONE:
				Set_Thread_Event();
				Send_Queue_States();
				ThreadState = THREAD_IDLE;
				break;

			/*
			** Detect the firewall settings.
			*/
			case THREAD_DETECT_FIREWALL:
				Behavior = Detect_Firewall_Behavior();
				WWDEBUG_SAY(("FirewallHelper: Behavior is = %08x\n", (unsigned long)Behavior));
				ThreadState = THREAD_DETECT_FIREWALL_DONE;
				Set_Thread_Event();
				break;

			/*
			** Detection done, signal as required.
			*/
			case THREAD_DETECT_FIREWALL_DONE:
				Set_Thread_Event();
				ThreadState = THREAD_IDLE;
				break;

			/*
			** Get the local chat connection address.
			*/
			case THREAD_GET_LOCAL_ADDRESS:
				Get_Local_Chat_Connection_Address();
				ThreadState = THREAD_IDLE;
				break;

			/*
			** We finished getting the local chat connection address.
			*/
			case THREAD_GET_LOCAL_ADDRESS_DONE:
				ThreadState = THREAD_IDLE;
				Set_Thread_Event();
				break;

			default:
				fw_assert(false);
				break;
		}

	}

	ReleaseMutex(NATThreadMutex);
	return(0);
}




/***********************************************************************************************
 * FirewallHelperClass::Reset -- Causes a re-detect of the firewall                            *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/29/01 1:04AM ST : Created                                                               *
 *=============================================================================================*/
void FirewallHelperClass::Reset(void)
{
	ThreadLockClass locker(this);
	Behavior = FIREWALL_TYPE_UNKNOWN;
}




/***********************************************************************************************
 * FirewallHelperClass::Reset_Server -- Reset server side variables                            *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/10/2001 3:23PM ST : Created                                                             *
 *=============================================================================================*/
void FirewallHelperClass::Reset_Server(void)
{
	ConnectionHistory.Delete_All();
	MangledPortHistory.Delete_All();
}



/***********************************************************************************************
 * FirewallHelperClass::Detect_Firewall -- See what our firewall is up to                      *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/15/01 6:47PM ST : Created                                                               *
 *=============================================================================================*/
void FirewallHelperClass::Detect_Firewall(HANDLE event)
{
	ThreadLockClass locker(this);

	if (Behavior == FIREWALL_TYPE_UNKNOWN) {
		Add_Thread_Action(THREAD_DETECT_FIREWALL, event);
	} else {
		SetEvent(event);
	}
}



/***********************************************************************************************
 * FirewallHelperClass::Connected_To_WWOnline_Server                                           *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/7/2001 10:02PM ST : Created                                                             *
 *=============================================================================================*/
void FirewallHelperClass::Connected_To_WWOnline_Server(void)
{
	/*
	** Don't need to go through this again if we already got the local chat connection address.
	*/
	if (!LocalChatConnectionAddress.Is_Valid()) {

		/*
		** Get the local chat connection address.
		*/
		ThreadLockClass locker(this);
		Add_Thread_Action(THREAD_GET_LOCAL_ADDRESS, INVALID_HANDLE_VALUE);
	}
}







/***********************************************************************************************
 * FHC::Get_Next_Temporary_Source_Port -- 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                                                              *
 *=============================================================================================*/
unsigned short FirewallHelperClass::Get_Next_Temporary_Source_Port(int skip)
{
	unsigned short return_port = (unsigned short) 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--) {

		SourcePortPool += skip;
		return_port = (unsigned short) SourcePortPool;

		if (SourcePortPool > PORT_POOL_MAX) {
			SourcePortPool = PORT_POOL_MIN;
		}

		/*
		** Validate the port by trying to bind it to a socket.
		*/
		SocketHandlerClass *temp = new SocketHandlerClass;
		bool result = temp->Open(return_port, 0);
		delete temp;
		if (result) {
			return(return_port);
		}
	}

	return(return_port);

}



/***********************************************************************************************
 * FHC::Send_To_Mangler -- Send to the mangler from the specified socket handler               *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Address of mangler server                                                         *
 *           Socket handler to use when sending                                                *
 *           ID to insert into the packet                                                      *
 *           blitzme flag (obsolete, must be false)                                            *
 *                                                                                             *
 * OUTPUT:   True if sent OK                                                                   *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/15/01 12:47PM ST : Created                                                              *
 *=============================================================================================*/
bool FirewallHelperClass::Send_To_Mangler(IPAddressClass *address, SocketHandlerClass *socket_handler, unsigned long packet_id, bool blitzme)
{
	/*
	** Build the packet to send out.
	*/
	unsigned char packet_buf[512];
	int packet_size = Build_Mangler_Packet(packet_buf, socket_handler->Get_Incoming_Port(), packet_id, blitzme);

	WWDEBUG_SAY(("FirewallHelper: Sending from port %d to %s\n", (unsigned int)(socket_handler->Get_Incoming_Port()), address->As_String()));

	/*
	** Send it.
	*/
	socket_handler->Write(packet_buf, packet_size, address, address->Get_Port());
	return(true);
}




/***********************************************************************************************
 * FHC::Get_Mangler_Response -- Get the manglers response to a specific query                  *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Packet id of packet we are looking for                                            *
 *           Ptr to socket handler class to query for packets                                  *
 *           Timeout, 0=default                                                                *
 *           service_all - true if all sockets should be serviced                              *
 *                                                                                             *
 * OUTPUT:   Port the mangler saw this packet come from.                                       *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/15/01 12:51PM ST : Created                                                              *
 *=============================================================================================*/
unsigned short FirewallHelperClass::Get_Mangler_Response(unsigned long packet_id, SocketHandlerClass *socket_handler, int time, bool all_service)
{
	/*
	** Locals.
	*/
	int peek_packet = 0;
	CnCPacketType *packet;
	unsigned char packet_buf[1024];
	int packet_size = sizeof(packet_buf);
	IPAddressClass address;
	unsigned long id;
	unsigned long timeout = TIMEGETTIME();
	unsigned char temp_address[4];
	unsigned short temp_port;

	/*
	** Asserts.
	*/
	fw_assert(socket_handler != NULL);

	if (time) {
		timeout += (unsigned long) time;
	} else {
		timeout += (unsigned long) TIMER_SECOND;
	}

	while (timeout > TIMEGETTIME()) {

		if (all_service) {
			SocketHandlerClass::Service_All();
		} else {
			socket_handler->Service();
		}

		/*
		** Get a copy of the global packet we want to examine. This doesn't remove it from the queue.
		*/
		int result = socket_handler->Peek(packet_buf, packet_size, &temp_address, &temp_port, peek_packet);

		/*
		** Check if we got a packet from the mangler.
		*/
    	if (result) {

			packet = (CnCPacketType*)packet_buf;

			if (packet->Packet.Command == NET_MANGLER_RESPONSE) {

				WWDEBUG_SAY(("FirewallHelper: Got NET_MANGLER_RESPONSE packet\n"));

				/*
				** We got a mangler packet, see if it's the one we are looking for.
				*/
#ifdef WWDEBUG
				int original_port = packet->Packet.ManglerData.OriginalPortNumber;
#endif //WWDEBUG
				id = packet->GHeader.Header.PacketID;

				if (packet_id == id) {

					/*
					** Looks good, lets get the mangled port number out.
					*/
					unsigned short mangled_port = packet->Packet.ManglerData.MangledPortNumber;
					WWDEBUG_SAY(("FirewallHelper: Mangler is seeing packets from port %d as coming from port %d\n", (unsigned int)original_port, (unsigned int)mangled_port));

					/*
					** Now the mangled address. This should never change.
					*/
					ExternalAddress.Set_Address(packet->Packet.ManglerData.MangledAddress);
					WWDEBUG_SAY(("FirewallHelper: Mangler is seeing our packets coming from address %s\n", ExternalAddress.As_String()));

					/*
					** Remove the packet from the queue.
					*/
					result = socket_handler->Read(packet_buf, packet_size, &temp_address, &temp_port, peek_packet);
					fw_assert(result != NULL);
					return(mangled_port);
				}
			}
			peek_packet++;
		} else {
			peek_packet = 0;
		}

		Sleep(0);
	}
	return(0);
}




/***********************************************************************************************
 * FHC::Detect_Firewall_Behavior -- 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                                                              *
 *=============================================================================================*/
FirewallHelperClass::FirewallBehaviorType FirewallHelperClass::Detect_Firewall_Behavior(void)
{
	unsigned short mangler_port = 4321;
	char temp_mangler_name[128];
	unsigned long packet_id = 0x7f000000;

	/*
	** Well, we are going to need some manglers.
	*/
	static char _manglername[5][80] = {
		"mangler1.westwood.com",
		"mangler2.westwood.com",
		"mangler3.westwood.com",
		"mangler.westwood.com",
		""
	};

	unsigned char mangler_addresses[4][4];
	int num_mangler_addresses = 0;
	FirewallBehaviorType behavior = FIREWALL_TYPE_SIMPLE;
	IPAddressClass manglers[4];
	unsigned long timeout;
	int mangler_index_offset = 0;

	unsigned short source_ports[NUM_TEST_PORTS];
	unsigned short mangled_ports[NUM_TEST_PORTS];
	SocketHandlerClass *port_sockets[NUM_TEST_PORTS];
	int delta = 0;

	bool save_firewall_type = true;
	bool looks_good = true;

	/*
	** Figure out which mangler to use this time. Pick randomly from the available manglers.
	*/
	NumManglerServers = WOLNATInterface.Get_Num_Mangler_Servers();
	if (CurrentManglerServer == -1) {
		CurrentManglerServer = FreeRandom.Get_Int(0, NumManglerServers-1);
		WWDEBUG_SAY(("FirewallHelper - Using mangler server %d\n", CurrentManglerServer));
	}

	/*
	** If the user specified a particular port to use then we act as if there is no firewall.
	*/
	if (WOLNATInterface.Get_Force_Port()) {
		behavior = FIREWALL_TYPE_SIMPLE;
		WWDEBUG_SAY(("FirewallHelper: Source port %d specified by user\n", WOLNATInterface.Get_Force_Port()));

		if (SendDelay) {
			unsigned long addbehavior = FIREWALL_TYPE_NETGEAR_BUG;
			addbehavior |= (unsigned long)behavior;
			behavior = (FirewallBehaviorType) addbehavior;
			WWDEBUG_SAY(("FirewallHelper: Netgear bug specified by command line or SendDelay flag\n"));
		}
		return(behavior);
	}

	/*
	** Slave servers inherit their firewall info from the master server.
	*/
	if (SlaveMaster.Am_I_Slave()) {
		behavior = LastBehavior;
		SourcePortAllocationDelta = LastSourcePortAllocationDelta;
		ExternalAddress.Set_Address(WOLNATInterface.Get_Reg_External_IP(), WOLNATInterface.Get_Reg_External_Port());
		return(behavior);
	}

	timeout = TIMEGETTIME() + (5*TIMER_SECOND);
	WWDEBUG_SAY(("FirewallHelper: About to call gethostbyname for the mangler address\n"));
	int namenum = 0;

	/*
	** Convert the list of mangler servers to numeric IP addresses.
	**
	*/
	do {
		char *mangler_name_ptr = &_manglername[namenum][0];

		/*
		** Use the wolapi supplied mangler info if available.
		*/
		bool got_name = WOLNATInterface.Get_Mangler_Name_By_Index(namenum, temp_mangler_name);

		if (got_name) {
			mangler_name_ptr = temp_mangler_name;
			unsigned short servserv_port = WOLNATInterface.Get_Mangler_Port_By_Index(namenum);
			fw_assert(servserv_port != 0);
			if (servserv_port) {
				mangler_port = servserv_port;
			}
			WWDEBUG_SAY(("FirewallHelper - Using mangler server from servserv - address is %s ; %d\n", mangler_name_ptr, mangler_port));
		} else {
			WWDEBUG_SAY(("FirewallHelper - Using default mangler name for mangler %d\n", namenum));
		}

		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) {
			WWDEBUG_SAY(("FirewallHelper: 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 ; i<num_mangler_addresses ; i++) {
			if (memcmp(mangler_addresses[i], &host_info->h_addr_list[0][0], 4) == 0) {
				found = true;
				break;
			}
		}
		/*
		** Add the address in if we didn't find it.
		*/
		if (!found) {
			int m = num_mangler_addresses++;
			memcpy(&mangler_addresses[m][0], &host_info->h_addr_list[0][0], 4);
			WWDEBUG_SAY(("FirewallHelper: 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 (num_mangler_addresses < 4 && TIMEGETTIME() < timeout);


	/*
	** We need a certain number of manglers to be able to do the detection.
	*/
	fw_assert(num_mangler_addresses > 2);
	if (num_mangler_addresses < 3) {
		WWDEBUG_SAY(("FirewallHelper: Not enough manglers - returning default behavior\n"));
		if (Confidence > 0) {
			behavior = LastBehavior;
			SourcePortAllocationDelta = LastSourcePortAllocationDelta;
		}
		return(behavior);
	}

	/*
	** Convert the mangler addresses to IPAddressClass format.
	*/
	for (int i=0 ; i<num_mangler_addresses ; i++) {
		unsigned char addr[4];
		memcpy(addr, &mangler_addresses[i][0], 4);
		manglers[i].Set_Address(addr, mangler_port);
	}


	/*
	** OK, we have our manglers.
	**
	** First test, see if there is any port mangling at all.
	**
	**
	**
	*/

	/*
	** Get a spare port number and create a new socket to bind it to.
	*/
	unsigned short port = Get_Next_Temporary_Source_Port(0);
	SocketHandlerClass socket;
	if (!socket.Open(port, 4321)) {
		WWDEBUG_SAY(("FirewallHelper: Unable to open temp source port - returning default behavior\n"));
		return(behavior);
	}

	/*
	** Send to the mangler from this port until we get a response.
	*/
	timeout = TIMEGETTIME() + TIMER_SECOND * 6;
	unsigned short mangled_port = 0;
	while (TIMEGETTIME() < timeout && mangled_port == 0) {
		Send_To_Mangler(&manglers[0], &socket, packet_id);
		mangled_port = Get_Mangler_Response(packet_id, &socket);
	}

	if (mangled_port == 0) {
		/*
		** No response from mangler 0. That's bad. I guess we could try the other manglers.
		** We already tried the first one so skip that. Don't use the last one either since that will be needed later to detect
		** port per ip behavior.
		*/
		for (int m=1 ; m<num_mangler_addresses - 1 ; m++) {
			mangler_index_offset = m;
			timeout = TIMEGETTIME() + TIMER_SECOND * 3;
			while (TIMEGETTIME() < timeout && mangled_port == 0) {
				Send_To_Mangler(&manglers[mangler_index_offset], &socket, packet_id);
				mangled_port = Get_Mangler_Response(packet_id, &socket);
			}
		}
	}

	/*
	** See if we got no response or a non-mangled response.
	*/
	if (mangled_port == 0 || mangled_port == port) {
		if (mangled_port == port) {
			SourcePortAllocationDelta = 0;
			LastSourcePortAllocationDelta = 0;
			LastBehavior = FIREWALL_TYPE_SIMPLE;
			Confidence = 0;
		} else {
			WWASSERT(mangled_port == 0);
			if (Confidence) {
				SourcePortAllocationDelta = LastSourcePortAllocationDelta;
				return(LastBehavior);
			}
		}
		return(FIREWALL_TYPE_SIMPLE);
	}


	/*
	**
	** 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.
	*/
	timeout = TIMEGETTIME() + (TIMER_SECOND * 6);
	unsigned short second_ip_mangled_port = 0;
	while (TIMEGETTIME() < timeout && second_ip_mangled_port == 0) {
		Send_To_Mangler(&manglers[mangler_index_offset + 1], &socket, packet_id+1);
		second_ip_mangled_port = Get_Mangler_Response(packet_id+1, &socket);
	};

	/*
	** We are done with this socket/port
	*/
	socket.Close();

	/*
	** See if we got no response or a non-mangled response.
	*/
	if (second_ip_mangled_port == 0 || second_ip_mangled_port == port) {
		WWDEBUG_SAY(("FirewallHelper: No response or response is unmangled - returning simple firewall behavior\n"));
		return(FIREWALL_TYPE_SIMPLE);
	}

	/*
	** Got a mangled port. Is it the same as the last one (mangles the same regardless of destination IP).
	*/
	if (mangled_port == second_ip_mangled_port) {
		behavior = FIREWALL_TYPE_DUMB_MANGLING;
		WWDEBUG_SAY(("FirewallHelper: Source ports mangled the same way regardless of destination IP\n"));
	} else {
		behavior = FIREWALL_TYPE_SMART_MANGLING;
		WWDEBUG_SAY(("FirewallHelper: Source ports mangled differently per destination IP\n"));
	}




	/*
	** Third test.
	**
	** This test tries to detect a pattern in the ports allocated by the NAT.
	** We use several source ports for this one.
	**
	*/

	/*
	** Try this whole thing a max of 3 times.
	*/
	int try_again;
	for (try_again = 0 ; try_again < 3 ; try_again++) {

		memset(source_ports, 0, sizeof(source_ports));
		memset(mangled_ports, 0, sizeof(source_ports));
		memset(port_sockets, 0, sizeof(port_sockets));

		/*
		** 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.
		*/
		bool port_open_failed = false;
		for (i=0 ; i<NUM_TEST_PORTS ; i++) {
			source_ports[i] = Get_Next_Temporary_Source_Port(i);
			port_sockets[i] = new SocketHandlerClass;
			if (!port_sockets[i]->Open(source_ports[i], 4321)) {

				port_open_failed = true;

				/*
				** Close any spare ports we allocated already before we bail.
				*/
				for (int j=0 ; j<i ; j++) {
					if (port_sockets[j]) {
						delete port_sockets[j];
						port_sockets[j] = NULL;
					}
				}
				WWDEBUG_SAY(("FirewallHelper: Unable to open all source ports for allocation pattern test - returning default behavior\n"));
				break;	//return(behavior);
			}
		}

		if (port_open_failed) {
			continue;
		}


		/*
		** OK, lets get some numbers from the mangler.
		**
		** Keep sending from each port until we get a response to all the sends. There's a implied
		** delay between initial sends due to the timeout in Get_Mangler_Response.
		*/
		int num_responses = 0;
		packet_id = packet_id + 10;
		timeout = TIMEGETTIME() + (TIMER_SECOND * 12);

		while (TIMEGETTIME() < timeout && num_responses < NUM_TEST_PORTS) {
			for (i=0 ; i<NUM_TEST_PORTS ; i++) {
				if (mangled_ports[i] == 0) {
					Send_To_Mangler(&manglers[mangler_index_offset], port_sockets[i], packet_id+i);
					mangled_ports[i] = Get_Mangler_Response(packet_id+i, port_sockets[i], 0, true);
					if (mangled_ports[i] != 0) {
						num_responses++;
					}
				}
			}
		}

		/*
		** Close down those sockets - we are finished with them.
		*/
		for (int j=0 ; j<i ; j++) {
			if (port_sockets[j]) {
				delete port_sockets[j];
				port_sockets[j] = NULL;
			}
		}

		/*
		** We need at least 4 responses to be sure of the port allocation scheme.
		*/
		if (num_responses < 4) {
			if (LastSourcePortAllocationDelta != 0 && (int)LastBehavior > (int)FIREWALL_TYPE_SIMPLE) {
				/*
				** If the delta we got last time we played looks good then use that.
				*/
				SourcePortAllocationDelta = LastSourcePortAllocationDelta;
				WWDEBUG_SAY(("FirewallHelper: Unable to verify responses from 4 mangler servers - returning default behavior\n"));
				return(behavior);
			}
			/*
			** Try again.
			*/
			continue;
		}


		bool relative_delta = false;
		delta = Get_NAT_Port_Allocation_Scheme(num_responses, source_ports, mangled_ports, relative_delta, looks_good);

		if (delta) {

			/*
			** Hey, we got it!
			*/
			unsigned long addbehavior = 0;
			if (relative_delta) {
				addbehavior = (unsigned long)FIREWALL_TYPE_RELATIVE_PORT_ALLOCATION;
			} else {
				addbehavior = (unsigned long)FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION;
			}
			addbehavior |= (unsigned long)behavior;
			behavior = (FirewallBehaviorType) addbehavior;

			SourcePortAllocationDelta = delta;
			if (!looks_good) {
				save_firewall_type = false;
			}
			break;
		} else {
			if (LastSourcePortAllocationDelta != 0 && (int)LastBehavior > (int)FIREWALL_TYPE_SIMPLE) {
				/*
				** If the delta we got last time we played looks good then use that.
				*/
				SourcePortAllocationDelta = LastSourcePortAllocationDelta;
				behavior = LastBehavior;
				save_firewall_type = false;
				break;
			}
		}
	}

	/*
	** See if we screwed up.
	*/
	if (try_again == 3) {
		/*
		** We know it mangles differently per IP but we don't know how. Make something up.
		*/
		SourcePortAllocationDelta = 1;
		save_firewall_type = false;
	}


	/*
	** Test to see if the NAT mangles differently per destination port at the same IP.
	**
	**
	*/
	if ((behavior & FIREWALL_TYPE_SMART_MANGLING) != 0) {

		if ((behavior & FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION) != 0) {

			WWDEBUG_SAY(("FirewallHelper: Testing to see if the NAT mangles differently per destination port at the same IP\n"));

			/*
			** We need 2 source ports for this.
			*/
			unsigned short port1 = Get_Next_Temporary_Source_Port(0);
			SocketHandlerClass socket1;
			if (!socket1.Open(port1, 4321)) {
				return(behavior);
			}

			unsigned short port2 = Get_Next_Temporary_Source_Port(0);
			SocketHandlerClass socket2;
			if (!socket2.Open(port2, 4321)) {
				return(behavior);
			}

			/*
			** Get a reference port.
			*/
			timeout = TIMEGETTIME() + (TIMER_SECOND * 4);
			mangled_port = 0;
			packet_id = packet_id + 10;

			/*
			** Wait for a response.
			*/
			while (TIMEGETTIME() < timeout && mangled_port == 0) {
				Send_To_Mangler(&manglers[mangler_index_offset], &socket1, packet_id);
				mangled_port = Get_Mangler_Response(packet_id, &socket1);
			}

			if (mangled_port == 0) {
				return(behavior);
			}

			/*
			** Send out to a different port at that IP.
			** We won't get a response for this.
			*/
			IPAddressClass addr = manglers[mangler_index_offset];
			addr.Set_Port(addr.Get_Port() + 1);
			Send_To_Mangler(&addr, &socket1, packet_id);
			Send_To_Mangler(&addr, &socket1, packet_id);
			Send_To_Mangler(&addr, &socket1, packet_id);

			/*
			** 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.
			*/
			packet_id++;
			unsigned short new_mangled_port = 0;
			timeout = TIMEGETTIME() + (TIMER_SECOND * 4);
			while (TIMEGETTIME() < timeout && new_mangled_port == 0) {
				Send_To_Mangler(&manglers[mangler_index_offset], &socket2, packet_id);
				new_mangled_port = Get_Mangler_Response(packet_id, &socket2);
			}

			if (new_mangled_port != 0) {
				if (new_mangled_port != mangled_port + SourcePortAllocationDelta) {
					WWDEBUG_SAY(("FirewallHelper: NAT uses different source ports for different destination ports\n"));

					unsigned long addbehavior = 0;
					addbehavior = (unsigned long)FIREWALL_TYPE_DESTINATION_PORT_DELTA;
					addbehavior |= (unsigned long)behavior;
					behavior = (FirewallBehaviorType) addbehavior;
				} else {
					fw_assert(new_mangled_port == mangled_port + SourcePortAllocationDelta);
					if (new_mangled_port == mangled_port + SourcePortAllocationDelta) {
						WWDEBUG_SAY(("FirewallHelper: NAT uses the same source port for different destination ports\n"));
					} else {
						WWDEBUG_SAY(("FirewallHelper: Unable to complete destination port mangling test\n"));
						fw_assert(false);
					}
				}
			}
		} else {
			/*
			** NAT32 uses different mangled source ports for different destination ports.
			*/
			unsigned long addbehavior = 0;
			addbehavior = (unsigned long)FIREWALL_TYPE_DESTINATION_PORT_DELTA;
			addbehavior |= (unsigned long)behavior;
			behavior = (FirewallBehaviorType) addbehavior;
		}
	}

	/*
	** See if the user specified a netgear firewall - that will save us the trouble of detecting it.
	*/
	if (SendDelay) {
		unsigned long addbehavior = FIREWALL_TYPE_NETGEAR_BUG;
		addbehavior |= (unsigned long)behavior;
		behavior = (FirewallBehaviorType) addbehavior;
		WWDEBUG_SAY(("FirewallHelper: Netgear bug specified by command line or SendDelay flag\n"));
	}

	/*
	** Remember this firewall type for next time.
	*/
	if (save_firewall_type) {
		LastBehavior = behavior;
		LastSourcePortAllocationDelta = SourcePortAllocationDelta;
		Confidence = 0;
	} else {

		/*
		** If we have high confidence in the last settings we detected then we might want to use those in preference.
		*/
		if (LastBehavior == behavior && LastSourcePortAllocationDelta != SourcePortAllocationDelta) {
			if (Confidence > 0) {

				/*
				** Only do this max 3 times before accepting the new delta.
				*/
				Confidence = (Confidence / 3) - 1;
				SourcePortAllocationDelta = LastSourcePortAllocationDelta;
			}
		}
	}

	return(behavior);
}





/***********************************************************************************************
 * 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::Get_NAT_Port_Allocation_Scheme(int num_ports, unsigned short *original_ports, unsigned short *mangled_ports, bool &relative_delta, bool &looks_good)
{
	fw_assert(num_ports > 3);

	WWDEBUG_SAY(("FirewallHelper: Looking for port allocation pattern in original_ports %d, %d, %d, %d\n", original_ports[0], original_ports[1], original_ports[2], original_ports[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<num_ports ; x++) {
		for (int y=0 ; y<num_ports-1 ; y++) {
			if (mangled_ports[y] > mangled_ports[y+1]) {
				int temp = mangled_ports[y];
				mangled_ports[y] = mangled_ports[y+1];
				mangled_ports[y+1] = temp;
				temp = original_ports[y];
				original_ports[y] = original_ports[y+1];
				original_ports[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 (mangled_ports[1] - mangled_ports[0] == 1) {
		if (mangled_ports[2] - mangled_ports[1] == 1) {
			if (mangled_ports[3] - mangled_ports[2] == 1) {
				WWDEBUG_SAY(("FirewallHelper: Incremental port allocation detected\n"));
				relative_delta = false;
				looks_good = true;
				return(1);
			}
		}
	}

	/*
	** 2. Check for semi sequential.
	*/
	if (mangled_ports[1] - mangled_ports[0] == 2) {
		if (mangled_ports[2] - mangled_ports[1] == 2) {
			if (mangled_ports[3] - mangled_ports[2] == 2) {
				WWDEBUG_SAY(("FirewallHelper: Semi-incremental port allocation detected\n"));
				relative_delta = false;
				looks_good = true;
				return(2);
			}
		}
	}

	int diff1 = mangled_ports[1] - mangled_ports[0];
	int diff2 = mangled_ports[2] - mangled_ports[1];
	int diff3 = mangled_ports[3] - mangled_ports[2];


	/*
	** 3. Check for absolute scheme skipping 'n' ports.
	*/
	if (diff1 == diff2 && diff2 == diff3) {
		WWDEBUG_SAY(("FirewallHelper: Looks good for absolute allocation sequence delta of %d\n", diff1));
		relative_delta = false;
		looks_good = true;
		return(diff1);
	}

	if (diff1 == diff2) {
		WWDEBUG_SAY(("FirewallHelper: Probable absolute allocation sequence delta of %d\n", diff1));
		relative_delta = false;
		looks_good = false;
		return(diff1);
	}

	if (diff2 == diff3) {
		WWDEBUG_SAY(("FirewallHelper: Probable absolute allocation sequence delta of %d\n", diff2));
		relative_delta = false;
		looks_good = 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 ; i<num_ports ; i++) {
		mangled_ports[i] -= original_ports[i];
	}

	diff1 = mangled_ports[1] - mangled_ports[0];
	diff2 = mangled_ports[2] - mangled_ports[1];
	diff3 = mangled_ports[3] - mangled_ports[2];

	/*
	** Look for a linear pattern.
	*/
	if (diff1 == diff2 && diff2 == diff3) {
		/*
		** Return a -ve result to indicate that port mangling is relative.
		*/
		WWDEBUG_SAY(("FirewallHelper: Looks good for a relative port range delta of %d\n", diff1));
		relative_delta = true;
		looks_good = true;
		return(diff1);
	}

	/*
	** Look for a broken pattern. Maybe the NAT skipped a whole range.
	*/
	if (diff1 == diff2 || diff1 == diff3) {
		WWDEBUG_SAY(("FirewallHelper: Detected probable broken relative port range delta of %d\n", diff1));
		relative_delta = true;
		looks_good = false;
		return(diff1);
	}

	if (diff2 == diff3) {
		WWDEBUG_SAY(("FirewallHelper: Detected probable broken relative port range delta of %d\n", diff2));
		relative_delta = true;
		looks_good = false;
		return(diff2);
	}


	/*
	** Aw hell, I don't know what it is.
	*/
	looks_good = false;
	relative_delta = false;
	return(0);
}






/***********************************************************************************************
 * FHC::Get_Firewall_Hardness -- How hard is it to connect to this firewall                    *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Firewall behavior                                                                 *
 *                                                                                             *
 * OUTPUT:   Hardness                                                                          *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/16/01 11:43AM ST : Created                                                              *
 *=============================================================================================*/
int FirewallHelperClass::Get_Firewall_Hardness(FirewallBehaviorType behavior)
{

	int hardness = 0;


	unsigned long fw = (unsigned long) behavior;

	if (((unsigned long)FIREWALL_TYPE_SIMPLE & fw) != 0) {
		hardness++;
	}

	if (((unsigned long)FIREWALL_TYPE_DUMB_MANGLING & fw) != 0) {
		hardness += 2;
	}

	if (((unsigned long)FIREWALL_TYPE_SMART_MANGLING & fw) != 0) {
		hardness += 3;
	}

	if (((unsigned long)FIREWALL_TYPE_NETGEAR_BUG & fw) != 0) {
		hardness += 10;
	}

	if (((unsigned long)FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION & fw) != 0) {
		hardness += 1;
	}

	if (((unsigned long)FIREWALL_TYPE_RELATIVE_PORT_ALLOCATION & fw) != 0) {
		hardness += 2;
	}

	return(hardness);
}





/***********************************************************************************************
 * FHC::Get_Firewall_Retries -- How many retries is it likely to take before we connect?       *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Firewall behavior                                                                 *
 *                                                                                             *
 * OUTPUT:   Hardness                                                                          *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/16/01 11:43AM ST : Created                                                              *
 *=============================================================================================*/
int FirewallHelperClass::Get_Firewall_Retries(FirewallBehaviorType behavior)
{

	int retries = 2;


	unsigned long fw = (unsigned long) behavior;

	if (((unsigned long)FIREWALL_TYPE_SIMPLE & fw) != 0) {
		retries++;
	}

	if (((unsigned long)FIREWALL_TYPE_DUMB_MANGLING & fw) != 0) {
		retries += 1;
	}

	if (((unsigned long)FIREWALL_TYPE_SMART_MANGLING & fw) != 0) {
		retries += 1;
	}

	if (((unsigned long)FIREWALL_TYPE_NETGEAR_BUG & fw) != 0) {
		//retries += 10;
	}

	if (((unsigned long)FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION & fw) != 0) {
		//retries += 1;
	}

	if (((unsigned long)FIREWALL_TYPE_RELATIVE_PORT_ALLOCATION & fw) != 0) {
		retries += 5;
	}

	return(retries);
}





/***********************************************************************************************
 * FHC::Get_Next_Mangled_Source_Port -- How will the firewall mangle our next source port      *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Source port that we will use                                                      *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/16/01 3:30PM ST : Created                                                               *
 *=============================================================================================*/
unsigned short FirewallHelperClass::Get_Next_Mangled_Source_Port(unsigned short source_port)
{

	/*
	** Locals.
	*/
	static unsigned long _packet_id = 0x7f100000;
	unsigned long timeout;
	int return_port = source_port;
	SocketHandlerClass socket;

	/*
	** If our firewall is stupid then just return the source port.
	*/
	unsigned long fw = (unsigned long) Behavior;
	if (fw == 0) {
		return(source_port);
	}
	if (((unsigned long)FIREWALL_TYPE_SIMPLE & fw) != 0) {
		return(source_port);
	}

	/*
	** If our NAT uses the same mangled source port regardless of the dest IP then we can use any previous connection to a different IP.
	*/
	if (WOLNATInterface.Am_I_Server() && ((unsigned long)FIREWALL_TYPE_DUMB_MANGLING & fw) != 0) {
		for (int h=0 ; h<ConnectionHistory.Count() ; h++) {

			/*
			** Make sure it isn't my address (should never be, even with players from behind the same firewall).
			*/
			if (!ConnectionHistory[h].Is_IP_Equal(ExternalAddress)) {
				WWDEBUG_SAY(("FirewallHelper - same port regardless of dest ip - using port %d from connection to %s ; %d\n", (unsigned long) MangledPortHistory[h], ConnectionHistory[h].As_String()));
				return(MangledPortHistory[h]);
			}
		}
	}


	/*
	** Get the address of a mangler server.
	*/
	unsigned short mangler_port = 4321;
	char mangler_name[256];
	strcpy(mangler_name, "mangler2.westwood.com");
	unsigned char maddress[4];

	if (NumManglerServers > 0) {
		fw_assert(CurrentManglerServer >= 0 && CurrentManglerServer < NumManglerServers);

		bool got_name = WOLNATInterface.Get_Mangler_Name_By_Index(CurrentManglerServer, mangler_name);

		if (got_name) {
			unsigned short servserv_port = WOLNATInterface.Get_Mangler_Port_By_Index(CurrentManglerServer);
			fw_assert(servserv_port != 0);
			if (servserv_port) {
				mangler_port = servserv_port;
			}
			WWDEBUG_SAY(("FirewallHelper - Using mangler server from servserv - address is %s ; %d\n", mangler_name, mangler_port));
		} else {
			WWDEBUG_SAY(("FirewallHelper - Using default mangler name for mangler %d\n", CurrentManglerServer));
		}
	}

	/*
	** Get the address to send the packets to.
	*/
	//WWDEBUG_SAY (("Firewall helper - About to call gethostbyname for %s\n", mangler_name));
	struct hostent *host_info = gethostbyname(mangler_name);

	if (!host_info) {
		WWDEBUG_SAY(("FirewallHelper - gethostbyname failed! Error code %d\n", WSAGetLastError()));
		return(source_port);
	}
	memcpy(maddress, &host_info->h_addr_list[0][0], 4);
	//WWDEBUG_SAY(("FirewallHelper: Mangler address is %d.%d.%d.%d ; %d\n", maddress[0], maddress[1], maddress[2], maddress[3], (int)mangler_port));
	IPAddressClass mangler_address;
	mangler_address.Set_Address(maddress, mangler_port);
	//WWDEBUG_SAY(("FirewallHelper: Mangler address is %s\n", mangler_address.As_String()));

	//WWDEBUG_SAY(("FirewallHelper: fw = %08x\n", fw));

	/*
	** Send to the mangler to establish a reference port.
	*/
	unsigned short port = Get_Next_Temporary_Source_Port(0);
	fw_assert(port != source_port);

	if (!socket.Open(port, 4321)) {
		return(source_port);
	}

	WWDEBUG_SAY(("FirewallHelper - Getting mangling for temp source port %d\n", (int)port));

	/*
	** Send to the mangler from this port until we get a response.
	*/
	timeout = TIMEGETTIME() + (TIMER_SECOND * 3);
	unsigned short mangled_port = 0;
	while (TIMEGETTIME() < timeout && mangled_port == 0) {
		Send_To_Mangler(&mangler_address, &socket, _packet_id);
		mangled_port = Get_Mangler_Response(_packet_id, &socket, TIMER_SECOND / 2);
		if (mangled_port != 0) {
			WWDEBUG_SAY(("FirewallHelper - Mangled port = %d\n", (int)mangled_port));
		}
	}

	_packet_id++;

	fw_assert(mangled_port != 0 && mangled_port != source_port);

	if (mangled_port == 0) {
		/*
		** We failed to get a response from the mangler, try a different one next retry.
		*/
		CurrentManglerServer++;
		if (CurrentManglerServer >= NumManglerServers) {
			CurrentManglerServer = 0;
		}

		WWDEBUG_SAY(("FirewallHelper: Couldn't find mangled port, returning source port\n"));
		return(source_port);
	}

	/*
	** Our new reference port is 'mangled port'. If we don't care about IP then we are done.
	*/
	if (((unsigned long)FIREWALL_TYPE_DUMB_MANGLING & fw) != 0) {
		WWDEBUG_SAY(("FirewallHelper - Dumb firewall, returning next mangled port as %d\n", mangled_port));
		return(mangled_port);
	}


	/*
	** Apply our known delta to the mangled port.
	*/
	if (((unsigned long)FIREWALL_TYPE_SIMPLE_PORT_ALLOCATION & fw) != 0) {

		/*
		** Simple port allocation.
		*/
		return_port = (int)mangled_port + SourcePortAllocationDelta;
	} else {

		/*
		** Rats. It's a relative mangler. This is much harder. Damn NAT32 guy.
		*/
		if (SourcePortAllocationDelta == 100) {

			/*
			** Special NAT32 section.
			**
			** NAT32 mangles source UDP port by adding 1700 + 100 * internal table index
			**
			** To get the current index we need to take the mangled port and subtract 1700 and the source port
			*/
			return_port = mangled_port - port;
			return_port -= 1700;

			return_port += SourcePortAllocationDelta;
			return_port += source_port;
			return_port += 1700;
		} else {
			WWASSERT(SourcePortAllocationDelta != 0);
			if (SourcePortAllocationDelta == 0) {
				/*
				** This should never happen....
				*/
				return_port = mangled_port;
			} else {

				return_port = mangled_port / SourcePortAllocationDelta;
				return_port = return_port * SourcePortAllocationDelta;

				return_port += (source_port % SourcePortAllocationDelta);
				return_port += SourcePortAllocationDelta;
			}
		}
	}

	/*
	** This bit is probably doomed.
	*/
	if (return_port > 65535) {
		return_port -= 65535;
	}
	if (return_port < 1024) {
		return_port += 1024;
	}

	WWDEBUG_SAY(("FirewallHelper - Returning next mangled port as %d\n", return_port));

	return(unsigned short(return_port));
}



/***********************************************************************************************
 * NatterClass::Build_Mangler_Packet -- Build a packet to send to the mangler.                 *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Buffer to build packet in                                                         *
 *           Port to set into the packets Port area                                            *
 *           Packet ID.                                                                        *
 *           Blitzme flag (shouldn't be used)                                                  *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/6/2001 1:11PM ST : Created                                                              *
 *=============================================================================================*/
int FirewallHelperClass::Build_Mangler_Packet(unsigned char *buffer, unsigned short port, unsigned long packet_id, bool blitzme)
{
	/*
	** Asserts.
	*/
	fw_assert(buffer != NULL);
	fw_assert(blitzme == false);

	/*
	** Need the buffer to exist.
	*/
	if (buffer == NULL) {
		return(0);
	}

	/*
	** Cast the buffer to a CnCPacketType.
	*/
	CnCPacketType *packet = (CnCPacketType*)buffer;

	/*
	** Clear out the buffer prior to setting fields.
	*/
	memset(packet, 0, sizeof(CnCPacketType));

	/*
	** Set the required Global Header firelds.
	*/
	packet->GHeader.Header.MagicNumber = GLOBAL_MAGICNUM;
	packet->GHeader.Header.Code = PACKET_DATA_NOACK;
	packet->GHeader.Header.ForwardTo = 0;
	packet->GHeader.Header.PacketID = packet_id;
	packet->GHeader.ProductID = COMMAND_AND_CONQUER_RA2;

	/*
	** Set the Global Packet fields.
	*/
	packet->Packet.Command = NET_MANGLER_REQUEST;
	strcpy(packet->Packet.Name, "ren");
	packet->Packet.ManglerData.MangledPortNumber = port;
	packet->Packet.ManglerData.OriginalPortNumber = port;
	packet->Packet.ManglerData.BlitzMe = 0;

	/*
	** Blitzme field is obsolete and should never be used.
	*/
	if (blitzme) {
		packet->Packet.ManglerData.BlitzMe = 1;
	}

	/*
	** Return success.
	*/
	return(sizeof(CnCPacketType));
}





/***********************************************************************************************
 * FirewallHelperClass::Add_Thread_Action -- Add a new action to the thread control FIFO       *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Thread action                                                                     *
 *           Notification event                                                                *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/9/2001 9:06PM ST : Created                                                              *
 *=============================================================================================*/
void FirewallHelperClass::Add_Thread_Action(int thread_action, HANDLE thread_event)
{
	ThreadLockClass locker(this);
	ThreadActionClass thread;
	thread.ThreadAction = thread_action;
	thread.ThreadEvent = thread_event;
	ThreadQueue.Add(thread);
}



/***********************************************************************************************
 * FirewallHelperClass::Set_Thread_Event -- Set the event associated with action completion    *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/9/2001 9:09PM ST : Created                                                              *
 *=============================================================================================*/
void FirewallHelperClass::Set_Thread_Event(void)
{
	ThreadLockClass locker(this);
	if (ThreadEvent != INVALID_HANDLE_VALUE) {
		SetEvent(ThreadEvent);
		ThreadEvent = INVALID_HANDLE_VALUE;
	}
}


/***********************************************************************************************
 * FirewallHelperClass::Talk_To_New_Player -- A new player wants to negotiate a NAT port       *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    User struct for player                                                            *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/7/2001 8:31PM ST : Created                                                              *
 *=============================================================================================*/
void FirewallHelperClass::Talk_To_New_Player(WOL::User *user)
{
	/*
	** We are only reading here - no need to get exclusive access.
	*/
	//ThreadLockClass locker(this);

	WWASSERT(PTheGameData != NULL);

	/*
	** Fill in an invitation options packet to send to the guest.
	*/
	WOLNATInterfaceClass::PrivateGameOptionsStruct options;
	strcpy(options.NATOptionsPrefix, "NAT:");
	options.Option = WOLNATInterfaceClass::OPTION_INVITE_PORT_NEGOTIATION;
	sprintf(options.OptionData.Invitation.LocalIP, "%08x,", (unsigned long) LocalChatConnectionAddress.Get_Address());
	sprintf(options.OptionData.Invitation.LocalPort, "%04x,", The_Game()->Get_Port());
	sprintf(options.OptionData.Invitation.ExternalIP, "%08x,", (unsigned long) ExternalAddress.Get_Address());
	sprintf(options.OptionData.Invitation.FirewallType, "%08x,", Get_Raw_Firewall_Behavior());
	sprintf(options.OptionData.Invitation.Queued, "%04x", ClientQueue.Count());

	/*
	** Send it.
	*/
	WOLNATInterface.Send_Private_Game_Options(user, (char*)&options);
}



/***********************************************************************************************
 * FirewallHelperClass::Process_Game_Options -- Process private game options packets           *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/9/2001 10:01PM ST : Created                                                             *
 *=============================================================================================*/
void FirewallHelperClass::Process_Game_Options(void)
{
	/*
	** Locals.
	*/
	WOL::User user;
	char options_buffer[OPTIONS_STAGING_BUFFER_SIZE];
	unsigned long addr_ip = 0;
	unsigned short addr_port = 0;
	unsigned long firewall = 0;

	ThreadLockClass locker(this);

	/*
	** See if there are any pending game options packets.
	*/
	if (WOLNATInterface.Get_Private_Game_Options(&user, options_buffer, OPTIONS_STAGING_BUFFER_SIZE)) {

		/*
		** Cast the options buffer to a more manageable type.
		*/
		WOLNATInterfaceClass::PrivateGameOptionsStruct *options = (WOLNATInterfaceClass::PrivateGameOptionsStruct*) options_buffer;

		/*
		** Get the option type.
		*/
		switch (options->Option) {

			/*
			** This option represents a game server inviting us to negotiate a connection through firewalls. It will contain his
			** local IP and port. This is a client side message only.
			*/
			case WOLNATInterfaceClass::OPTION_INVITE_PORT_NEGOTIATION:

				WWDEBUG_SAY(("FirewallHelper - Got OPTION_INVITE_PORT_NEGOTIATION\n"));
				fw_assert(!WOLNATInterface.Am_I_Server());

				if (!WOLNATInterface.Am_I_Server()) {

					/*
					** Pull out the players info from the packet.
					*/
					sscanf(options->OptionData.Invitation.LocalIP, "%08x", &addr_ip);
					sscanf(options->OptionData.Invitation.LocalPort, "%04hx", &addr_port);
					PlayersLocalAddress.Set_Address(addr_ip, addr_port);
					sscanf(options->OptionData.Invitation.ExternalIP, "%08x", &addr_ip);
					PlayersExternalAddress.Set_Address(addr_ip, addr_port);
					sscanf(options->OptionData.Invitation.FirewallType, "%08x", (unsigned long*)(&PlayersFirewallType));
					sscanf(options->OptionData.Invitation.Queued, "%04x", &QueuedPlayers);
					strcpy(PlayersName, (char*)user.name);
					PlayerAsUser = user;
					if (QueueNotifyPtr) {
						*QueueNotifyPtr = QueuedPlayers;
					}

					WWDEBUG_SAY(("FirewallHelper - Received port negotiation invitation. %d players in the queue ahead of me\n", QueuedPlayers));
					WWDEBUG_SAY(("FirewallHelper - Server is %s. Local addr = %s, ", PlayersName, PlayersLocalAddress.As_String()));
					WWDEBUG_SAY(("external addr = %s, firewall = %08x\n", PlayersExternalAddress.As_String(), (unsigned long) PlayersFirewallType));

					/*
					** Wait for queue notification.
					*/
					if (QueuedPlayers == 0) {
						QueuedPlayers = -1;
					}

					/*
					** Respond to the server with my info.
					*/
					ClientPort = WOLNATInterface.Get_Next_Client_Port();
					WOLNATInterfaceClass::PrivateGameOptionsStruct send_options;
					strcpy(send_options.NATOptionsPrefix, "NAT:");
					send_options.Option = WOLNATInterfaceClass::OPTION_ACCEPT_PORT_NEGOTIATION_INVITATION;
					sprintf(send_options.OptionData.Accept.LocalIP, "%08x,", (unsigned long) LocalChatConnectionAddress.Get_Address());
					sprintf(send_options.OptionData.Accept.LocalPort, "%04x,", ClientPort);
					sprintf(send_options.OptionData.Accept.ExternalIP, "%08x,", (unsigned long) ExternalAddress.Get_Address());
					sprintf(send_options.OptionData.Accept.FirewallType, "%08x", Get_Raw_Firewall_Behavior());

					/*
					** Send it.
					*/
					WOLNATInterface.Send_Private_Game_Options(&user, (char*)&send_options);

					/*
					** Go into connect mode.
					** Don't do this until we get a confirmation of our queue position.
					*/
					Add_Thread_Action(THREAD_CONNECT_FIREWALL, INVALID_HANDLE_VALUE);
				}
				break;



			/*
			** This option represents a game client accepting our invitation to negotiate a connection through firewalls. It
			** will contain his local IP and port. This is a server side message only.
			*/
			case WOLNATInterfaceClass::OPTION_ACCEPT_PORT_NEGOTIATION_INVITATION:

				WWDEBUG_SAY(("FirewallHelper - Got OPTION_ACCEPT_PORT_NEGOTIATION_INVITATION\n"));

				fw_assert(WOLNATInterface.Am_I_Server());

				if (WOLNATInterface.Am_I_Server()) {

					/*
					** Pull out the players info from the packet.
					*/
					unsigned long ext_ip = 0;
					sscanf(options->OptionData.Accept.LocalIP, "%08x", &addr_ip);
					sscanf(options->OptionData.Accept.LocalPort, "%04hx", &addr_port);
					sscanf(options->OptionData.Accept.ExternalIP, "%08x", &ext_ip);
					sscanf(options->OptionData.Accept.FirewallType, "%08x", &firewall);

					/*
					** Create a queue entry for the player.
					*/
					ClientStruct *client = new ClientStruct;
					strcpy(client->Name, (char*)user.name);
					client->LocalAddress.Set_Address(addr_ip, addr_port);
					client->ExternalAddress.Set_Address(ext_ip, addr_port);
					client->FirewallType = (FirewallBehaviorType) firewall;
					client->User = user;

					/*
					** Make sure this client isn't in the client list already (Should never happen but...)
					*/
					if (Remove_Player_From_Negotiation_Queue(client->Name)) {
						WWDEBUG_SAY(("FirewallHelper OPTION_ACCEPT_PORT_NEGOTIATION_INVITATION - Removed %s from ClientQueue\n", client->Name));
					}

					/*
					** Add this player to the negotiation queue.
					*/
					ClientQueue.Add(client);
					WWDEBUG_SAY(("FirewallHelper - Got client accept from %s. Local addr = %s,", client->Name, client->LocalAddress.As_String()));
					WWDEBUG_SAY((" external addr = %s, firewall = %08x\n", client->ExternalAddress.As_String(), (unsigned long) firewall));

					Add_Thread_Action(THREAD_CONNECT_FIREWALL, INVALID_HANDLE_VALUE);
				}
				break;

			/*
			** Handle connection result message.
			**
			*/
			case WOLNATInterfaceClass::OPTION_CONNECTION_RESULT:
			{
				int result = options->OptionData.ConnectionResult.Result[0] - 'a';
				WWDEBUG_SAY(("FirewallHelper - Got OPTION_CONNECTION_RESULT %d from %s\n", result, options->OptionData.ConnectionResult.Name));
				if (stricmp(PlayersName, options->OptionData.ConnectionResult.Name) == 0) {
					PlayersConnectionResult = result;
					unsigned long port;
					sscanf(options->OptionData.ConnectionResult.Port, "%04x", &port);
					PlayersConnectionResultPort = (unsigned short) port;
					if (WOLNATInterface.Am_I_Server()) {
						LastOptionsFromClient = TIMEGETTIME();
					}
				}
				break;
			}


			/*
			** Other player is telling us what port to use when talking to him.
			*/
			case WOLNATInterfaceClass::OPTION_PORT_NOTIFICATION:
			{
				WWDEBUG_SAY(("FirewallHelper - Got OPTION_PORT_NOTIFICATION from %s\n", options->OptionData.Port.Name));
				unsigned long port;
				sscanf(options->OptionData.Port.MangledPort, "%04x", &port);
				WWDEBUG_SAY(("FirewallHelper - Port is %d\n", port));
				//fw_assert(port >= 1024 && port < 65536);

				//if (port >= 1024 && port < 65536) {
					if (stricmp(PlayersName, options->OptionData.Port.Name) == 0) {
						PlayersMangledPort = (unsigned short) port;
						if (WOLNATInterface.Am_I_Server()) {
							LastOptionsFromClient = TIMEGETTIME();
						}
					}
				//}
				break;
			}


			/*
			** Server is telling us how close to the front of the queue we are.
			*/
			case WOLNATInterfaceClass::OPTION_QUEUE_STATE:
			{
				WWDEBUG_SAY(("FirewallHelper - Got OPTION_QUEUE_STATE\n"));
				int queue;
				sscanf(options->OptionData.QueueState.Position, "%02x", &queue);
				WWDEBUG_SAY(("FirewallHelper - Queue position is %d\n", queue));
				QueuedPlayers = queue;
				if (QueueNotifyPtr) {
					*QueueNotifyPtr = queue;
				}
				//if (QueuedPlayers == 0) {
				//	if (ThreadState != THREAD_DETECT_FIREWALL) {
				//		Add_Thread_Action(THREAD_CONNECT_FIREWALL, INVALID_HANDLE_VALUE);
				//	}
				//}
				break;
			}

			/*
			** Client has cancelled out of the game channel.
			*/
			case WOLNATInterfaceClass::OPTION_ABORT_NEGOTIATION:
				WWDEBUG_SAY(("FirewallHelper - Got OPTION_ABORT_NEGOTIATION from %s\n", (char*)user.name));
			{
				ThreadLockClass locker(this);
				if (WOLNATInterface.Am_I_Server()) {
					if (stricmp((char*)user.name, PlayersName) == 0) {
						strcpy(CancelPlayer, (char*)user.name);
					}
				}

				/*
				** Remove this player from the client queue if he is in there.
				*/
				if (Remove_Player_From_Negotiation_Queue((char*)user.name)) {
					WWDEBUG_SAY(("FirewallHelper OPTION_ABORT_NEGOTIATION - Removed %s from ClientQueue\n", (char*)user.name));
				}

				break;
			}


			/*
			** Client has joined a channel and is available for negotiation.
			*/
			case WOLNATInterfaceClass::OPTION_CLIENT_IN_CHANNEL:
				WWDEBUG_SAY(("FirewallHelper - Got OPTION_CLIENT_IN_CHANNEL from %s\n", (char*)user.name));
				if (WOLNATInterface.Am_I_Server()) {
					Talk_To_New_Player(&user);
				}
				break;


			default:
				fw_assert(false);
				break;

		}
	}
}



/***********************************************************************************************
 * FirewallHelperClass::Cleanup_Client_Queue -- Remove pending entries from client queue       *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: ONLY CALL THIS FROM THE MAIN THREAD                                               *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/22/2002 4:09PM ST : Created                                                             *
 *=============================================================================================*/
void FirewallHelperClass::Cleanup_Client_Queue(void)
{
	/*
	** This is the main thread removing entries from the client queue that it wasn't able to remove before due to thread
	** contention.
	*/
	fw_assert(Get_Main_Thread_ID() == GetCurrentThreadId());

	while (ClientQueueRemoveList.Count()) {
		if (Remove_Player_From_Negotiation_Queue_If_Mutex_Available(ClientQueueRemoveList[0]->Name)) {
			delete ClientQueueRemoveList[0];
			ClientQueueRemoveList.Delete(0);
		} else {
			break;
		}
	}
}


/***********************************************************************************************
 * FirewallHelperClass::Remove_Player_From_Negotiation_Queue_If_Mutex_Available - What it says *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Player name                                                                       *
 *                                                                                             *
 * OUTPUT:   True if mutex was available                                                       *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/22/2002 3:41PM ST : Created                                                             *
 *=============================================================================================*/
bool FirewallHelperClass::Remove_Player_From_Negotiation_Queue_If_Mutex_Available(char *player_name)
{
	ThreadLockClass locker(this, 0);
	/*
	** If we can grab the mutex then remove the item immediately, otherwise add it to a list and return.
	*/
	if (locker.WaitResult == WAIT_OBJECT_0) {
		Remove_Player_From_Negotiation_Queue(player_name);
		return(true);
	} else {

		/*
		** Just add the name to a list that will be checked later when it's safe to remove stuff from the client queue.
		** This list must only ever be accessed from the main thread.
		*/
		fw_assert(Get_Main_Thread_ID() == GetCurrentThreadId());
		for (int i=0 ; i<ClientQueueRemoveList.Count() ; i++) {
			if (stricmp(ClientQueueRemoveList[i]->Name, player_name) == 0) {
				return(false);
			}
		}
		ClientStruct *clientptr = new ClientStruct;
		strcpy(clientptr->Name, player_name);
		ClientQueueRemoveList.Add(clientptr);
	}
	return(false);
}


/***********************************************************************************************
 * FirewallHelperClass::Remove_Player_From_Negotiation_Queue - What it says                    *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Player name                                                                       *
 *                                                                                             *
 * OUTPUT:   True if player was in queue                                                       *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   3/3/2002 10:20PM ST : Created                                                             *
 *=============================================================================================*/
bool FirewallHelperClass::Remove_Player_From_Negotiation_Queue(char *player_name)
{
	fw_assert(WOLNATInterface.Am_I_Server());
	if (ClientQueue.Count() == 0) {
		return(false);
	}
	ThreadLockClass locker(this);
	ClientStruct *clientptr = NULL;
	bool retcode = false;

	for (int i=0 ; i<ClientQueue.Count() ; i++) {
		clientptr = ClientQueue[i];
		if (stricmp(clientptr->Name, player_name) == 0) {
			WWDEBUG_SAY(("FirewallHelper - Removed %s from ClientQueue\n", clientptr->Name));
			delete clientptr;
			ClientQueue.Delete(i);
			i--;
			retcode = true;
		}
	}
	return(retcode);
}



/***********************************************************************************************
 * FirewallHelperClass::Send_My_Port -- Send my mangled port number to the other guy           *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Port                                                                              *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/13/2001 11:37AM ST : Created                                                            *
 *=============================================================================================*/
void FirewallHelperClass::Send_My_Port(unsigned short port)
{

	ThreadLockClass locker(this);
	WWDEBUG_SAY(("FirewallHelper - Sending my port number (%d) to %s\n", (unsigned int)port, PlayersName));

	/*
	** Fill in an port notification options packet to send to the guest.
	*/
	WOLNATInterfaceClass::PrivateGameOptionsStruct options;
	strcpy(options.NATOptionsPrefix, "NAT:");
	options.Option = WOLNATInterfaceClass::OPTION_PORT_NOTIFICATION;
	sprintf(options.OptionData.Port.MangledPort, "%04x,", port);

	char my_name[64];
	WOLNATInterface.Get_My_Name(my_name);
	strcpy(options.OptionData.Port.Name, my_name);

	/*
	** Send it.
	*/
	WOLNATInterface.Send_Private_Game_Options(&PlayerAsUser, (char*)&options);
}




/***********************************************************************************************
 * FirewallHelperClass::Send_Connection_Result -- Send connection result game option           *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Result enum                                                                       *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/13/2001 2:17PM ST : Created                                                             *
 *=============================================================================================*/
void FirewallHelperClass::Send_Connection_Result(int result, unsigned short port)
{

	ThreadLockClass locker(this);

	/*
	** Fill in a connection result options packet to send to the other player.
	*/
	WOLNATInterfaceClass::PrivateGameOptionsStruct options;
	strcpy(options.NATOptionsPrefix, "NAT:");
	options.Option = WOLNATInterfaceClass::OPTION_CONNECTION_RESULT;
	options.OptionData.ConnectionResult.Result[0] = 'a' + result;
	options.OptionData.ConnectionResult.Result[1] = ',';
	sprintf(options.OptionData.ConnectionResult.Port, "%04x,", (unsigned long) port);

	char my_name[64];
	WOLNATInterface.Get_My_Name(my_name);
	strcpy(options.OptionData.ConnectionResult.Name, my_name);

	/*
	** Send it.
	*/
	WOLNATInterface.Send_Private_Game_Options(&PlayerAsUser, (char*)&options);
}




/***********************************************************************************************
 * FirewallHelperClass::Send_Connection_Result -- Send connection result game option           *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Result enum                                                                       *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/13/2001 2:17PM ST : Created                                                             *
 *=============================================================================================*/
void FirewallHelperClass::Send_Queue_States(void)
{

	if (WOLNATInterface.Am_I_Server()) {

		if (ClientQueue.Count()) {

			ThreadLockClass locker(this);

			/*
			** Fill in a queue state options packet to send to the other players.
			*/
			WOLNATInterfaceClass::PrivateGameOptionsStruct options;
			strcpy(options.NATOptionsPrefix, "NAT:");
			options.Option = WOLNATInterfaceClass::OPTION_QUEUE_STATE;

			char my_name[64];
			WOLNATInterface.Get_My_Name(my_name);
			strcpy(options.OptionData.ConnectionResult.Name, my_name);

			/*
			** Send updates to all queued players.
			*/
			for (int i=0 ; i<ClientQueue.Count() ; i++) {
				sprintf(options.OptionData.QueueState.Position, "%02x", i);
				ClientStruct *client = ClientQueue[i];
				fw_assert(client != NULL);

				if (client) {
					/*
					** Send it.
					*/
					WOLNATInterface.Send_Private_Game_Options(&client->User, (char*)&options);
				}
			}
		}
	}
}




/***********************************************************************************************
 * FirewallHelperClass::Set_Client_Connect_Event -- Set client event completion info           *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Event                                                                             *
 *           Ptr to success flag                                                               *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/14/2001 10:56PM ST : Created                                                            *
 *=============================================================================================*/
void FirewallHelperClass::Set_Client_Connect_Event(HANDLE thread_event, HANDLE cancel_event, int *flag_ptr, int *queue_ptr)
{
	ThreadLockClass locker(this);

	fw_assert(!WOLNATInterface.Am_I_Server());

	if (!WOLNATInterface.Am_I_Server()) {
		SuccessFlagPtr = flag_ptr;
		ClientConnectEvent = thread_event;
		ClientCancelEvent = cancel_event;
		QueueNotifyPtr = queue_ptr;
	}
}



/***********************************************************************************************
 * FirewallHelperClass::Set_Client_Success -- Set the client connect return status             *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Success - Did the client get a good port and IP for the server                    *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/14/2001 10:59PM ST : Created                                                            *
 *=============================================================================================*/
void FirewallHelperClass::Set_Client_Success(int success)
{
	if (!WOLNATInterface.Am_I_Server()) {
		if (SuccessFlagPtr) {
			*SuccessFlagPtr = success;
		}
		SuccessFlagPtr = NULL;
		if (QueueNotifyPtr) {
			*QueueNotifyPtr = 0;
		}
		QueueNotifyPtr = NULL;
		if (ClientConnectEvent != INVALID_HANDLE_VALUE) {
			SetEvent(ClientConnectEvent);
		}
		ClientConnectEvent = INVALID_HANDLE_VALUE;
		ClientCancelEvent = INVALID_HANDLE_VALUE;
	}
}




/***********************************************************************************************
 * FirewallHelperClass::Client_Cancelled -- Checks to see if the player hit cancel.            *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   True if player cancelled                                                          *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/17/2001 12:04PM ST : Created                                                            *
 *=============================================================================================*/
bool FirewallHelperClass::Client_Cancelled(void)
{
	if (!WOLNATInterface.Am_I_Server()) {
		ThreadLockClass locker(this);
		if (ClientCancelEvent != INVALID_HANDLE_VALUE) {
			int result = WaitForSingleObject(ClientCancelEvent, 0);

			if (result == WAIT_OBJECT_0) {
				WWDEBUG_SAY(("FirewallHelper - Client cancelled\n"));
				return(true);
			}
		}
	}
	return(false);
}



/***********************************************************************************************
 * FirewallHelperClass::Remote_Client_Cancelled -- Did the remote client hit cancel?           *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    True if our client cancelled                                                      *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: Requres PlayersName variable to be correctly set up                               *
 *           Will only return true once for each cancellation                                  *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/19/2001 9:26PM ST : Created                                                             *
 *=============================================================================================*/
bool FirewallHelperClass::Remote_Client_Cancelled(void)
{
	ThreadLockClass locker(this);

	if (WOLNATInterface.Am_I_Server() && CancelPlayer[0] != 0) {
		if (stricmp(CancelPlayer, PlayersName) == 0) {
			CancelPlayer[0] = 0;
			return(true);
		}
	}
	return(false);
}


/***********************************************************************************************
 * FirewallHelperClass::Send_Cancel_Notification -- Send abort to host                         *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Nothing                                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/19/2001 9:32PM ST : Created                                                             *
 *=============================================================================================*/
void FirewallHelperClass::Send_Cancel_Notification(void)
{
	fw_assert(!WOLNATInterface.Am_I_Server());

	if (!WOLNATInterface.Am_I_Server()) {
		ThreadLockClass locker(this);
		WWDEBUG_SAY(("FirewallHelper - Sending my cancellation notice to the server\n"));

		/*
		** Fill in an abort notification options packet to send to the host.
		*/
		WOLNATInterfaceClass::PrivateGameOptionsStruct options;
		strcpy(options.NATOptionsPrefix, "NAT:");
		options.Option = WOLNATInterfaceClass::OPTION_ABORT_NEGOTIATION;
		options.OptionData.QuitTalking.Nothing = 0;

		/*
		** Send it.
		*/
		WOLNATInterface.Send_Private_Game_Options(&PlayerAsUser, (char*)&options);
	}
}




/***********************************************************************************************
 * FirewallHelperClass::Negotiate_Port -- Option communications with one other player          *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   True if we heard from the other player directly                                   *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   8/7/2001 8:54PM ST : Created                                                              *
 *=============================================================================================*/
int FirewallHelperClass::Negotiate_Port(void)
{
	int retries = 5;

	unsigned long timeout;
	unsigned long resend_timer;
	bool mangling = true;
	bool sharing_a_nat = false;

	int result;
	IPAddressClass address;
	bool gotit = false;
	SocketHandlerClass *socket = NULL;
	char receive_packet[512];

	char my_name[256];
	my_name[0] = 0;

	bool server = WOLNATInterface.Am_I_Server();
	bool got_name = WOLNATInterface.Get_My_Name(my_name);

	fw_assert(got_name);
	if (!got_name) {
		WWDEBUG_SAY(("FirewallHelper - Failed to get user log on name\n"));
		return(FW_RESULT_FAILED);
	}

	PlayersMangledPort = 0;
	PlayersConnectionResult = -1;
	PlayersConnectionResultPort = 0;

	/*
	** On the server side, we need to find out who we are supposed to be connecting to.
	** We will use the game comms to send and receive on the server side since we already have a socket bound to the port
	** we want to use.
	*/
	if (server) {
		if (ClientQueue.Count() == 0) {
			return(FW_RESULT_FAILED);
		}

		/*
		** Update everyones queue state.
		*/
		Send_Queue_States();

		ClientStruct *client = ClientQueue[0];

		PlayersLocalAddress = client->LocalAddress;
		PlayersExternalAddress = client->ExternalAddress;
		PlayersFirewallType = client->FirewallType;
		PlayerAsUser = client->User;
		strcpy(PlayersName, client->Name);
		LastOptionsFromClient = TIMEGETTIME();

		delete client;
		ClientQueue.Delete(0);

		WWDEBUG_SAY(("FirewallHelper - Removed next player %s from client queue\n", PlayersName));

		if (Remote_Client_Cancelled()) {
			return(FW_RESULT_FAILED);
		}
		CancelPlayer[0] = 0;

	} else {
		/*
		** On the client side, we need to open a new socket using the client side port number. Use any port number for the
		** second parameter since it's the default send port and will always be overridden when we send anyway.
		*/
		socket = new SocketHandlerClass;
		socket->Service_Never();
		if (!socket->Open(ClientPort, PlayersLocalAddress.Get_Port())) {
			WWDEBUG_SAY(("FirewallHelperClass: Failed to open socket for client\n"));
			fw_assert(false);
			delete socket;
			return(FW_RESULT_FAILED);
		}
		WOLNATInterface.Set_Service_Socket_Handler(socket);

		/*
		** If we aren't sure of the queue size then wait until we are.
		*/
		if (QueuedPlayers == -1) {
			timeout = TIMEGETTIME() + (8 * TIMER_SECOND);
			while (TIMEGETTIME() < timeout && QueuedPlayers == -1) {
				Process_Game_Options();
				Sleep(5);

				/*
				** See if the user cancelled.
				*/
				if (Client_Cancelled()) {
					Send_Cancel_Notification();
					WOLNATInterface.Set_Service_Socket_Handler(NULL);
					delete socket;
					return(FW_RESULT_CANCELLED);
				}
			}
		}

		if (QueuedPlayers == -1) {

			/*
			** This should never happen right....?
			*/
			if (socket) {
				WOLNATInterface.Set_Service_Socket_Handler(NULL);
				delete socket;
			}
			return(FW_RESULT_FAILED);
		}

		/*
		** If there are players queued ahead of us, wait til we get to the front of the queue.
		*/
		timeout = TIMEGETTIME() + (QueuedPlayers * 32 * TIMER_SECOND);
		while (TIMEGETTIME() < timeout && QueuedPlayers != 0) {
			Process_Game_Options();
			Sleep(5);

			/*
			** See if the user cancelled.
			*/
			if (Client_Cancelled()) {
				Send_Cancel_Notification();
				WOLNATInterface.Set_Service_Socket_Handler(NULL);
				delete socket;
				return(FW_RESULT_CANCELLED);
			}
		}
	}

	PlayersFirewallAddress = PlayersExternalAddress;
	PlayersFirewallAddress.Set_Port(PlayersLocalAddress.Get_Port());


	WWDEBUG_SAY(("FirewallHelper - Negotiating port with player %s, local address = %s,", PlayersName, PlayersLocalAddress.As_String()));
	WWDEBUG_SAY((" ext address = %s, firewall = %08x\n", PlayersExternalAddress.As_String(), PlayersFirewallType));

	/*
	** What will our mangled port be if we try to talk to this player?
	**
	** If they are behind the same firewall then there will be no mangling.
	**
	*/
	if (PlayersExternalAddress.Is_IP_Equal(ExternalAddress)) {
		mangling = false;
		sharing_a_nat = true;
		PlayersFirewallAddress = PlayersLocalAddress;
		WWDEBUG_SAY(("Both external IPs match - we are behind the same firewall\n"));
	}

	/*
	** Maybe neither of our firewalls use port mangling. If so, then we can use the default port number.
	*/
	if (mangling) {
		if (Behavior == 0 || (Behavior & FIREWALL_TYPE_SIMPLE) != 0) {
			if (PlayersFirewallType == 0 || (PlayersFirewallType & FIREWALL_TYPE_SIMPLE) != 0) {
				/*
				** Ok, we both have crap firewalls or modems or somesuch. Anyway, this should be easy.
				*/
				mangling = false;
			}
		}
	}

	int trying = 0;
	unsigned short last_send_port = 0;
	unsigned short my_last_mangled_port = 0;
	unsigned short verified_mangled_port = 0;
	bool used_old_port = false;

	do {

		Sleep(0);

		/*
		** See if the user cancelled.
		*/
		if (Client_Cancelled()) {
			Send_Cancel_Notification();
			WOLNATInterface.Set_Service_Socket_Handler(NULL);
			delete socket;
			return(FW_RESULT_CANCELLED);
		}

		if (Remote_Client_Cancelled()) {
			return(FW_RESULT_FAILED);
		}

		/*
		** If we haven't got any kind of options at all from the client for 10 seconds then give up.
		*/
		if (trying > 0 && server && (TIMEGETTIME() - LastOptionsFromClient > TIMER_SECOND * 10)) {
			break;
		}

		if (!mangling) {

			/*
			** Use their unmangled port number. Nothing fancy needed here.
			*/
			PlayersMangledPort = PlayersLocalAddress.Get_Port();
		} else {

			unsigned short mangled_port = 0;

			/*
			** If our firewall uses the same source port regardless of destination port and we have
			** already connected to someone at this address (two players behind the same NAT) then
			** we have to try the same source port we are already talking to the other guy on.
			**
			** If that's not working out then try a different port after two attempts.
			*/
			if (server && trying < 2) {
				if ((Behavior & FirewallHelperClass::FIREWALL_TYPE_DESTINATION_PORT_DELTA) == 0) {
					WWDEBUG_SAY(("FirewallHelper: No destination port delta - looking for earlier connections at the same address\n"));

					for (int r = 0 ; r < ConnectionHistory.Count() ; r++) {
						if (ConnectionHistory[r].Is_IP_Equal(PlayersExternalAddress)) {
							mangled_port = MangledPortHistory[r];
							used_old_port = true;
							WWDEBUG_SAY(("FirewallHelper - Found old connection port number to use - port number %d\n", mangled_port));
							break;
						}
					}
				}
			}

			/*
			** If this isn't the first try then we might already know what the port will be since it can't change between tries.
			*/
			if (mangled_port == 0) {
				if ((Behavior & FirewallHelperClass::FIREWALL_TYPE_DESTINATION_PORT_DELTA) == 0) {
					if (trying > 0) {
						if (!used_old_port) {
							mangled_port = my_last_mangled_port;
							WWDEBUG_SAY(("FirewallHelper - No dest port delta - using last port number %d\n", mangled_port));
						} else {
							used_old_port = false;
						}
					}
				}
			}

			if (mangled_port == 0) {

				/*
				** Work out what my port will be.
				*/
				unsigned short base_port = 0;
				if (server) {
					base_port = WOLNATInterface.Get_Port_As_Server();
				} else {

					/*
					** Client only. Roll onto the next port and try that.
					*/
					if (trying > 0) {
						WOLNATInterface.Set_Service_Socket_Handler(NULL);
						Sleep(100);
						socket->Close();
						for (int cp=0 ; cp < 2048 ; cp++) {
							ClientPort = WOLNATInterface.Get_Next_Client_Port();
							if (socket->Open(ClientPort, PlayersLocalAddress.Get_Port())) {
								break;
							} else {
								socket->Close();
								ClientPort = 0;
							}
						}
						fw_assert(ClientPort != 0);
						WOLNATInterface.Set_Service_Socket_Handler(socket);
					}

					if (ClientPort != 0) {
						base_port = ClientPort;
					}

				}

				/*
				** Get my mangled port. This can take up to 4 seconds.
				*/
				mangled_port = FirewallHelper.Get_Next_Mangled_Source_Port(base_port);
			}

			/*
			** Send my mangled port to the other chap.
			*/
			fw_assert(mangled_port != 0);
			Send_My_Port(mangled_port);
			my_last_mangled_port = mangled_port;
		}

		/*
		** Wait until we know what port to send to. Allow plenty of time for slow chat server response.
		** Players are more likely to be in 'sync' with each other after the first try.
		*/
		timeout = TIMEGETTIME() + (6 * TIMER_SECOND);
		if (trying > 0) {
			timeout = timeout - (3*TIMER_SECOND);
		}
		while (TIMEGETTIME() < timeout && ((mangling == true && PlayersMangledPort == last_send_port) || (mangling == false && PlayersMangledPort == 0))) {
			Process_Game_Options();
			Sleep(5);

			/*
			** See if the user cancelled.
			*/
			if (Client_Cancelled()) {
				Send_Cancel_Notification();
				WOLNATInterface.Set_Service_Socket_Handler(NULL);
				delete socket;
				return(FW_RESULT_CANCELLED);
			}

			if (Remote_Client_Cancelled()) {
				return(FW_RESULT_FAILED);
			}

		}

		if (verified_mangled_port != 0) {
			PlayersMangledPort = verified_mangled_port;
		}

		last_send_port = PlayersMangledPort;
		PlayersFirewallAddress.Set_Port(PlayersMangledPort);

		if (PlayersMangledPort == 0) {
			WWDEBUG_SAY(("FirewallHelper: PlayersMangledPort = 0 - skipping packet send\n"));
		}

		if (PlayersMangledPort != 0) {

			/*
			** OK, we know what port to send to. Let dooooo it.
			*/
			/*
			** Build the port probe packet to send.
			*/
			char packet[256];
			strcpy(packet, my_name);

			/*
			** If we are a netgear and talking to a non netgear then pause before sending.
			**
			** This is needed when playing a netgear against a linksys. If the netgear sends first
			** then the linksys can respond with a ICMP port unreachable message in the case that
			** it already has an open port table mapping with a pre-existing player.
			**
			** When it gets an ICMP port unreachable, the netgear invalidates any existing port mappings.
			**
			** The delay has to be long enough for our port number to be passed through the chat server
			** to the other guy and for him to start sending.
			*/
			if (Is_Netgear() && !Is_Netgear(PlayersFirewallType)) {
				WWDEBUG_SAY(("FirewallHelper: Doing the netgear sleep thing\n"));
				Sleep(TIMER_SECOND * 4);
			}

			/*
			** Send the packet to the other player. If a NAT mangles an outgoing port number then it will be noted
			** as the packet comes in.
			*/
			WWDEBUG_SAY(("FirewallHelper: Sending PACKETTYPE_FIREWALL_PROBE packet to %s at port %d\n", PlayersName, PlayersMangledPort));
			timeout = TIMEGETTIME() + (TIMER_SECOND * 6);
			resend_timer = 0;

			do {
				if (resend_timer < TIMEGETTIME()) {
					//if (!CrapFlag)
					WOLNATInterface.Send_Game_Format_Packet_To(&PlayersFirewallAddress, packet, strlen(packet)+1, socket);
					resend_timer = TIMEGETTIME() + (TIMER_SECOND / 2);
				}

				Sleep(5);
				Process_Game_Options();

				/*
				** See if the user cancelled.
				*/
				if (Client_Cancelled()) {
					Send_Cancel_Notification();
					WOLNATInterface.Set_Service_Socket_Handler(NULL);
					delete socket;
					return(FW_RESULT_CANCELLED);
				}

				if (Remote_Client_Cancelled()) {
					return(FW_RESULT_FAILED);
				}


				//if (!gotit) {

					/*
					** See if a port probe came in from this player.
					*/
					IPAddressClass receive_address;
					result = WOLNATInterface.Get_Packet(receive_packet, sizeof(receive_packet), receive_address);

					/*
					** Check if we got a packet from this game.
					*/
       			if (result) {

						WWDEBUG_SAY(("FirewallHelper: Got a packet from player %s at address %s\n", receive_packet, receive_address.As_String()));

						/*
						** Is it from the guy we are trying to talk to?
						*/
						if (stricmp(receive_packet, PlayersName) == 0) {

							WWDEBUG_SAY(("FirewallHelper: Packet is from other guy\n"));

							/*
							** Send a connection result game option.
							*/
							Send_Connection_Result(CONNRESULT_CONNECTED, receive_address.Get_Port());

							/*
							** Note the address the packet came in on.
							*/
							PlayersFirewallAddress = receive_address;
							verified_mangled_port = receive_address.Get_Port();

							/*
							** Got it.
							*/
							gotit = true;
						}
					}
				//}

				/*
				** If the other player got our packet and we got theirs then we are finished.
				*/
				if (gotit && PlayersConnectionResult == CONNRESULT_CONNECTED) {
					WWDEBUG_SAY(("FirewallHelper: Got connection result - my packet must have got through\n"));
					break;
				}

				/*
				** If the other guy has started re-trying then we might as well do that too.
				*/
				if (PlayersConnectionResult >= CONNRESULT_TRY1 && PlayersConnectionResult < CONNRESULT_CONNECTED) {
					int their_try = PlayersConnectionResult - CONNRESULT_TRY1;
					if (their_try > trying) {
						WWDEBUG_SAY(("FirewallHelper: Other player is trying again - their_try = %d\n", their_try));
						trying = their_try - 1;
						break;
					}
				}

			} while(TIMEGETTIME() < timeout);
		}

		if (gotit && PlayersConnectionResult == CONNRESULT_CONNECTED) {
			break;
		}

		if (trying >= retries) {
			break;
		}

		/*
		** Well, that didn't go too well, let's try again.
		*/
		trying++;
		Send_Connection_Result(CONNRESULT_TRY1 + trying, 0);
		WWDEBUG_SAY(("FirewallHelper: Trying again - trying = %d\n", trying));
	} while(true);

	if (socket) {
		WOLNATInterface.Set_Service_Socket_Handler(NULL);
		delete socket;
	}

	if (gotit && PlayersConnectionResult == CONNRESULT_CONNECTED) {
		WWDEBUG_SAY(("FirewallHelper: Port negotiation successful! Correct address is %s\n", PlayersFirewallAddress.As_String()));
		WWDEBUG_SAY(("Player saw our port as %d\n", (unsigned long)PlayersConnectionResultPort));

		/*
		** Well it worked so we have more confidence in our firewall settings.
		*/
		Confidence++;

		/*
		** Save the port number in case we have to connect to this guy again.
		*/
		if (server) {

			unsigned short history_port = PlayersConnectionResultPort;
			if (history_port == 0) {
				history_port = my_last_mangled_port;
			}
			bool found_history = false;

			for (int h=0 ; h<ConnectionHistory.Count() ; h++) {
				if (ConnectionHistory[h].Is_IP_Equal(PlayersExternalAddress)) {
					MangledPortHistory[h] = history_port;
					found_history = true;
					break;
				}
			}
			if (!found_history) {
				ConnectionHistory.Add(PlayersExternalAddress);
				MangledPortHistory.Add(history_port);
			}
			fw_assert(ConnectionHistory.Count() == MangledPortHistory.Count());
		}
		return(FW_RESULT_SUCCEEDED);
	} else {
		WWDEBUG_SAY(("FirewallHelper: *** Port negotiation failed! ***\n"));
		Confidence--;
		if (Confidence < 0) {
			Confidence = 0;
		}
	}

	return(FW_RESULT_FAILED);
}










/*
** Function definitions for the MIB-II entry points.
*/
BOOL (__stdcall *SnmpExtensionInitPtr)(IN DWORD dwUpTimeReference, OUT HANDLE *phSubagentTrapEvent, OUT AsnObjectIdentifier *pFirstSupportedRegion);
BOOL (__stdcall *SnmpExtensionQueryPtr)(IN BYTE bPduType, IN OUT RFC1157VarBindList *pVarBindList, OUT AsnInteger32 *pErrorStatus, OUT AsnInteger32 *pErrorIndex);
LPVOID (__stdcall *SnmpUtilMemAllocPtr)(IN DWORD bytes);
VOID (__stdcall *SnmpUtilMemFreePtr)(IN LPVOID pMem);

typedef struct tConnInfoStruct {
	unsigned int State;
	unsigned long LocalIP;
	unsigned short LocalPort;
	unsigned long RemoteIP;
	unsigned short RemotePort;
} ConnInfoStruct;


/***********************************************************************************************
 * Get_Local_Chat_Connection_Address -- Which address are we using to talk to the chat server? *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Ptr to address to return local address                                            *                                                                                             *
 *                                                                                             *
 * OUTPUT:   True if success                                                                   *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   10/27/00 3:24PM ST : Created                                                              *
 *=============================================================================================*/
bool FirewallHelperClass::Get_Local_Chat_Connection_Address(void)
{
	/*
	** Local defines.
	*/
	enum {
		CLOSED = 1,
		LISTENING,
		SYN_SENT,
		SEN_RECEIVED,
		ESTABLISHED,
		FIN_WAIT,
		FIN_WAIT2,
		CLOSE_WAIT,
		LAST_ACK,
		CLOSING,
		TIME_WAIT,
		DELETE_TCB
	};

	enum {
		tcpConnState = 1,
		tcpConnLocalAddress,
		tcpConnLocalPort,
		tcpConnRemAddress,
		tcpConnRemPort
	};


	/*
	** Locals.
	*/
	char server_name[128];
	unsigned char server_address[4];
	unsigned char remote_address[4];
	HANDLE trap_handle;
	AsnObjectIdentifier first_supported_region;
	DynamicVectorClass<ConnInfoStruct*> connection_list;
	int last_field;
	int index;
	AsnInteger error_status;
	AsnInteger error_index;
	int conn_entry_type_index;
	int conn_entry_type;
	bool found;
	unsigned int server_port = 0;
	IPAddressClass my_address;

	/*
	** Statics.
	*/
	static char _conn_state[][32] = {
		"?",
		"CLOSED",
		"LISTENING",
		"SYN_SENT",
		"SEN_RECEIVED",
		"ESTABLISHED",
		"FIN_WAIT",
		"FIN_WAIT2",
		"CLOSE_WAIT",
		"LAST_ACK",
		"CLOSING",
		"TIME_WAIT",
		"DELETE_TCB"
	};

	/*
	** If we already did this then there's no need to do it again.
	*/
	if (LocalChatConnectionAddress.Is_Valid()) {
		return(true);
	}


	WWDEBUG_SAY(("FirewallHelper - Finding local address used to talk to the chat server\n"));

	/*
	** Get the name of the current server.
	*/
	WOLNATInterface.Get_Current_Server_ConnData(server_name, sizeof(server_name));

	if (strlen(server_name) == 0) {
		return(false);
	}

	/*
	** the conndata field will look something like
	**
	** 	TCP;ra2chat.westwood.com;7000
	*/
	fw_assert(strnicmp((char*)server_name, "TCP;", 4) == 0);
	char *server_name_ptr = &server_name[4];
	char *semi_colon_ptr = strchr(server_name_ptr, ';');

	if (semi_colon_ptr) {
		*semi_colon_ptr = 0;
		sscanf(semi_colon_ptr+1, "%d", &server_port);
	} else {
		WWDEBUG_SAY(("FirewallHelper - Failed to parse server name\n"));
		return(false);
	}
	WWDEBUG_SAY(("FirewallHelper - Current chat server name is %s\n", server_name_ptr));
	WWDEBUG_SAY(("FirewallHelper - Chat server port is %d\n", server_port));

	/*
	** Get the address of the chat server.
	*/
	WWDEBUG_SAY(("FirewallHelper - About to call gethostbyname\n"));
	struct hostent *host_info = gethostbyname(server_name_ptr);

	if (!host_info) {
		WWDEBUG_SAY(("FirewallHelper - gethostbyname failed! Error code %d\n", WSAGetLastError()));
		return(false);
	}

	memcpy(server_address, &host_info->h_addr_list[0][0], 4);
	unsigned long temp = *((unsigned long*)(&server_address[0]));
	temp = ntohl(temp);
	*((unsigned long*)(&server_address[0])) = temp;

	WWDEBUG_SAY(("FirewallHelper - Host address is %d.%d.%d.%d\n", server_address[3], server_address[2], server_address[1], server_address[0]));

	/*
	** Load the MIB-II SNMP DLL.
	*/
	WWDEBUG_SAY(("FirewallHelper - About to load INETMIB1.DLL\n"));

	HINSTANCE mib_ii_dll = LoadLibrary("inetmib1.dll");
	if (mib_ii_dll == NULL) {
		WWDEBUG_SAY(("FirewallHelper - Failed to load INETMIB1.DLL\n"));
		return(false);
	}

	WWDEBUG_SAY(("FirewallHelper - About to load SNMPAPI.DLL\n"));

	HINSTANCE snmpapi_dll = LoadLibrary("snmpapi.dll");
	if (snmpapi_dll == NULL) {
		WWDEBUG_SAY(("FirewallHelper - Failed to load SNMPAPI.DLL\n"));
		FreeLibrary(mib_ii_dll);
		return(false);
	}

	/*
	** Get the function pointers into the .dll
	*/
	SnmpExtensionInitPtr = (int (__stdcall *)(unsigned long,void ** ,AsnObjectIdentifier *)) GetProcAddress(mib_ii_dll, "SnmpExtensionInit");
	SnmpExtensionQueryPtr = (int (__stdcall *)(unsigned char,SnmpVarBindList *,long *,long *)) GetProcAddress(mib_ii_dll, "SnmpExtensionQuery");
	SnmpUtilMemAllocPtr = (void *(__stdcall *)(unsigned long)) GetProcAddress(snmpapi_dll, "SnmpUtilMemAlloc");
	SnmpUtilMemFreePtr = (void (__stdcall *)(void *)) GetProcAddress(snmpapi_dll, "SnmpUtilMemFree");
	if (SnmpExtensionInitPtr == NULL || SnmpExtensionQueryPtr == NULL || SnmpUtilMemAllocPtr == NULL || SnmpUtilMemFreePtr == NULL) {
		WWDEBUG_SAY(("FirewallHelper - Failed to get proc addresses for linked functions\n"));
		FreeLibrary(snmpapi_dll);
		FreeLibrary(mib_ii_dll);
		return(false);
	}


	RFC1157VarBindList *bind_list_ptr = (RFC1157VarBindList *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBindList) + 8192);
	RFC1157VarBind *bind_ptr = (RFC1157VarBind *) SnmpUtilMemAllocPtr(sizeof(RFC1157VarBind) + 128);

	/*
	** OK, here we go. Try to initialise the .dll
	*/
	WWDEBUG_SAY(("FirewallHelper - About to init INETMIB1.DLL\n"));
	int ok = SnmpExtensionInitPtr(GetCurrentTime(), &trap_handle, &first_supported_region);

	if (!ok) {
		/*
		** Aw crap.
		*/
		WWDEBUG_SAY(("FirewallHelper - Failed to init the .dll\n"));
		SnmpUtilMemFreePtr(bind_list_ptr);
		SnmpUtilMemFreePtr(bind_ptr);
		FreeLibrary(snmpapi_dll);
		FreeLibrary(mib_ii_dll);
		return(false);
	}

	/*
	** Name of mib_ii object we want to query. See RFC 1213.
	**
	** iso.org.dod.internet.mgmt.mib-2.tcp.tcpConnTable.TcpConnEntry.tcpConnState
	**  1   3   6      1      2     1   6        13          1             1
	*/
	unsigned int mib_ii_name[] = {1,3,6,1,2,1,6,13,1,1};
	unsigned int *mib_ii_name_ptr = (unsigned int *) SnmpUtilMemAllocPtr(sizeof(mib_ii_name) + 1024);
	memcpy(mib_ii_name_ptr, mib_ii_name, sizeof(mib_ii_name));

	/*
	** Get the index of the conn entry data.
	*/
	conn_entry_type_index = ARRAY_SIZE(mib_ii_name) - 1;

	/*
	** Set up the bind list.
	*/
	bind_ptr->name.idLength = ARRAY_SIZE(mib_ii_name);
	bind_ptr->name.ids = mib_ii_name_ptr;
	bind_list_ptr->list = bind_ptr;
	bind_list_ptr->len = 1;


	/*
	** We start with the tcpConnLocalAddress field.
	*/
	last_field = 1;

	/*
	** First connection.
	*/
	index = 0;

	/*
	** Suck out that tcp connection info....
	*/
	while (true) {

		if (!SnmpExtensionQueryPtr(ASN_RFC1157_GETNEXTREQUEST, bind_list_ptr, &error_status, &error_index)) {
			WWDEBUG_SAY(("FirewallHelper - SnmpExtensionQuery returned false\n"));
			SnmpUtilMemFreePtr(bind_list_ptr);
			SnmpUtilMemFreePtr(bind_ptr);
			FreeLibrary(snmpapi_dll);
			FreeLibrary(mib_ii_dll);
			return(false);
		}

		/*
		** If this is something new we aren't looking for then we are done.
		*/
		if (bind_ptr->name.idLength < ARRAY_SIZE(mib_ii_name)) {
			break;
		}

		/*
		** Get the type of info we are looking at. See RFC1213.
		**
		** 1 = tcpConnState
		** 2 = tcpConnLocalAddress
		** 3 = tcpConnLocalPort
		** 4 = tcpConnRemAddress
		** 5 = tcpConnRemPort
		**
		** tcpConnState is one of the following...
		**
		**   1  closed
		**   2  listen
		**   3  synSent
		**   4  synReceived
		**   5  established
		**   6  finWait1
		**   7  finWait2
		**   8  closeWait
		**   9  lastAck
		**   10 closing
		**   11 timeWait
		**   12 deleteTCB
		*/
		conn_entry_type = bind_ptr->name.ids[conn_entry_type_index];

		if (last_field != conn_entry_type) {
			index = 0;
			last_field = conn_entry_type;
		}

		switch (conn_entry_type) {

			/*
			** 1. First field in the entry. Need to create a new connection info struct
			** here to store this connection in.
			*/
			case tcpConnState:
			{
				ConnInfoStruct *new_conn = new ConnInfoStruct;
				new_conn->State = bind_ptr->value.asnValue.number;
				connection_list.Add(new_conn);
				break;
			}

			/*
			** 2. Local address field.
			*/
			case tcpConnLocalAddress:
				fw_assert(index < connection_list.Count());
				connection_list[index]->LocalIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream);
				index++;
				break;

			/*
			** 3. Local port field.
			*/
			case tcpConnLocalPort:
				fw_assert(index < connection_list.Count());
				connection_list[index]->LocalPort = bind_ptr->value.asnValue.number;
				index++;
				break;

			/*
			** 4. Remote address field.
			*/
			case tcpConnRemAddress:
				fw_assert(index < connection_list.Count());
				connection_list[index]->RemoteIP = *((unsigned long*)bind_ptr->value.asnValue.address.stream);
				index++;
				break;

			/*
			** 5. Remote port field.
			*/
			case tcpConnRemPort:
				fw_assert(index < connection_list.Count());
				connection_list[index]->RemotePort = bind_ptr->value.asnValue.number;
				index++;
				break;
		}
	}

	SnmpUtilMemFreePtr(bind_list_ptr);
	SnmpUtilMemFreePtr(bind_ptr);
	//SnmpUtilMemFreePtr(mib_ii_name_ptr);		// Don't free this - the SnmpExtensionQueryPtr call frees it apparently

	WWDEBUG_SAY(("FirewallHelper - Got %d connections in list, parsing...\n", connection_list.Count()));

	/*
	** Right, we got the lot. Lets see if any of them have the same address as the chat
	** server we think we are talking to.
	*/
	found = false;
	while (connection_list.Count()) {
		ConnInfoStruct *connection = connection_list[0];

		temp = ntohl(connection->RemoteIP);
		memcpy(remote_address, (unsigned char*)&temp, 4);

		/*
		** See if this connection has the same address as our server.
		*/
		if (!found && memcmp(remote_address, server_address, 4) == 0) {
			WWDEBUG_SAY(("FirewallHelper - Found connection with same remote address as server\n"));

			if (server_port == 0 || server_port == (unsigned int)connection->RemotePort) {

				WWDEBUG_SAY(("FirewallHelper - Connection has same port\n"));
				/*
				** Make sure the connection is current.
				*/
				if (connection->State == ESTABLISHED) {
					WWDEBUG_SAY(("FirewallHelper - Connection is ESTABLISHED\n"));
					my_address.Set_Address((unsigned char*)&connection->LocalIP, connection->LocalPort);
					found = true;
				} else {
					WWDEBUG_SAY(("FirewallHelper - Connection is not ESTABLISHED - skipping\n"));
				}
			} else {
				WWDEBUG_SAY(("FirewallHelper - Connection has different port. Port is %d, looking for %d\n", connection->RemotePort, server_port));
			}
		}

		/*
		** Free the memory.
		*/
		delete connection_list[0];
		connection_list.Delete(0);
	}

	if (found) {
		WWDEBUG_SAY(("FirewallHelper - Using address %s to talk to chat server\n", my_address.As_String()));
		LocalChatConnectionAddress = my_address;
	}

	FreeLibrary(snmpapi_dll);
	FreeLibrary(mib_ii_dll);

	ThreadLockClass locker(this);
	ThreadState = THREAD_GET_LOCAL_ADDRESS_DONE;

	return(found);
}




/***********************************************************************************************
 * FirewallHelperClass::Get_Local_Address -- Get local chat address ip as a long               *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Local IP - 0 if unknown                                                           *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   10/11/2001 4:30PM ST : Created                                                            *
 *=============================================================================================*/
unsigned long FirewallHelperClass::Get_Local_Address(void)
{
	unsigned long ip = 0;
	if (LocalChatConnectionAddress.Is_Valid()) {
		ip = LocalChatConnectionAddress.Get_Address();
		ip = htonl(ip);
	}

	return(ip);
}



/***********************************************************************************************
 * FirewallHelperClass::Get_Raw_Firewall_Behavior -- Get firewall behavior bits                *
 *                                                                                             *
 *                                                                                             *
 *                                                                                             *
 * INPUT:    Nothing                                                                           *
 *                                                                                             *
 * OUTPUT:   Firewall behavior                                                                 *
 *                                                                                             *
 * WARNINGS: None                                                                              *
 *                                                                                             *
 * HISTORY:                                                                                    *
 *   11/3/2001 8:50PM ST : Created                                                             *
 *=============================================================================================*/
unsigned short FirewallHelperClass::Get_Raw_Firewall_Behavior(void)
{
	unsigned short behave = (unsigned short)Behavior;

	/*
	** If we are forcing a particular port to be used then set the behavior type to be as if there was no firewall there at all.
	*/
	if (WOLNATInterface.Get_Force_Port() != 0) {
		behave = (unsigned short) FIREWALL_TYPE_SIMPLE;
	}
	return(behave);
}