NAT Traversal

Traversing Firewalls and NATs

Notes

As almost everybody is aware, the Internet is partially partitioned by the presence of firewalls and Network Address Translation devices.  Many hosts do not have a globally visible IP address on any network interface and instead have private network address that are  translated to and from a global address via an intermediate host usually referred to as a firewall or NAT router. This usually isn't a problem for an outgoing connection as the intermediates are designed to handle this situation transparently.  However, an incoming connection is a problem. The intermediate gets the connection...and it has no idea that the connection should be forwarded to an interface on its other side.

There are a number of solutions to this problem. You can manually configure the intermediate to recognize that an incoming connection on a certain port should be forwarded to a service on another host. There are also several systems for having service software configure the intermediates automatically. They unfortunately aren't inter-compatible nor standardized. (http://en.wikipedia.org/wiki/NAT_traversal)

In simplified TeaTime the only service that gets incoming TCP connections is the message router. All of the island replicates only generate outgoing TCP connections to the router. In the variation used by Wisconsin there are two islands, one for the scene graph and another for content distribution.  On a LAN there is a contact point system (CRQT) that broadcasts over UDP the appropriate connection information, the router IP, port number, and the UUIDs of the capabilities that identify the specific island, and the scene graph object to use as a teleport destination. Note that the router does not have to run on the same host as any of the island replicates.

We must have a manual configuration option as some intermediates don't have any automatic means of configuring NAT (or this my be prohibited by the security policy) and  some of the more complex configurations won't work with the automatic systems. Many of the pieces of the manual configuration system can serve as a building blocks for more automatic schemes.

Just for clarity and generality, we'll assume that the router is being run on another host. This implies that the router will have to either handle the NAT traversal and and communicate the appropriate address information back to the initial connecting island so it can generate the contact point or it will do all the contact point handling itself when a new island connects and registers. Either scenario can work, more research is required into finding the optimal approach.

Thus, we need some way to tell Open Cobalt that there are potentially two kinds of contact points (or perhaps extend the contact points to have both addresses and ports). If the request for a contact comes from a host NOT on the local private network (or the intermediate?), the postcards/contact points it hands out for itself should have the address information for the intermediate rather that the local host. This will also require a way of fixing the port number of the cobalt message router at start-up.  This should be pretty easy to do and should be the first thing do do in this area. Manual configuration alone will be adequate for an early release which will be looked at by competent developers.  There should be some preferences that the NAT traversal configuration can be stored in.

The automatic configuration options require some more thought as it is not obvious which approach should be first. We might want to survey our user base or write detection software and gather some statistics about which automatic system is more common to get the most effective use of our time. But once a manual system is working it should be straightforward (if time-consuming) to have an automatic configuration mode.

  • Configuration and Firewall transition software
    • Bonjour (was Rendezvous) - http://www.dns-sd.org/
      • a service announcement/discovery protocol that is layered on the DNS 
      • Designed for local use, but nothing stops it from working over the Internet at large. 
      • Would be cool to have a croquet router or island announce itself using Bonjour 
    • NAT Port Mapping Protocol (NAT-PMP) - http://en.wikipedia.org/wiki/NAT_Port_Mapping_Protocol 
    • Fairly simple protocol, not a lot of implementation effort would be required.
  • Simple Traversal Underneath Network Address Translators (STUN)
    • For UDP only
    • Might be useful if we want to extend the "here I am" protocol outside the firewall. 
  • Universal Plug & Play (UPnP) - http://en.wikipedia.org/wiki/UPnP
    • Microsoft protocols, with all that implies 
    • Simple Service Discovery Protocol (SSDP)
      • Like Bonjour, a service announcement/discovery protocol
      • Uses multicast for announcements, could theoretically work of the WAN, but multicast router support is currently kinda scarce. 
      • Would be cool to have a croquet router or island announce itself using SSDP
    • Internet Gateway Device Protocol (IGD)
      • Many current NAT router support this.
      • Unfortunately is insecure so it usually turned off by default. 
    • Unfortunately uses SOAP for device control.
  • SOCKS
    • It's major drawback it that it can require a rewrite of your networking support as it's essentially an outboard TCP/IP system.
    • And it is gradually losing popularity. 
  • SSH Tunnels?
    • SSH is for technical users.
    • Has some security advantages
    • Usage scenarios are unclear, but would seem to require an SSHd running on a host with public IPs. 
  • VPNs
    • L2TP and PPTP
    • Probably too complex to do.= as an internal protocol
    • And it's too complex to set up externally for home users. 

 

  • Router preferences?
    • router interface and port
    • firewall interface and port
  • Island preferences
    • Preferred registries?

Implementation

Finding the router

