NAT Punchthrough overview

What is NAT? NAT is short for network address translation. It is used by routers to map addresses behind the router to a single destination address, using different ports. For example, if you have two computers behind a router, but only one ISP, then both computers will use the same IP address, but with different source ports than what the application actually assigned. The router keeps a lookup table of what mappings it provides, so when a remote computer replies it is routed to the correct local computer behind the NAT. The problem with NAT is that remote computers cannot initiate sends to local computers because no mapping yet exists. Therefore, if two computers both behind NAT try to connect, neither will be able to do so. This is a problem with voice communication, peer to peer games, or games where your users host and the host is behind a NAT. In the old days your users would have to go to their router configuration screen and setup a mapping. However, in modern applications users are not usually required to do this, thanks to NatPunchthrough. NAT Punchthrough The NatPunchthroughClient.cpp plugin requires a server, not behind NAT, running NatPunchthroughServer.cpp that both clients can connect to. The server will find the external IP address of each client, and tell both clients to connect to that address at the same time. If that fails, each client will attempt to estimate the ports used by the other. If that fails, the process repeats once again, in case later port estimation opened a prior port. If that also fails, the plugin returns ID_NAT_PUNCHTHROUGH_FAILED. Client Implementation Create an instance of the plugin: NatPunchthroughClient natPunchthroughClient; Attach the plugin to an instance of RakPeerInterface: rakPeer->AttachPlugin(&natPunchthroughClient); Connect to the server, and wait for ID_CONNECTION_REQUEST_ACCEPTED. Use the following line to use the free server provided by RakNet: rakPeer->Connect("216.224.123.180", 60481, 0, 0); Call OpenNAT with the GUID (globally unique identifier) of the remote system that you want to connect to. In order to get the GUID, you would either have to transmit it with your own code on the server, upload it to the PHPDirectoryServer, or use a plugin that stores it, such as LightweightDatabase: natPunchthroughClient.OpenNAT(remoteGuid, serverSystemAddress);. In order to read your own guid, use RakPeerInterface::GetGuidFromSystemAddress(UNASSIGNED_SYSTEM_ADDRESS); Wait a while. It may take over 10 seconds to try every possible port twice, although it often works within a couple of seconds. If you want to get text messages on what is happening, you can use NatPunchthroughClient::SetDebugInterface() ID_NAT_PUNCHTHROUGH_SUCCEEDED means the punchthrough succeeded, and you should be able to connect or send other messages to the remote system. Packet::SystemAddress is the address of the system you can now connect to. Any other ID_NAT_* means the punchthrough failed. See MessageIdentifiers.h for the list of codes and comments on each. Server Implementation Host a server somewhere, not using NAT / e.g. behind a firewall. (RakNet provides a free one at 216.224.123.180:60481, however you may wish to host your own for consistent uptime). Create an instance of the plugin: NatPunchthroughServer natPunchthroughServer; Attach the plugin: rakPeer->AttachPlugin(&natPunchthroughServer); Don't forget to call RakPeerInterface::Startup() and RakPeerInterface::SetMaximumIncomingConnections(MAX_CONNECTIONS); Using the NatPunchthrough class See the sample \Samples\NATPunchtrough
UDP Proxy
With some poor quality or homemade routers, it is possible that NAT punchthrough will not work. For example, a router that picks a new random port for each outgoing connection, and will only allow incoming connections to this port, will never work. This happens about 5% of the time. To handle this case, RakNet provides the UDPProxy system. Essentially, it uses a server that you run to route messages between the source and destination client transparently. This even works to route datagrams from games not using RakNet (though you need RakNet to setup the forwarding). The combination of NATPunchthrough and UDPProxy should enable any system to connect to any other system with a 100% success rate, provided you are willing to host enough proxy servers to forward all the traffic. The UDP Proxy system uses 3 main classes: UDPProxyClient: Makes requests of UDPProxyCoordinator to setup forwarding. This is the class the client runs. UDPProxyCoordinator: Runs on the server that will get all requests from UDPProxyClient. Also, gets all logins from UDPProxyServer UDPProxyServer: Actually does the UDP datagram forwarding, via a composite instance of UDPForwarder.cpp Client Implementation: Create an instance of the plugin: UDPProxyClient udpProxyClient; Derive a class from RakNet::UDPProxyClientResultHandler to get event notifications for the system. Attach the plugin to an instance of RakPeerInterface: rakPeer->AttachPlugin(&udpProxyClient); Call UDPProxyClient::SetResultHandler() on the class created in step 2. Try NATPunchthrough first. If you get ID_NAT_PUNCHTHROUGH_FAILED for the system that initiated NATPunchthrough, go to step 6. Both systems will return ID_NAT_PUNCHTHROUGH_FAILED, however, only one system needs to start the proxy system. Call UDPProxyClient::RequestForwarding with the address of the coordinator, the address you want to forward from (UNASSIGNED_SYSTEM_ADDRESS for your own), the address you want to forward to, and how long to keep the forwarding active on no data. For example: SystemAddress coordinatorAddress; coordinatorAddress.SetBinaryAddress("216.224.123.180"); coordinatorAddress.port=60481; udpProxyClient.RequestForwarding(coordinatorAddress, UNASSIGNED_SYSTEM_ADDRESS, p->systemAddress, 7000); Assuming you are connected to the coordinator, and the coordinator is running the plugin, your event handler class created in step 2 should get a callback within a second or two. UDPProxyClientResultHandler::OnForwardingSuccess will be returned if a UDPProxyServer has been assigned to forward datagrams from the source system specified in step 6, to the target system specified in step 6. For example, to connect to the remote system use: rakPeer->Connect(proxyIPAddress, proxyPort, 0, 0); If more than one server is available, and both source and target relay systems are running RakNet, then source and target will automatically ping all available servers. The servers will be attempted in order of lowest ping sum to highest. This is based on the assumption that the lowest ping sum gives you the server that has the shortest path between the two systems, and therefore the least lag. Coordinator Implementation: Create an instance of the plugin: UDPProxyCoordinator udpProxyCoordinator; Attach the plugin to an instance of RakPeerInterface: rakPeer->AttachPlugin(&udpProxyCoordinator); Set the password on the coordinator for the servers to use udpProxyCoordinator.SetRemoteLoginPassword(COORDINATOR_PASSWORD); Don't forget to call RakPeerInterface::Startup() and RakPeerInterface::SetMaximumIncomingConnections(MAX_CONNECTIONS); Server Implementation: Create an instance of the plugin: UDPProxyServer udpProxyServer; Attach the plugin to an instance of RakPeerInterface: rakPeer->AttachPlugin(&udpProxyServer); Login to the coordinator. This can be done at runtime, so you can dynamically add more forwarding servers as your game is more popular. udpProxyServer.LoginToCoordinator(COORDINATOR_PASSWORD, coordinatorSystemAddress); If the coordinator plugin is on the same system as the server plugin, use: udpProxyServer.LoginToCoordinator(COORDINATOR_PASSWORD, rakPeer->GetInternalID(UNASSIGNED_SYSTEM_ADDRESS)); If you want to get callbacks as events occur (especially login failure) derive from RakNet::UDPProxyServerResultHandler and register your derived class with UDPProxyServer::SetResultHandler() State diagram with UDP Proxy
Server hosting
Server requirements No network address translation. No firewall, or firewall opened on the appropriate ports. Static IP address. Dynamic DNS is one way to get around this requirement. Compile with __GET_TIME_64BIT if you want to run the server longer than a month without rebooting Enough bandwidth to handle all connections Commercial hosting solutions Hypernia Worldwide locations. Servers are individual machines. Starting at $150 a month If you find more hosting solutions, contact us and it'll be added to this list.
See Also
Index Lightweight Database