Jenkins Software

NAT Traversal Architecture

How to use combine UPNP, NAT type detection, NAT punchthrough, and Router2 so P2P connections complete quickly and efficiently

RakNet offers 5 systems, each of which deal partly with not being able to connect to other systems.

These systems are:

  1. NAT type detection (Optional) - Find out if we have a router, and how restrictive that router is
  2. UPNP - Tell the router to open a specified port
  3. NAT punchthrough - Connect past routers by sending simultaneously between two systems
  4. Router2 (Optional) - Use other player's bandwidth for routers that cannot connect
  5. UDPProxyClient (Optional) - Route failed connections through your own servers

First, I'll describe the complete solution in case you are writing a major backend, or a massively multiplayer game. Skip to the bottom of this page if you want to just do things "the easy way".

Step 1: Connect to the server

Connect to your server running NATCompleteServer using the usual method RakPeerInterface::Connect().

Step 2: Detect router type

Call NatTypeDetectionClient::DetectNATType(). You should get back a packet ID_NAT_TYPE_DETECTION_RESULT indicating the NAT type. For example:

if (packet->data[0]==ID_NAT_TYPE_DETECTION_RESULT) {
RakNet::NATTypeDetectionResult detectionResult = (RakNet::NATTypeDetectionResult) packet->data[1]; }

If detectionResult is NATTypeDetectionResult::NAT_TYPE_NONE then this system does not have a router. You can connect to every system, and every system can connect to you.

You should tell the server this system is directly connectable, so that incoming systems do not waste time trying to do NAT punch to this system. See Appendix A, passing NAT_TYPE_NONE. Connect to every existing user in the game session.

Step 3: Use UPNP to open the router

Assuming our router in step 2 was not NATTypeDetectionResult::NAT_TYPE_NONE, we are going to try to use UPNP to open the router.

To build MiniUPNP

  1. Include DependentExtensions\miniupnpc-1.5 in the include paths
  2. Define STATICLIB in the preprocessor if necessary (See DependentExtensions\miniupnpc-1.5\declspec.h)
  3. Link ws2_32.lib and IPHlpApi.Lib
#include "miniupnpc.h"
#include "upnpcommands.h"
#include "upnperrors.h"

bool OpenUPNP(RakPeerInterface *rakPeer, SystemAddress serverAddress)
{	
	struct UPNPDev * devlist = 0;
	devlist = upnpDiscover(2000, 0, 0, 0);
	if (devlist)
	{
		char lanaddr[64];	/* my ip address on the LAN */
		struct UPNPUrls urls;
		struct IGDdatas data;
		if (UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr))==1)
		{
			DataStructures::List< RakNetSmartPtr< RakNetSocket> > sockets;
			rakPeer->GetSockets(sockets);

			char iport[32];
			Itoa(sockets[0]->boundAddress.GetPort(),iport,10);
			char eport[32];
			Itoa(rakPeer->GetExternalID(serverAddress).GetPort(),eport,10);

			int r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype, eport, iport, lanaddr, 0, "UDP", 0);
			if(r!=UPNPCOMMAND_SUCCESS)
			{
				return false;
			}
		}
		else
		{
			return false;
		}
	}
	else
	{
		return false;
	}

	return true;
}
If OpenUPNP returned true, you are done. You can connect to every system, and every system can connect to you. Remote systems should connect to your external port as seen by the server.

You should tell the server this system is directly connectable, so that incoming systems do not waste time trying to do NAT punch to this system. See Appendix A, passing NAT_TYPE_SUPPORTS_UPNP. Connect to every existing user in the game session.

Step 4: Run NATPunchthroughClient

  1. Download the list of remote players in the game session from the server, including their connectivity status.
  2. If the remote player's connectivity status is NAT_TYPE_SUPPORTS_UPNP or NAT_TYPE_NONE, you can connect to them directly. Store in memory this player as punchthrough succeeded, we will deal with this player in step 6.
  3. If the remote player's connectivity status is NAT_TYPE_SYMMETRIC and our own nat type from step 2 is also NAT_TYPE_SYMMETRIC, NATPunchthroughClient will fail for this player. Store in memory player as punchthrough failed, we will deal with this player in step 5.
  4. Otherwise, call NatPunchthroughClient::OpenNAT for that remote player and mark that player as processing.

