NAT Negotiation SDK

From GameSpy SDK

NAT Negotiation SDK

Overview

The GameSpy NAT Negotiation SDK interacts with GameSpy's NAT Negotiation server to allow hosting of multiplayer games by users behind NAT and firewall devices. Typically, a user behind a NAT or firewall device cannot host multiplayer games because the device will block incoming connections from outside users. GameSpy's NAT Negotiation technology allows two users, one or both of whom are behind a NAT device, to open a clear UDP channel directly between the users.

For background information on the networking challenges posed by NAT and Firewall devices, see the appendix of the Query & Reporting 2 documentation entitled "NAT and Firewall Support". This document assumes you are familiar with the terminology and issues discussed in that document.

GameSpy's NAT Negotiation technology uses a method known as "Port Guessing" to attempt to discern future port mapping information for two users based on their connections to the NAT Negotiation server. Once this mapping information is determined, the server exchanges the information with the users, and they connect to each other directly (note: the term "connect" in this document is understood to mean the establishment a clear, two-way channel between the users, since UDP is in reality a connection-less protocol).

Note that the NAT Negotiation SDK does not make any distinction between the "client" who is connecting to a "server" (or "host"), however this document will use those terms for clarity, and because the other SDKs involved do make that distinction.

The NAT Negotiation SDK itself is very simple - two users who want to be connected to each other have a shared "cookie" value that the NAT Negotiation server uses to match the users up.

The NAT Negotiation SDK has no limit to the number of users that can be connected together, but each channel between two users must be independently established.

In order for a client to connect to a server behind a NAT, both the client and the server must know a shared cookie value. Because the server is behind a NAT, the client cannot communicate this value to the server directly (if it could, there would be no need for the NAT negotiation step!). To communicate indirectly to the server, the client uses the Peer or ServerBrowsing SDK (depending on which of the two SDKs the game has implemented) to send a connection request to the GameSpy Master Server. The GameSpy Master Server then sends this connection request directly to the game server.

Because the game server is sending heartbeats to the Master Server (via the Peer or Query & Reporting 2 SDK), the Master Server has an open channel to communicate back to the game server. It uses this channel to pass the connection cookie back to the server. The server and client are now both aware of the cookie, and can use the NAT Negotiation SDK to establish a connection directly between them.

Supported Network Models

The NAT Negotiation SDK supports the establishment of UDP connections between two users, with one or both behind a NAT. For games using a dedicated server model, a number of clients will generally connect and disconnect from the server over time. For a Peer-to-Peer model, the game may have a centralized host that everyone connects to, or may require a distinct connection between each set of Peers. Any of these models will function using the NAT Negotiation SDK. The only limitations imposed are that TCP networking is not supported, and that the game must have direct access to the underlying UDP "SOCKET" - thus abstracted networking layers such as DirectPlay that hide the underlying sockets may not be compatible.

Some games use a single UDP socket for all communications to all clients that are connected to a server. Other games use a distinct socket for each 2-party connection. Both models are compatible with the NAT Negotiation SDK. For games that use a single socket for all clients, this socket is allocated by the game, and the game reads messages off of it and passes them into the NAT Negotiation SDK as needed for processing. For games that require a single UDP socket for each connection, the NAT Negotiation SDK allocates this socket as part of the negotiation process and passes it back to the game when negotiation is complete.

Note that if you are sharing a socket with the Query & Reporting (or Peer SDK) for server queries, this same socket can be shared for all client communications and NAT Negotiation as well. You will simply need to look at the packets as they come in and determine whether to pass them to the Q&R SDK or to the NAT Negotiation SDK (or process them as game networking packets).

File Manifest

The following files should be included with this package. If any of the files are missing, please contact devsupport@gamespy.com.

File
Description
natneg.c
NAT Negotiation SDK source
natneg.h
NAT Negotiation MAIN HEADER - include this in your source
nninternal.h
Internal structures and defines
\simpletest\
Simple connection sample

In addition, to build the SDK and samples, you will need to separately download the GameSpy "common code" package, which includes the shared SDK code used by this SDK and others.

When extracting this package, make sure you preserve the directory tree in order to ensure that the code builds correctly.

Implementation

Step 1: Server-side changes

For the server to be able to host behind a NAT, several changes are needed to the basic implementation of the Query & Reporting 2 or Peer SDK (follow the instructions for the SDK you are using).

These SDKs report the availability of your server to our Master Server. Normally, our Master Server does not allow games to be hosted from behind a NAT, as this would prevent outside clients from being able to connect to them. However, because you are using the NAT Negotiation SDK, your game client will be able to connect to servers behind a NAT and our Master Server needs to be aware of this. To indicate this support to our Master Server simple pass: "1" to the "natnegotiate" parameter of qr2_init() [Q&R 2 SDK] or "PEERTrue" to the "natNegotiate" parameter of peerSetTitle() [Peer SDK].

