Using VNC when both the server and client are behind NATs

This is a guide on how to use VNC when both the server and client are behind NAT routers.

There are two methods explained - one is direct connection (no proxy server is involved) and the other one requires a proxy server.

The proxy-less method will readily work to handle arbitrary connections between arbitrary programs on the both ends. The proxy-based method one can also be extended to do so with minimal effort.

Background

VNC was created in the early days in the Internet, in the context of machines running in a local network, when one machine can get a direct connection to another.

Since then, it has gotten very popular as a remote access tool - at one point in time it was the only serious contender for "Terminal Services" that one can find in the Windows world.

Its popularity drove its adoption for use beyond simple local networks, and straight to the Internet. Things were easy back then as there were not so many NATs, most connections are direct.

Those days, however, are over. Most of the non-commercial machines today connected to the Internet are almost always behind NAT routers - if not at home, then at the ISP level.

VNC consist of a server and a client (being developed during the heyday of "client-server" model). As a typical client-server, the VNC server runs continuously, waiting for any clients to contact it; and the client runs as needed, initiating the connection to contact the server when needed.

The server does not need to know where its clients are; but the clients needs to know how to contact the server (usually IP address and port number is enough).

The problem with NAT is that the machines located behind the NAT router do not have public IP address that others can connect to. Those it is difficult or near impossible to run servers on machines located behind the NAT.

To solve this problem, VNC has two ways or operating - the standard client-server as described above, or it can operate in reverse client-server model. In this reverse mode, the client is started first and is the one that waits for the incoming connection from the server. Thus it is not a problem if the server is located behint NAT - as long as the client is not also within it.

In other words, for VNC, if only one of the machines (either the server or the client) is behind NAT, then there is not problem at all. Connection can still be established rather easily.

But what happens when both client and server are located behind the NAT?

Most modern routers have the ability to port forward and/or put one of the machines in the NAT on the virtual DMZ (sometimes also called virtual host).

Port forward means that the NAT router is configured so all incoming traffic for a given port will always be redirected to a specific machine inside the NAT. This in effect "expose" that specific machine directly to the Internet as if it is directly connected - but only for that specific port.

Virtual DMZ/Virtual Host. If you want to expose the whole machine (inside NAT) to the Internet, then you you use the Virtual DMZ/Virtual Host feature. When this feature is configured, all incoming traffic on all port (except those in use by the router to serve other machines) will be redirected to that specific machine.

This is good and actually solves the problem, but it has two distinct disadvantages:

  1. You need to configure the NAT router in order to do this (this is the biggest disadvantage).
  2. If you need to communicate with more than one machine, then either:


The methods explained in this article try to address this difficulty. They do not need re-configuration of the NAT routers - they should work as is.

The best solution, of course, is to move to IPv6 where everyone can be on direct connection to the Internet again, just like the old days of IPv4. Then we don't have to play with tricks like this. But while we are in transition, hopefully the tricks outlined in this article is useful.

The methods explained in this article are neither original nor new. It has been invented and used for many years. (See the references for the details). My contribution is how to do this using common tools instead of specialised tools.

The methods described here is familiar to many. As such while it will usually work for home routers; it will probably not work on those on company network where firewalls (not only NAT, but full-fledged firewalls) are being used to manage the connections.

Proxy-less NAT-to-NAT VNC connection (aka NAT-traversal)

Before I begin, I need to acknowledge that the method used here is basically the one used by nat-traverse (reference 2 at the end of this article), and it has this to say:

nat-traverse does not work with gateways which change the port numbers. This is a fundamental problem of nat-traverse's design, as the changed port numbers are (in general) not predictable.

The proxy-less method relies on specific nature of UDP traffic processing as it is carried by NAT routers. You can read the relevant RFCs if you're interested: RFC 1631 and 3022. Simply put - for most routers incoming UDP path from NAT router to a machine behind the NAT is opened when you send outgoing UDP traffic. In another word - we send outgoing UDP packets to tell the NAT router to automatically port-forward incoming UDP packets of similar nature back to us.

For this to work, both the client and the server (actually it doesn't matter which one is which, because the situation is an exact symmetry) need to know the public IP address of their peers. The public IP address here refers to the Internet IP address held by the NAT routers (if your connection has to pass through several routers, then this refers to the outermost NAT routers - it could be your ISP's routers!).

