Microsoft DirectX 9.0 SDK Update (Summer 2003) |
Advanced NAT Techniques for Peer Hosts
Microsoft® DirectPlay® cannot provide 100 percent connectivity for hosts behind Network Address Translation (NAT) devices that do not support Universal Plug and Play (UPnP). However, your application might be able to improve support for some of these cases using more elaborate methods. The techniques described here require additional development effort, as well as external server resources.
NAT Resolver
NAT devices create an implicit port mapping when an internal computer sends a packet to an external computer. Some NAT devices allow any external computer to use this port mapping to send to the internal computer, instead of only forwarding replies sent by the original external target. These are sometimes referred to as "loose NATs." DirectPlay can take advantage of this behavior using the IDirectPlay8NATResolver interface and address components to allow hosting behind these devices.
You must start by implementing an IDirectPlay8NATResolver server that will be accessible from the Internet. Then your game application can add the DPNA_KEY_NAT_RESOLVER component to the device address passed to IDirectPlay8Peer::Host. The component data is a string containing the Internet Protocol (IP) address and port for the NAT resolver to use, separated by a colon. For example, the following code specifies to use a server located on port 5678 at the address 123.123.123.123.
WCHAR * wszNATResolver = L"123.123.123.123:5678"; DWORD dwNATResolverSize = (wcslen(wszNATResolver) + 1) * sizeof(WCHAR); hr = pDP8DeviceAddress->AddComponent(DPNA_KEY_NAT_RESOLVER, wszNATResolver, dwNATResolverSize, DPNA_DATATYPE_STRING);
You can also specify a hostname instead of a numerical IP address, as shown in the following example.
WCHAR * wszNATResolver = L"resolver.mydomain.com:5678"; DWORD dwNATResolverSize = (wcslen(wszNATResolver) + 1) * sizeof(WCHAR); hr = pDP8DeviceAddress->AddComponent(DPNA_KEY_NAT_RESOLVER, wszNATResolver, dwNATResolverSize, DPNA_DATATYPE_STRING);
For robustness, you might want to have more than one resolving servers to try. You can specify multiple addresses separated by commas as shown in the following example.
WCHAR * wszNATResolvers = L"123.123.123.123:5678,backupresolver.mydomain.com:6789"; DWORD dwNATResolverSize = (wcslen(wszNATResolvers) + 1) * sizeof(WCHAR); hr = pDP8DeviceAddress->AddComponent(DPNA_KEY_NAT_RESOLVER, wszNATResolvers, dwNATResolverSize, DPNA_DATATYPE_STRING);
Each resolver is tried simultaneously for speed, and the first response is used. If no server responds, the IDirectPlay8Peer::Host call still succeeds.
Because hosting these resolving servers requires resources, you might want to prevent arbitrary players from using the resolver. This can be achieved with the DPNA_KEY_NAT_RESOLVER_USER_STRING address component. This value is passed directly to the resolver for verification in the DPN_MSGID_NAT_RESOLVER_QUERY message, and it can choose to respond or ignore as appropriate. The following example shows how to do this.
WCHAR * wszPassword = L"MyPassword"; DWORD dwPasswordSize = (wcslen(wszPassword) + 1) * sizeof(WCHAR); hr = pDP8DeviceAddress->AddComponent(DPNA_KEY_NAT_RESOLVER_USER_STRING, wszPassword, dwPasswordSize, DPNA_DATATYPE_STRING);
The host's NAT device generates a port mapping for the NAT resolver query. If the NAT resolver elects to respond to the query, DirectPlay will send the mapping's public address back to the querying host. The host will then have it included in the addresses returned by the IDirectPlay8Peer::GetLocalHostAddresses method.
External peer clients use the mapping address when connecting to the internal host. However the NAT device will expire the mapping if it is inactive, which will prevent the clients from using it. The actual expiration timeout varies by NAT device. Generally, the external peer clients are expected to begin connecting within 30 seconds of the IDirectPlay8Peer::Host call completing.
An example usage of NAT resolver address components can be found in the NATPeer sample.
Enumerating Clients to Create Implicit NAT Mappings
An expanded technique can sometimes be used to host behind so-called "strict NATs" that only allow the target of an outbound packet to send packets in to the private network. If the strict NAT device uses the same external port number for sending to different external addresses, even though they are using different implicit mappings, the host might be able to use the IDirectPlay8Peer::EnumHosts method to generate mappings for external peer clients that want to join.
The host must know that the external peer client intends join, as well as the address that the client will use when connecting. This is determined through match-making or similar means. The client's address is then passed as the pdpaddrHost parameter for IDirectPlay8Peer::EnumHosts by the host itself. See Enumerating Hosts for more information.
The joining peers do not need to send an enumeration response and will not even receive a query notification message callback. The NAT device will merely create a mapping for the outbound enumeration packet from the host that allows the client's inbound enumeration or connect requests to be forwarded to the internal host. The host's reverse IDirectPlay8Peer::EnumHosts operation should remain active until the peer client successfully joins or stops attempting to join so that the NAT mapping remains active.
Because the address of the external peer clients must be determined beforehand, the clients also must not be behind NAT devices that select different port numbers for different external targets.