For each player that we called OpenNAT for, we should get one of the following response codes:

  • ID_NAT_TARGET_NOT_CONNECTED - Remove this user from the list of remote players in the game session
  • ID_NAT_TARGET_UNRESPONSIVE - Remove this user from the list of remote players in the game session
  • ID_NAT_CONNECTION_TO_TARGET_LOST - Remote this user from the list of remote players in the game session
  • ID_NAT_ALREADY_IN_PROGRESS - Ignore
  • ID_NAT_PUNCHTHROUGH_FAILED - Store in memory this player as punchthrough failed, we will deal with this player in step 5.
  • ID_NAT_PUNCHTHROUGH_SUCCEEDED - Store in memory this player as punchthrough succeeded, we will deal with this player in step 6.

Step 5: Use Router2 or UDPProxyClient (optional)

For players that failed NATPunchthrough, you can route their connections through players that did not fail, using the Router2 plugin. You can also use the UDPProxyClient while you are running your own UDPProxyServer to forward those connections through a server.

Router2 will return ID_ROUTER_2_FORWARDING_NO_PATH if forwarding is not possible and ID_ROUTER_2_FORWARDING_ESTABLISHED on success.

UDPPRoxyClient will return ID_UDP_PROXY_GENERAL. Byte 1 indicates the return value. Success is returned on ID_UDP_PROXY_FORWARDING_SUCCEEDED. The remote system will get ID_UDP_PROXY_FORWARDING_NOTIFICATION on success. Anything else means failure.

If these solutions fail, or you do not use them, then it is not possible to complete a peer to peer gaming session. Leave the game session on the server. You should show a dialog box to the user that they need to manually open ports on their router before they can play. Or you can just try a different session.

Step 6: Connect to all other players in the session that we did not already connect to.

Step 6 assumes all users that failed connectivity were already connected to in step 5. If not, leave the game session on the server. You should show a dialog box to the user that they need to manually open ports on their router before they can play.

For players previously marked with NAT_TYPE_NONE, NAT_TYPE_SUPPORTS_UPNP, or that passed NAT punchthrough, you should now connect to these users. You can assume the connections will complete.

See the sample \Samples\NATCompleteClient

The easy way

Just UPNP and NatPunchthroughClient

UPNP enabled routers are common these days. Therefore, this simpler solution will work in nearly all cases, and is recommended unless you are writing a back-end service with multiple fallbacks.

  1. Connect to a hosting server running NATPunchthroughServer. For example, our free server.
  2. Call the OpenUPNP() function in step 3 above. It doesn't matter if the function succeeds or not, because you do the next steps regardless.
  3. When you find which game server you want to connect to, call natPunchthroughClient->OpenNAT(gameServerGuid, masterServerAddress);
  4. If you get ID_NAT_PUNCHTHROUGH_SUCCEEDED, connect to that server. For a client/server game, you are done.
  5. For a peer to peer game, you will also need to connect to the other peers after connecting to the server. Using the plugin FullyConnectedMesh2, call fullyConnectedMesh2->StartVerifiedJoin(gameServerGuid);
  6. As described in the documentation for FullyConnectedMesh2::StartVerifiedJoin,() you will get ID_FCM2_VERIFIED_JOIN_START which contains a list of all peers in the session. Call NatPunchthroughClient::OpenNAT() on each of those peers, and connect on success, similar to step 3. FullyConnectedMesh2 internally tracks which systems failed or passed, and you will get ID_FCM2_VERIFIED_JOIN_ACCEPTED once negotation has completed.

See the sample \Samples\ComprehensivePCGame

See Also

Index
MiniUPnP
NAT punchthrough
NAT type detection
Router2