Another tool you need is a tool that can send (and receive) UDP packets with arbitrary but pre-defined local source port, target host address and target source port. You can use the original "netcat" if you wish to, in this example I'm using a variant called "netcat6" because that's the tool that will be included in Fatdog64. You can of course use ncat from nmap, socat, or other similar tools.

You also need pppd - both in kernel and userspace. pppd is required to encapsulate a raw, unreliable pipe into a reliable, ordered TCP-capable pipe. Fatdog64 comes ready with both.


Okay, how to get to know your public IP address? One of the easiest way is:

wget -q -O - icanhazip.com

or if you don't trust icanhazip, just google for "my public IP address". In Fatdog64 you can use the ipinfo utilities to get your public IP address.

Then give this information to your peer and ask about his/her public IP address. This way, both you and your peer know each other's public IP address.

Now to start the connection:

modprobe ppp_generic
! test -p /tmp/pipe && rm -f /tmp/pipe && mkfifo /tmp/pipe
pppd nodetach noauth notty passive 10.0.0.201:10.0.0.202 < /tmp/pipe |
nc6 -u -p 4000 your-peer-public-IP 4000 > /tmp/pipe
On your machine

modprobe ppp_generic
! test -p /tmp/pipe && rm -f /tmp/pipe && mkfifo /tmp/pipe
pppd nodetach noauth notty < /tmp/pipe |
nc6 -u -p 4000 your-public-ip 4000 > /tmp/pipe
On your peer's machine

Remember because the situation is exacly symmetric, you and your peer can easily switch the roles and it would still work. If you don't like to use port "4000" you can change it to any other number as long as all the four ports are identical. (They can be different, but must be in pairs; and that's a story for another time).

Also, note that IP address 10.0.0.201 will become your IP address and the other one (10.0.0.202) will become your peer's IP address. Make sure that both addresses do not collide with existing IP address(es) in your and your peer's machines. You can of course happily modify them to any IP address as long as they are within the same subnet.

Once done, all you need to do is wait until the interface "ppp0" is up on both sides. Then you can start pinging your peer's IP address (10.0.0.202 in this example). Keep the pinging - you need to keep the link active otherwise your NAT router will shutdown the connection behind your back.

Once the connection is up (ppp0 exist and you can ping your peer and vice versa) then you can do anything that you need to do since basically you have a direct TCP/IP connection to your peer. You can connect through ssh, transfer files using Samba, or anything.

For this article's purpose, your peer can start the VNC server on their end and you connect using the client as usual:

x11vnc
On your peer's machine

vncviewer -bgr233 10.0.0.202
On your machine

Again, you can change roles because the situation is exactly symmetric.

Once you're finished with this, stop the ping and kill pppd or nc6 by using Ctrl-C on the xterm window that runs pppd/nc6.

Note 1: when I was looking at this, I tried to use Bash networking facilities (particularly /dev/udp pseudo-device). It did not work, because for this to work, you need to be able to control 3 parameters: source port, target host, and target port. Bash only allows you to control target host and target port --- the source port is randomly allocated.

Note 2: busybox version of "netcat" does not work unless you compile it with ENABLE_NC_110_COMPAT because otherwise it cannot do UDP.

Proxy-based NAT-to-NAT VNC connection

If the proxy-less connection does not work for whatever reason, then your fallback option is to use a proxy. A proxy is a machine that sits on the Internet directly (not within NAT). Both the VNC server and the VNC client will connect to this proxy, and the proxy will "route" messages between the server and the clients.

It is not efficient (since the routing is done at least at layer 4 or layer 7 depending on how you see it) and it will tax the proxy machine in terms of CPU power and network bandwidth (especially network bandwidth - it will have to handle twice the bandwidth of the client and server combined); but at least you can get a connection.

How to get a VNC proxy machine? It is unlikely you can get a public VNC proxy because of the above. Your best bet is to buy a VPS server that is dedicated for this very purpose.

If you can get over the above facts; then the next step is to configure your proxy. There are two ways to configure the proxy: using a repeater, or using a transferer. They amount to more or less the same thing but I need something to differentiate them - so pardon me for the jargon.

1. VNC Repeater

There are two ways to run a VNC proxy. One is to use the so-called VNC repeater. This technology was first developed by UltraVNC but it is now quite popular and available for many VNC derivatives.

Here is an explanation from the horse's mouth of how it works. You want to use mode 2 as that's the mode that enables both server and clients to be behind NATs.

