/* ** Command & Conquer Renegade(tm) ** Copyright 2025 Electronic Arts Inc. ** ** This program is free software: you can redistribute it and/or modify ** it under the terms of the GNU General Public License as published by ** the Free Software Foundation, either version 3 of the License, or ** (at your option) any later version. ** ** This program is distributed in the hope that it will be useful, ** but WITHOUT ANY WARRANTY; without even the implied warranty of ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ** GNU General Public License for more details. ** ** You should have received a copy of the GNU General Public License ** along with this program. If not, see . */ /*********************************************************************************************** *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *** *********************************************************************************************** * * * Project Name : Command & Conquer * * * * $Archive:: /Commando/Code/Commando/NAT.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 #include "systimer.h" #include #include #include #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 ; ih_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 ; iOpen(source_ports[i], 4321)) { port_open_failed = true; /* ** Close any spare ports we allocated already before we bail. */ for (int j=0 ; j (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 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 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 ; iName, 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 ; iName, 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 ; iUser, (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 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); }