Note that you can set that parameter to true whether or not the current user hosting a game is behind a NAT. The backend will determine whether the user is behind a NAT automatically and inform clients whether they can connect directly or need to attempt NAT Negotiation. You simply need to set the parameter to indicate that you support NAT Negotiation as an option.

If a client wishes to connect to the server via NAT Negotiation, it will send a cookie value to the master server, which will forward it directly to your server. The Peer or QR2 SDK will indicate this request via a callback, as described below.

Query & Reporting 2 SDK

You need to create a callback function that will be called when a NAT Negotiate request comes in. The prototype for this callback is as follows:

typedef void (*qr2_natnegcallback_t)(int cookie, void *userdata);

The cookie value is the value passed from the connecting client, which will be used with the NAT Negotiate SDK.

Once you have created your callback, you need to register it with:

void qr2_register_natneg_callback(qr2_t qrec, qr2_natnegcallback_t nncallback);
You should generally call this immediately after qr2_init().

Peer SDK

You need to create a callback function that will be called when a NAT Negotiate request comes in. The prototype for this callback is as follows:

typedef void (* peerQRNatNegotiateCallback)(PEER peer, int cookie, void * param);

The cookie value is the value passed from the connecting client, which will be used with the NAT Negotiate SDK.

Pass this function in as part of the PEERCallbacks structure in peerInitialize.

Step 2: Client-side changes

On your client, which is doing matchmaking via either the ServerBrowsing or Peer SDK, you need to take special steps when connecting to a server behind a NAT versus a directly accessible server.

When a client selects the server they wish to connect to, the first step is to determine whether the server is behind a NAT, and if so, whether it is behind the same NAT as the client. This is important because most NATs will not allow inside clients to connect to inside servers via the "public" IP address on the NAT - the client must connect to the server directly via the private IP address.

To check this, call:

SBBool SBServerHasPrivateAddress(SBServer server);

... to determine whether the server has a private Address. If it does, you should compare the public address of the server with the public address of your current client to determine if they match.

You can determine the server's public address with:

unsigned int SBServerGetPublicInetAddress(SBServer server);

... you will compare this value to the value returned from:

unsigned int ServerBrowserGetMyPublicIPAddr(ServerBrowser sb);  [ServerBrowsing SDK]

... or ...

unsigned int peerGetPublicIP(); [Peer SDK]

If the public address of the server and your client match, then you know both are behind the same NAT, and you should connect via the "Internal" address of the server. You can now proceed to connect directly to the server via the Private address (obtained with SBServerGetPrivateAddress or SBServerGetPrivateInetAddress, depending on the preferred format for your game).

If the server is not behind the same NAT as your client, you need to determine whether to connect to the server directly, or initiate NAT Negotiation. Direct connections can be used if the server is either not behind a NAT, or is behind a promiscuous NAT and your game uses shared sockets.

To determine whether a direct connection can be made, use the function:

SBBool SBServerDirectConnect(SBServer server);

If the server supports direct connection, you can proceed to connect to the server directly using the Public address (obtain via SBServerGetPublicAddress or SBServerGetPublicInetAddress).

If the server does not support direct connection, you will need to proceed with NAT Negotiation.

On the client, you need to generate a random "cookie" value to send to the server. This cookie is used by the NAT Negotiation server to match up requests for connection. Simply generate a random 32-bit integer and call: ServerBrowserSendNatNegotiateCookieToServer() [ServerBrowsing SDK] or peerSendNatNegotiateCookie() [Peer SDK]. Pass in the public IP address and public query port for the server (obtain via SBServerGetPublicAddress + SBServerGetPublicQueryPort). This will send the cookie value to the server and trigger the NAT Negotiation callback as described in Step 1.

Once you have sent the cookie, you should proceed with NAT Negotiation as described below.

Step 3: Initiate NAT Negotiation

On the client, immediately after sending the cookie value to the server, you should begin the NAT Negotiation process. To do this, simply call one of the BeginNegotiation functions below:

NegotiateError NNBeginNegotiation(int cookie, int clientindex, NegotiateProgressFunc progresscallback, NegotiateCompletedFunc completedcallback, void *userdata);
NegotiateError NNBeginNegotiationWithSocket(SOCKET gamesocket, int cookie, int clientindex, NegotiateProgressFunc progresscallback, NegotiateCompletedFunc completedcallback, void *userdata);

Which version you use depends on how your game networking is set up. If you want to use the same socket for all client connections, and manage incoming data on the socket yourself, use the “NNBeginNegotiationWithSocket” function and pass in the socket you want to use. If you want the NAT Negotiation SDK to allocate a new socket for each connection, use the “NNBeginNegotiation” function. The “cookie” parameter is the cookie value you generated and sent to the server. The “clientindex” parameter is simply 0 for the client, and “1” for the server (or reversed – it doesn’t matter as long as one of them is 0, and the other is 1). The progress function will be called to update you on the negotiation progress and the completed callback will be called when the negotiation is complete. The userdata will be passed into your callback functions. See below for a description of the individual callbacks.