You can download the repeater software from UltraVNC website itself; but unless you're planning to use Windows on the repeater OS, I'd recommend you get them from somewhere else because they are updated more often.

The repeater software comes into flavours: a C source code that you need to compile and build yourself (preferably on the proxy machine; or at least on the machine that runs on the same OS as the proxy machine); and a Perl script that you can just run as is. Both require that you run them as daemons (that is, server process) on the proxy machine; continuously listening for connections from both servers and clients.

In either case, please note that for this to work, you will need that both VNC client and server supports UltraVNC repeater extension. Standard server and client will not work. And example of VNC client that will work with these repeaters is SSVNC.

The Perl script version is available here. Look for a file called ultravnc_repeater.pl (or you can download the file directly from this link). I have tested this script and it works beautifully.

The C source code version is available here. It was a port of the original UltraVNC repeater version (which was Windows-only) so it is more or less is the "official" version.

There are other unofficial versions too:

I have not tested any of the C version, so I can't comment on their usability. The last link is especially interesting because it claims to work with standard VNC clients instead of UltraVNC-extended client.

Once the proxy server is setup, to initiate connection to the proxy, on server side:

x11vnc -connect repeater=ID:1234+host:port
where "1234" is a unique ID that identifies this server to the repeater.

On the client side:

ssvncviewer -repeater ID:1234 host:port

The client uses the same ID so that the repeater can connect it to the correct server.

The host:port on both server and client refers to the proxy server. The ports are not the same; usually the server port is 5500 and the client port is 5900. x11vnc and ssvnc is used in this example but you can easily use other UltraVNC-compatible server and clients.

2. VNC Transferer

The VNC connector is a relatively newer invention that the repeater. It enables standard server and client to connect to each other. From usage point of view it works similarly to the repeater but internally it works differently.

The software is available as a CGI Perl script courtesy or Karl Runge (the author of both ssvnc and ultra_repeater Perl script), here: vncxfer.

To use this, you need to have a web server running on the proxy server and configure it to run vncxfer script as CGI. When connected, the script will create a server process that will handle both server and client connections. The server process will create dynamically-allocated ephemeral port pair; one for the server and one for the client.

Connection is initiated by accessing the CGI URL using a web browser. The web page you see will ask for a "session id"; once that given it will launch a session with a random port for the server and the client. You must initiate both client and server connection within a given time (by default is 300 seconds) or the session will be cancelled.

Launch VNC server to connect to proxy using -connect as usual (no ID is required):

x11vnc -connect host:port

and launch the client as usual (no -repeater string is required)

vncviewer host:port

The host:port on both server and client refers to the proxy server. The ports are not the same; the webpage will tell you which port is for the server and which port is for the client. x11vnc is used in this example but you can easily use other server and clients.

Once the connection is ended, vncxfer will terminate the ephemeral session.

Note: you can't run this on ordinary webhosting account. You need VPS account because:

  1. Ordinary webhosting account usually forbids a long-running process in response to a CGI call (the vncxfer server process is effectively that)
  2. Ordinary webhosting account usually don't allow access to arbitrary ports other than HTTP and HTTPS (or SMTP).

Closing words

Other than VNC, there are other similar services that provide (or sell) remote access service for those behind NATs (mostly for Windows machines). Most of these providers have a client that you will need to install on the client machine and there it will run continuously; either waiting for command to connect to remote machine; or waiting for the remote machine to connect and take over the machine.

While I am not privy to the internals of their technology; I believe that they do it using one of these two methods; most likely the proxy-based one since it is more reliable; and if you're prepared to pay the (performance and monetary) cost, you can do a lot of tricks that can pass through even stricter NATs and firewalls.

This article punctures the myth that just because one is behind a NAT, one is safe from attack because the attacker cannot reach to one's machine, because, well, if I am behind the NAT I am unreachable, right? right?

It should be obvious now that the answer is no - your machine can still be contactable even if it is behind your NAT router.

A knife can be used for good and bad purposes. This articles explains to you how to use the NAT-traversal for a good purpose - but the same method can be used for bad purposes as well. Now that you are aware that your NAT doesn't offer your the protection you think it does; you should be taking more steps to protect yourself.

References

  1. http://samy.pl/pwnat/ (great idea but not working for me)
  2. http://m19s28.dyndns.org/iblech/nat-traverse/ (works - this is the inspiration for this article)
  3. https://github.com/gnubert/katz (works - if you use this you don't need nc6 and pppd as it does both)