Port forwarding is a convenient way to circumvent well-intentioned firewall rules, or to access resources on otherwise unaddressable networks, particularly those behind NAT (with addresses such as 192.168.0.1).
However, it has a shortcoming in that it only allows us to address a specific host and port on the remote end of the connection; if we forward a local port to machine A on the remote subnet, we can’t also reach machine B unless we forward another port. Fetching documents from a single server therefore works just fine, but browsing multiple resources over the endpoint is a hassle.
The proper way to do this, if possible, is to have a VPN connection into the appropriate network, whether via a virtual interface or a network route through an IPsec tunnel. In cases where this isn’t possible or practicable, we can use a set up via an SSH connection to delegate all kinds of network connections through a remote machine, using its exact network stack, provided our client application supports it.
Being command-line junkies, we’ll show how to set the tunnel up with ssh and to retrieve resources on it via , but of course graphical browsers are able to use SOCKS proxies as well.
As an added benefit, using this for browsing implicitly encrypts all of the traffic up to the remote endpoint of the SSH connection, including the addresses of the machines you’re contacting; it’s thus a useful way to protect unencrypted traffic from snoopers on your local network, or to circumvent firewall policies.
First of all we’ll make an SSH connection to the machine we’d like to act as a SOCKS proxy, which has access to the network services that we don’t. Perhaps it’s the only publically addressable machine in the network.
$ ssh -fN -D localhost:8001 remote.example.com
In this example, we’re backgrounding the connection immediately with -f, and explicitly saying we don’t intend to run a command or shell with -N. We’re only interested in establishing the tunnel.
Of course, if you do want a shell as well, you can leave these options out:
$ ssh -D localhost:8001 remote.example.com
If the tunnel setup fails, check that AllowTcpForwarding is set to yes in /etc/ssh/sshd_config on the remote machine:
AllowTcpForwarding yes
Note that in both cases we use localhost rather than 127.0.0.1, in order to establish both IPv4 and IPv6 sockets if appropriate.
We can then check that the tunnel is established with on Linux:
# ss dst :8001State Recv-Q Send-Q Local Address:Port Peer Address:PortESTAB 0 0 127.0.0.1:45666 127.0.0.1:8001ESTAB 0 0 127.0.0.1:45656 127.0.0.1:8001ESTAB 0 0 127.0.0.1:45654 127.0.0.1:8001
Now that we have a SOCKS proxy running on the far end of the tunnel, we can use it to retrieve documents from some of the servers that are otherwise inaccessible. For example, when we were trying to run this from the client side, we found it wouldn’t work:
$ curl curl: (6) Couldn't resolve host 'private.example'
This is because the example subnet is on a remote and unroutable LAN. If its name comes from a private DNS server, we may not even be able to resolve its address, let alone retrieve the document.
We can fix both problems with our local SOCKS proxy, by pointing curl to it with its --proxy option:
$ curl --proxy socks5h://localhost:8001 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> <title>Contacts</title>...
Older versions of curl may need to use the --socks5-hostname option:
$ curl --socks5-hostname localhost:8001
This not only tunnels our HTTP request through to remote.example.com and returns any response, it does the DNS lookup on the other end too. This means we can not only retrieve documents from remote servers, we can resolve their hostnames too, even if our client side can’t contact the appropriate DNS server on its own. This is what the h suffix does in the socks5h:// URI syntax above.
We can configure graphical web browsers to use the SOCKS proxy in the same way, optionally including DNS resolution:
Browsers are not the only application that can use SOCKS proxies; many IM clients such as and can use them too, for example.
If this all works for you and you’d like to set up the SOCKS proxy on the far end each time you connect, you can add it to your in $HOME/.ssh/config:
Host remote.example.com DynamicForward localhost:8001
With this done, you should only need to type the hostname of the machine to get a shell and to set up the dynamic forward in the background:
$ ssh remote.example.com