Port Forwarding for a networking newbie...

I’ve been working on the Networking for my FPS, Onslaught of the Laser Cat (In my signature.) I’m using a mildly modified version of the M2H networking code. While my tests do alright locally, I can’t have other people connect, or connect to other people’s games.

I’ve been searching and ran across “port forwarding” as something I may need to set up. What exactly does this entail?

Port forwarding is really easy, but setting it up is specific to your router make/model (although the process is usually very similar). You normally log in to your router’s admin panel (usually something like 192.168.1.1 or 10.128.1.1 or something similar), then find the port forwarding section, pick the IP address of the machine that you want those ports forwarded on (the computer you’ll be hosting the game on), and then pick the port range you want forwarded (if you used Leepo’s tut, I think his default is 25001 or something).

However, if your game relies on hosts having their ports forwarded, the multiplayer portion of it is probably going to fail miserably. I would try getting the masterserver NAT punchthrough to work (although I’ve never been able to do so myself). I did however have some luck playing with the PHP master server that someone posted in the showcase section.

The PHP Masterserver is awesome but it won’t help connecting to people behind a firewall – currently I’m looking for a solution to this. Someone can start a server, successfully talk with the master server, but be behind a firewall and not let people connect.

Is there a guide somewhere for using NAT punchthrough?

I spent much of yesterday working with NAT punchthrough. The thing is, Unity 3 handles it vastly different than Unity 2.6. For instance, I had to knock out two cases in the old TestConnection() function and insert two new cases. This is my modified connection code:

connection.js:

class netAddress
{
  var ipAddress : String = "";
  var ipPort : int = 0;
}

var address : netAddress;

var connectionInfo : String = "";
var errorMessage : String = "";

public var hostData : HostData[];
public var sortedList : Array;
public var filterNATHosts : boolean = false;

private var timer : float = 0;
private var lastRequest : float = 0;
private var gameName : String = "";

private var nowConnecting : boolean = false;
private var publicIP : boolean = false;
private var probingPublicIP : boolean = false;
private var useNAT : boolean = false;

private var natCapable : ConnectionTesterStatus = ConnectionTesterStatus.Undetermined;
private var doneTestingNAT : boolean = false;
private var testMessage : String = "NAT capabilities are being tested.";

private var ambassador : Ambassador;

// Awake executes before Start...
function Awake() : void
{
  // Read the game's name from the Ambassador.
  ambassador = FindObjectOfType( Ambassador );
  
  TestNATCapabilities();
  
  publicIP = Network.HavePublicAddress();
  if( publicIP ) { Debug.Log( "Connection: This machine's IP is public." ); }
  else { Debug.Log( "Connection: This machine's IP address is private." ); }
  
  NetworkPretest();
}

function NetworkPretest()
{
  while( ! doneTestingNAT )
  {
    TestConnection();
	yield;
  }
}

// Server engine hooks...
function TestNATCapabilities()
{
  while( natCapable == ConnectionTesterStatus.Undetermined )
  {
    natCapable = Network.TestConnection();
	yield;
  }
  
  Debug.Log( "Am I capable of NAT punchthrough?  Answer: " + natCapable );
}