On your server, the process is much the same. When your NAT Negotiation callback is called, you want to begin negotiation using the cookie value provided. Simply call the appropriate NNBeginNegotiation function as described above.

Step 4: NAT Negotiation Callbacks

The NAT Negotiation SDK requires two callbacks - a progress callback that gets called as negotiation is proceeding, and a completed callback when negotiation is complete.

The progress function prototype is:

typedef void (*NegotiateProgressFunc)(NegotiateState state, void *userdata);

The two times you will get a progress notification is when the NAT Negotiation server acknowledges your connection request (ns_initack), and when the guessed port data has been received from the NAT Negotiation server and direct negotiation with the other client is in progress (ns_connectping).

The completed function prototype is:

typedef void (*NegotiateCompletedFunc)(NegotiateResult result, SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata);

result will indicate the result of the negotiation attempt. Possible values are:

nr_success
Successful negotiation, an open channel has now been established.
nr_deadbeatpartner
Partner did not register with the NAT Negotiation Server.
nr_inittimeout
Unable to communicate with NAT Negotiation Server
nr_unknownerror
NAT Negotiation server indicated an unknown error condition

gamesocket is the socket you should use to continue communications with the client. If you used NNBeginNegotiationWithSocket then this will be the socket you passed in originally. Otherwise it will be a new socket allocated by the NAT Negotiation SDK.

remoteaddr is the remote address and port you should use to communicate with the new client. Make sure you copy this structure off before the callback returns.

userdata is for your own use.

Once your completed function is called, you can begin sending data to the other client immediately using the socket and address provided.

Step 5: Thinking and Processing Incoming Data

After you've begun negotiation, you need to call the NNThink() function on regular intervals (recommended: 100 ms) to process the connection. You may call NNThink() when no negotiations are in progress as well - it will simply return immediately.

If you are using a shared game socket for all communications (NNBeginNegotiationWithSocket) then in addition to calling NNThink() you will need to look for NAT Negotiation packets arriving on that socket and pass them into the SDK. The SDK considers your game the "owner" of that socket and will not try to read any data from it directly. Simply pass the data, length, and received address obtained from "recvfrom" to:

void NNProcessData(char *data, int len, struct sockaddr_in *fromaddr);

To identify NAT Negotiation packets, you can use the 6 magic bytes that are used at the beginning of every packet. These are defined in natneg.h starting with NN_MAGIC_0.

Note that even after negotiation is complete and the completed callback is called, you need to continue looking for incoming NAT Negotiation packets on that socket for at least 5 seconds and continue to pass them to NNProcessData. Due to the unreliable nature of UDP, some packets may get sent even after one side of the connection has determined that a connection is established (due to dropped packets, etc). You should also call NNThink() during this time period as well.

If you want to cancel a Negotiation in progress, you can do so at any time by passing the cookie value to NNCancel().

Step 6: Initiate Standard Game Networking

Both clients will receive the completed callback at about the same time. At this point you can commence normal network interaction between the clients using the sockets and addresses provided.

Step 7: Cleanup the Nat Negotiation SDK

Once you have finished negotiating, the internal SDK memory must be freed using NNFreeNegotiatorList. Calling this will NOT close the game sockets, you are free to continue game communications.

Appendix: Test Results

We have tested the NAT Negotiation SDK with a variety of hardware and software NAT devices, as outlined below. We will continue testing with new devices in the future to make sure we have the widest possible support, although the devices we have currently tested with represent most if not all of the NAT management schemes we are aware of, so most other devices are likely to be compatible.

Device HW/SW Port Mapping Scheme Success Comments
Dlink DI-604 Residential Gateway HW 1:tuple YES  
LinkSys Cable / DSL Router BEFSR41 v.1.34 HW 1:1 exact* YES* Has a bug in this version that reuses the same mapping for multiple clients if behind the same NAT using the same port.
LinkSys Cable / DSL Router BEFSR41 v.1.47 HW 1:1 exact / 1:1 port YES  
SMC Barricade 7004VBR HW New Every Packet* YES* Has a bug that causes it to allocate a new port for every packet. Connection can be established, but incoming ports for every packet will be different.
USR Broadband Router 8000A HW 1:1 exact / 1:tuple YES* Second client using same port behind NAT will get a random port allocation and may not be able to connect.
Belkin Wireless Router F5D6230 HW 1:1 exact / 1:tuple YES* Identical behavior to USR
Netgear Prosafe Firewall FR114 HW 1:1 per-ip YES  
Windows 2000 ICS SW 1:1 port (sometimes exact) YES* Uses unusual port allocation scheme. May result in bad port guess during rapid connections.
Sygate Home Network 4.2 SW 1:1 port YES  
Coyote Linux / IP Chains SW 1:tuple YES  
Floppy FW (Linux / IPTables) SW 1:1 exact YES