SSH Tunnel: Dynamic Port Forwarding

With a local or remote SSH tunnel the ports on both the local and remote machines must be specified at the time of creating the tunnel. But what if you need something more flexible? That’s where Dynamic Port Forwarding comes into play.

In previous posts we looked at using SSH tunnels for local and remote port forwarding.

What is Dynamic Port Forwarding?

Dynamic port forwarding sets up a connection that will forward traffic to a remote server, irrespective of the destination port. Effectively it turns the SSH client into a SOCKS5 proxy server.

This is different to local and remote port forwarding primarily in the way that ports are handled.

In local and remote port forwarding, both the source port (on the client or local host) and the destination port (on the remote or target host) are specified. Forwarding is set up only for these specific ports, and the tunnel directs traffic from the source port to the destination port.

In contrast, no destination port is specified with dynamic port forwarding. Only a local port is specified, which acts as a listening socket for a SOCKS proxy server. Requests to all local ports are then channeled through this proxy to their destination.

Some advantages of a SOCKS5 server:

  • protocol agnostic (can handle HTTP and many others)
  • TCP and UDP
  • DNS resolution (happens on the proxy server rather than the client).

Create Tunnel with Dynamic Port Forwarding

Create a SSH tunnel from the remote host to the SSH server. Since the remote host does not have access to the public network it needs to use the private IP of the SSH server to make this connection.

ssh -D 1080 -C -N -f ubuntu@172.31.45.250

Here’s a breakdown of the components of that command:

  • -D 1080 — dynamic port forwarding via 1080;
  • -C — compress all data;
  • -N — do not execute remote command or shell;
  • -f — run in background.

Testing the SOCKS5 Proxy

We can now send requests to a variety of different services via the SSH tunnel.

HTTP

Let’s test the tunnel with HTTP traffic. Try sending a request to an API.

curl --socks5 localhost:1080 http://ifconfig.me
18.170.229.50

The response contains the public IP of the SSH server, indicating that this is where the request appears to originate.

APT

A perennial challenge (for me anyway!) with a private EC2 instance is updating or installing packages. Fortuitously, you can also use the tunnel to install packages via APT. Update /etc/apt/apt.conf.d/01proxy on the remote host:

Acquire::http::Proxy "socks5h://localhost:1080";

🚨 This is different to the configuration specified in the previous post on remote port forwarding. Both the protocol and port have changed.

Now you should be able to freely proxy APT traffic through the tunnel.

sudo apt update
sudo apt install docker.io

Git

Perhaps the software you want to install is not available via APT or you want to install from the bleeding edge. Git won’t work directly (because you’re on a private network), but you can set it up to work via the SSH tunnel.

Configure Git to use the proxy.

git config --global http.proxy 'socks5://127.0.0.1:1080'

Use Git as if you were on a public network.

git clone https://github.com/jgm/pandoc.git

SOCKS5 Utilities

There are a few utilities which can make it easier to work with a SOCKS5 proxy.

ProxyChains

Having to set up a proxy for each application can be onerous. ProxyChains exists to solve precisely this problem. It can be used to proxy data from any application through the tunnel.

sudo apt install -y proxychains4

Edit the configuration file, /etc/proxychains4.conf, and give the details of the SOCKS5 proxy on the dynamic SSH tunnel.

[ProxyList]
socks5  127.0.0.1 1080

Test this out.

proxychains curl http://ifconfig.me
18.170.229.50

Success (despite not specifying the --socks5 option)! Why? Prefixing the command with proxychains will automatically route traffic through the proxy.

This will work with APT too. First delete /etc/apt/apt.conf.d/01proxy then inject proxychains into the command.

sudo proxychains apt update

Let’s try this out with Git.

# Remove the Git-specific proxy configuration.
rm ~/.gitconfig
# Clone a repository via the proxy by using ProxyChains.
proxychains git clone https://github.com/jgm/pandoc.git

Nice! A way to send all traffic through the tunnel.

tsocks

An alternative to ProxyChains is tsocks.

sudo apt install -y tsocks

Edit the configuration file at ``.

server = 127.0.0.1
server_type = 5
server_port = 1080

Now prefix any command that should use the SOCKS5 proxy with tsocks.

tsocks curl http://ifconfig.me
sudo tsocks apt update

Conclusion

Local and remote SSH tunnels provide a mechanism to send traffic to a particular service by tunneling traffic to a specific port. If, however, you need something more flexible then dynamic port forwarding via a SSH tunnel will set up a SOCKS5 proxy through which you can tunnel all of your traffic, irrespective of the destination port.