After digging around for a bit, it appears that the most widely used automatic NAT traversal option for TCP is the Internet Gateway Device service set (IGD) of Universal Plug and Play (UPnP). NAT-PMP is in use, but on a much more limited scale, and many NAT-PMP routers also support UPnP-IGD. SOCKS is also still around, but gradually going away. I would prefer the IETF protocols (http://www.zeroconf.org/ZeroconfAndUPnP.html), and perhaps I'll implement them later.

So I (jdougan) have implemented a UPnP stack that has enough support to work with the IGD.  As UPnP uses SOAP for controlling the devices and writing a SOAP stack is a PITA, I've just used the SOAPCore client implementation for that aspect. It's a bit large just for this, but we can find or build something lighter weight later.  For that matter, now we have a SOAP client, we may find more useful things for it to do. The UPnP package, which is still under development, is at the Cobalt mc (Monticello) server in the contributions project.  Besides SOAPCore, it also uses the RBT package to manage the UDP sockets. Both of these are available on SqueakMap and by now the updated versions with underscore assignments removed should be available.

To use the UPnP stack, first you must start the announcement protocol (SSDP):

SsdpListener startListener.

or

SsdpListener restart. 

Then we can get a reference to a WANIPConnection IGD service, which is the service that lets us manipulate port forwarding mappings.  Here we just just grab one at random which will work in most cases because there is only one. If you have more than one NAT router visible you'll have to make a more reasoned decision or manipulate all of them in parallel.

wanIpConnServ := SsdpListener wanIpConnectionServices anyOne.

And now we can manipulate the router:

"Get a OrderedCollection of descriptive OCs giving the port mapping info in a canonical order"
"Order is: { Remote Host. External Port. Protocol, Internal Port. Internal Client. Enabled?. Description. Lease Duration}"
"eventually this will probably get a class of it's own." 

wanIpConnServ getPortMappings.

"External IP Address as a string"

wanIpConnServ getExternalIPAddress.

"Add a port mapping"

wanIpConnServ
    addPortMapping: 'Description String'
    remoteHost: remoteHostIPString " '' is a wildcard and the usual value"
    externalPort: externalPort
    protocol: protocol "TCP or UDP"
    internalHost: internalHostIPString
    internalPort: internalPort
    enabled: isEnabled "should be a 1 or a 0"
    leaseDuration: leaseDuration "0 means unlimited duration"

"Abbreviated way of adding a port mapping that defaults many of the values"
"Most common options set: wildcard remotehost, tcp protocol, enabled, infinite lease" 

wanIpConnServ
    addPortMapping: desc
    externalPort: externalPort
    internalHost: internalHost
    internalPort: internalPort

"Delete a port mapping"

wanIpConnServ
    deletePortMappingHost: remoteHost
    externalPort: externalPort
    protocol: protocol

"alternate method of deleting a port mapping, argument is one of the descriptive arrays from #getPortMappings"

wanIpConnServ
    deletePortMapping: descArray

Dispatchers

I decided to put as much of the NAT Traversal functionality as possible with the dispatcher and routers. The major reason for this was because we will probably eventually have standalone dispatcher/router sets (or even a completely different P2P routing architecture) so we can't depend on having anything other than the dispatcher, the routers and their support code.

In the process of getting there, I've dumped using the TExampleDispatcher. Instead I've merged the code in TExampleDispatcher and KSDKDispatcher and put the result into the new TSessionDispatcher (so named because it associates session ids with routers). From there I've subclassed a CobaltDispatcher which has an understanding of the port forwarding and configuration.

Configuration is handled by the tree of classes rooted in TRouterConfig (I'll probably rename this). They will activate the appropriate port forwarding and tell the dispatcher the resulting information needed. They also tell the dispatcher what local port to use if a specific one is required.

CobaltDispatcher newWithConfig: (TLanOnlyRouterConfig new)

will create a new cobalt dispatcher with the configuration necessary to run on the LAN only. This is essentially the policy used in the demos.

CobaltDispatcher

    newWithConfig: (

       (TManualRouterConfig new)

            internalPort: 12000 ;

            externalPort: 12001 ;

            externalIp: '123.45.67.89' ;

            yourself

    ) 


Will return a CobaltDispatcher configured to assume that there is port forwarding already manually configured with the parameters described.


CobaltDispatcher

    newWithConfig: (TUpnpRouterConfig new)


will return a CobaltDispatcher configured to handle port forwarding automatically via an available UPnP Internet Gateway Device.
 

The CobaltDispatcher has a class variable DefaultConfig that holds a configuration to be used if the argument to #newWithConfig: is nil (oops, missed integration).

In the current version (on 20080525) the CobaltHarness holds a configuration to be used on dispatcher startup and the dispatcher is created in #startDispatcher. The configuration used is in the class variable RouterPrefs on the CobaltHarness.


This whole thing desperately needs some UI.

 

Contact Point

Now we have the port forwarding in place, we have a new problem.  The dispatcher potentially has 2 addresses: the internal to the private network address (usually the address of the local network interface) and the external network address (usually the address of the Internet side of the NAT router). Most of the code in the system is assuming that there is only one address....which puts me deep into refactoring land.

In the interim, I've cleaned up TContactPoint a bit and subclassed a CobaltContactPoint off of it.  The CobaltContactPoint understands that there is possibly an external address and will have additional functionality to tell some registry about it.  It's currently only half done right now.

The problem with all of this so far is that it assumes that you have unfettered access to the dispatcher, so you can call it and get the various bits of addressing information out of it.  In many real production cases this will not be true as the dispatcher will be on a remote  machine, so eventually there will have to be some network protocol between the island/harness/contact point and the dispatcher, probably via the Croquet datagrams in the same fashion as the router is currently communicated with.

Comments