function TestConnection() : void
{
  natCapable = Network.TestConnection();
  
  switch( natCapable )
  {
	case ConnectionTesterStatus.Error: 
	  testMessage = "Problem determining NAT capabilities";
	  errorMessage = "Test complete.  Couldn't determine NAT punchthrough ability.";
	  doneTestingNAT = true;
	  break;
			
	case ConnectionTesterStatus.Undetermined: 
	  testMessage = "Undetermined NAT capabilities";
	  errorMessage = "Testing connection...";
	  doneTestingNAT = false;
	  break;
	  
    case ConnectionTesterStatus.NATpunchthroughFullCone:
    case ConnectionTesterStatus.NATpunchthroughAddressRestrictedCone:
	  testMessage = "NAT can punchthrough as necessary.";
	  errorMessage = "";
	  doneTestingNAT = true;
	  useNAT = true;
	  break;
	  
	case ConnectionTesterStatus.LimitedNATPunchthroughPortRestricted:
	    testMessage = "Everyone except Symmetric NATs can connect.";
		errorMessage = "Test complete.  Some players cannot join your games.";
		doneTestingNAT = true;
		useNAT = true;
		break;
	
	case ConnectionTesterStatus.LimitedNATPunchthroughSymmetric:
	    testMessage = "NAT Punchthrough is limited to asymmetric NAT systems.";
		errorMessage = "Test complete.  Your NAT punchthrough is limited, do not host games.";
		doneTestingNAT = true;
		useNAT = true;
		break;
					
	case ConnectionTesterStatus.PublicIPIsConnectable:
		testMessage = "Directly connectable public IP address.";
		errorMessage = "";
		doneTestingNAT = true;
		useNAT = false;
		break;
		
	// This case is a bit special as we now need to check if we can 
	// cicrumvent the blocking by using NAT punchthrough
	case ConnectionTesterStatus.PublicIPPortBlocked:
		testMessage = "Non-connectble public IP address (port " + address.ipPort + " blocked), running a server is impossible.";
		errorMessage = "Test complete.  Port " + address.ipPort + " is blocked.  A server can't be started.";
		useNAT = false;
		
		// If no NAT punchthrough test has been performed on this public IP, force a test
		if ( !probingPublicIP )
		{
		  Debug.Log("Testing if firewall can be circumnvented");
		  natCapable = Network.TestConnectionNAT();
		  probingPublicIP = true;
		  timer = Time.time + 10;
		}
		// NAT punchthrough test was performed but we still get blocked
		else if( Time.time > timer )
		{
		  probingPublicIP = false; 		// reset
		  doneTestingNAT = true;
		  useNAT = true;
		}
		break;
		
	case ConnectionTesterStatus.PublicIPNoServerStarted:
		testMessage = "Server not started, it is needed to check ability to connect with server. Restart the test when ready.";
		break;
	
	default: 
		testMessage = "Error in test routine, got " + natCapable;
		errorMessage = "Test complete.  There was an error in the connection test: " + natCapable;
  }
  
  if( doneTestingNAT )
  {
    Debug.Log( "Connection: " + testMessage );
	Debug.Log( "Connection: " + natCapable + " " + probingPublicIP + " " + doneTestingNAT );
  }
  
}
// End server engine hooks.

// Client engine hooks...
function Connect( ipAddress : String, port : int ) : void
{
  address.ipAddress = ipAddress;
  address.ipPort = port;
  
  Debug.Log( "Connection: Connecting to " + address.ipAddress + ":" + address.ipPort );
  Network.Connect( address.ipAddress, address.ipPort );		
  nowConnecting = true;	
}

function OnConnectedToServer() : void
{
  Network.isMessageQueueRunning = false;
  address.ipAddress = Network.connections[0].ipAddress;
  address.ipPort = Network.connections[0].port;
}

function OnFailedToConnect() : void
{
  // Oh, shit.
  errorMessage = "Couldn't connect to " + address.ipAddress + ":" + address.ipPort + ".";
}

function OnFailedToConnectToMasterServer() : void
{
  // Oh, shit.
  errorMessage = "Couldn't talk to the Master Server.";
}
// End client engine hooks.

// Functions the connection system needs to run.
function HostGame( players : int, port : int, scene : int ) : void
{
  if( players < 1 ) { players = 1; }
  
  //var doNat : boolean = ! Network.HavePublicAddress();
  var descData : String = scene + "";
  
  ambassador.gameDesc = descData;
  
  Network.InitializeServer( players,port,useNAT );
}

function FindHosts()
{
  var timeout : int = 5;
  
  if( lastRequest == 0 || Time.realtimeSinceStartup > lastRequest + timeout )
  {
    lastRequest = Time.time;
	
	MasterServer.RequestHostList( ambassador.gameName );
	yield WaitForSeconds( 1 );
	
	hostData = MasterServer.PollHostList();
	yield WaitForSeconds( 1 );
	
	SortHostList();
	
	Debug.Log( "Host search complete.  Found " + sortedList.length + " games." );
  }
}

function SortHostList() : void
{
  var a : int;
  var b : int;
  var ctr : int = 0;
  var subCtr : int = 0;
  var baseList : HostData[] = hostData;
  
  sortedList = new Array();
  
  for( var data : HostData in baseList )
  {
    // Add the element to the list.
    sortedList.Add( ctr );
	
	// Sort this newest element in the correct position based
	// on numbers of connected players, unless the array is 0 
	// or 1 elements large.
	if( sortedList.length <= 1 ) { break; }
  
    for( subCtr = sortedList.length - 1; ctr > 0; ctr-- )
    {
      a = hostData[sortedList[ ctr - 1]].connectedPlayers;
	  b = hostData[ctr].connectedPlayers;
	
	  if( a < b ) { SwapElements( sortedList, ctr-1, ctr ); }
	  else { break; }
    }
	
	ctr++;
  }
}

function SwapElements( array, a : int, b : int ) : void
{
  // This is a simple element swap.  Nothing showy.
  var container = array[a];
  
  array[a] = array[b];
  array[b] = container;
}
// End of connection system functions.

I think that my trouble is happening - go figure - in the HostGame() function, even though I do the case-by-case check for punchthrough based on the results of the connection test.

However, since I know now port forwarding won’t solve this, I guess I can let this topic rest and try again elsewhere.