A few definitions
General definition of a tunnel
A tunnel is a special connection that makes it possible to reach a machine on a computer network that is not reachable directly (as illustrated in figure 1 below) but only through an intermediate host.
To reach this machine, instead of sending the data directly to the destination host, it should be sent to the entrance of the tunnel. The data will then be encapsulated in another protocol and transported to the intermediate host that forms its exit. From there, the data will be sent to the destination host as if it had been emitted from the intermediate host. (This general principle is illustrated in figure 2 below.)
This intermediate host located on the edge of the protected network is often called a getaway: following the principle of the bastion host, it is a host that can be reached from outside the network but that can also access the hosts only accessible from within. Its role is to allow a limited and authenticated access to the protected network from the outside. The use of a tunnel allows or eases this bounce on the getaway host and will even make this constraint mostly unnoticeable.
Figure 1. Example of an impossible direct connection. [toggle description]
Figure 2. Principle of a local forwarding static SSH tunnel. [toggle description]
Difference between static and dynamic tunnels in OpenSSH
As stated in the definition above, data sent to the entrance of a tunnel is then re-emitted from its exit to their final destination. Among the tunnels created using the OpenSSH client, we will distinguish two types of tunnels depending on whether the final destination of the data is provided at the creation of the tunnel (it is then referred to as a static tunnel) or if the software application sending the data must specify by itself the final destination for each data stream it sends (this is referred to as a dynamic tunnel).
Static tunnel
In a static tunnel, the incoming data passes through the tunnel and is then systematically sent to the final destination (host and port) specified when the tunnel was created.
This solution has the advantage of making the tunnel totally transparent for the application that sends the data: it can send it to the entrance of the tunnel the same way it would send it to the destination host. The tunnel will be responsible for addressing it, from the other side, to the destination specified at the time the tunnel was created. The disadvantage is obviously that the application cannot send the data to another destination, unless it uses another tunnel.
Two types of static SSH tunnels are possible with OpenSSH:
- in a local forwarding tunnel (illustrated in figure 2 above), the tunnel listens for incoming data on the host where the SSH tunnel was initiated (the host on which the SSH client was run, which is often your local machine), forwards the data to the SSH server to which the SSH client is connected and, from there, sends it to its final destination;
- in a remote forwarding tunnel (illustrated in figure 3 below), the data travels in the opposite way: the tunnel listens for incoming data on the SSH server (the host to which the SSH client is connected), forwards the data to the host running the SSH client and, from there, sends it to its final destination.
Figure 3. Principle of a remote forwarding static SSH tunnel. [toggle description]
Dynamic tunnel
In the case of a dynamic tunnel, the tunnel is created without specifying a final destination for the data it receives on its entrance. The application sending data into the tunnel must therefore also specify to which destination (which host and which port) the data should be re-emitted. (See figure 4 below for an illustration of this principle.)
The SOCKS protocol is used to provide the destination information to the tunnel. The dynamic tunnel then acts as a SOCKS proxy that receives data on a host (the entrance of the tunnel) and re-emits it from another host (the exit of the tunnel).
Figure 4. Principle of a dynamic SSH tunnel. [toggle description]
The possibility of accessing any host reachable from the other side of the tunnel and the fact that, from the point of view of the final target machine, the connection seems to come from the machine at the end of the tunnel (the SSH gateway in our example) make that type of tunnel look, in terms of functionality, like to a VPN (Virtual Private Network), except that only the applications configured to use this tunnel/proxy will be redirected to this pseudo-VPN.
Opening a static tunnel
Required information
In practice, to open a SSH tunnel that we will call static (as opposed to a dynamic tunnel described below), we need to provide three details:
- the DNS name of the host and the account name to connect to using SSH (this host will be the getaway host in our example describing a connection to a remote protected network). This is the host on which the exit of the tunnel will be located. This information should have been provided to you after you requested remote access from the administrator of the network you are connecting to.
- the TCP port and network interface of the local host (where the OpenSSH
client is running) where the entrance of the tunnel should be located:
- the choice of this port is free as long as it is not currently in use (and
is over 1024 if you are using a non-privileged account, i.e. other than the
rootaccount, as this type of users can only use these ports), - you will also have to choose on which network interface the port will be
opened. This choice is particularly important in terms of security, as, if
you decide to use an interface connected to a network, any host on this
network can potentially send data into the tunnel if the port is not
protected by a local firewall (see the note below on security). If no
interface is provided here, by default the loopback interface will be used
(as if you had provided the address
localhost) and the tunnel will only be reachable to the users of the local host.
- the choice of this port is free as long as it is not currently in use (and
is over 1024 if you are using a non-privileged account, i.e. other than the
- the DNS name or IP address and the TCP port of the host to which the data that went through the tunnel will be re-emitted to from its ending: as we already mentioned above, this information is required when this type of tunnel is created (this is the reason we call it a static tunnel in this document).
Note on security: please note that, once the tunnel is open, no additional authentication is applied when receiving data at the tunnel entrance. This means that OpenSSH will encapsulate and transport through the tunnel all data received on the incoming port and send them to the target host from the other side of the tunnel.
When the entrance of the tunnel is bound to an interface connected to a network, keep in mind that any host on this network will be able to send data into the tunnel, and thus to the destination host, if the incoming port is not protected by a local firewall.
It is therefore recommended to bind the entrance of the system to the loopback
interface localhost (corresponding to the address 127.0.0.1 or ::1),
which is the interface used by default if none is specified when the tunnel is
created, as this interface is only reachable by the users of the local host.
Even in this case, you should consider the risks caused by potential malevolent
system users.
As the creator of a remote access to the protected network on the other side of the getaway, you have the responsibility to make sure not to create a security risk by giving unknown users access to protected resources.
Command to open the tunnel
Local forwarding tunnel
In this document, we will assume that you own an account whose identifier is
hkrustofski (from my beloved character Herschel
Krustofski) accessible from
the internet by SSH on the host named gateway.example.org that acts as a SSH
gateway having access to a protected network whose hosts use the DNS suffix
.example.org.
The following command will open a tunnel between your local host and the remote
machine to which you are connecting using SSH (in our example, it is a gateway
host located on a firewall-protected network) to reach the TCP port 80 of a
host named host1.example.org:
ssh -N -L 3033:host1.example.org:80 hkrustofski@gateway.example.org &
Here is a description of the three arguments of the ssh command:
- the option
-N(which is optional, but I find it handy) is used here to prevent the opening of a command interpreter on the remote host. The tunnel will then remain open until the SSH command we ran on the background here is killed (using a command such askill %1in the same shell session, for example). Without this option, the SSH client would run a command interpreter (Unix Shell) and the connection, as well as the tunnel, would terminate when you exit this remote shell; - the option
-Lis the option requesting the opening of the tunnel and specifies its entrance and exit usually in the form[bind_address:]port:host:hostport:- in this example, the entrance will be set on TCP port
3033(it could be any port available above 1024). As no DNS name nor IP address is specified before the port number, it is as iflocalhost:3033would have been provided instead: it means that the tunnel will listen on TCP port 3033 on the local loopback interface of your host (of address127.0.0.1on IPv4, or::1on IPv6). (You can also specify*:3033to listen on all of the network interfaces of the host.), - data sent to the tunnel (in our example received on TCP port
localhost:3033of your local host) will be transmitted to the hosthost1.example.orgon port80through the gateway to which we are connecting using SSH. Note that the DNS namehost1.example.orgwill be resolved at the exit of the tunnel, on the SSH getaway (this name will likely not be visible outside of the protected network). As a side note, if the service you are trying to reach is located on the very host the tunnel leads to, you should specify the namelocalhostas a destination host to reach it; this name would then refer to the local loopback interface of the remote host;
- in this example, the entrance will be set on TCP port
hkrustofski@passerelle.example.orgrepresents the identifier of your account on the SSH gateway and its DNS name. This account must have been provided to you by the administrator of the gateway host. Upon connection, you will have to authenticate using a password or a SSH key previously set up.
The tunnel created by this command corresponds to the one illustrated by the figure 2 above.
Remote forwarding tunnel
To open a tunnel operating in the opposite way, meaning that it will transmit
data sent to a port on the remote host to a host reachable from your local
network, you would use the option -R on the command line, which is used the
same way as -L.
The following command will thus open a tunnel between a remote host to which
you are connecting using SSH and the host host2.example.net through your
local host (and therefore potentially located on your own firewall-protected
network):
ssh -N -R 3033:host2.example.net:80 hkrustofski@remote.example.org &
In the above example, the tunnel entrance will be set up on TCP port 3033 of
the loopback interface of the host remote.example.org and will allow users of
the remote host to reach the TCP port 80 on the host host2.example.net
through your local host. This allows remote users to bypass the firewall of
your own network and to connect to a host reachable from your local network.
Just like the local forwarding tunnel (created using the -L option), this
remote forwarding tunnel bypasses the security rules enforced on your network
and should therefore be used with great care.
The figure 3 above illustrates the tunnel resulting from this command.
Using the OpenSSH client configuration file
Local forwarding tunnel
Should the tunnel be used on a regularly basis, its use can be made more
convenient by adding an entry in the configuration file of the OpenSSH client,
~/.ssh/config. This will avoid having to specify the tunnel parameters on the
command line.
A connection name must be provided with the entry defined in the configuration
file to specify the parameters of the tunnel (in our example, it is simply
tunnel, but could be about any name of your choice). This tunnel can later be
created simply by providing the connection name to the OpenSSH client the same
way a remote host name would. (Note that the option IdentityFile used below
is only required if you need to designate a SSH key for the connection other
than the default key file, which is something like ~/.ssh/id_ed25519).
Here is an example of configuration (that could be added to your file
~/.ssh/config) matching our command line example above:
Host tunnel
Hostname gateway.example.org
User hkrustofski
IdentityFile ~/.ssh/id_hkrustofki
LocalForward 3033 host1.example.org:80
Once this configuration set up, the following command will open the tunnel to
the host gateway.example.org and it will be configured the exact same way as
in the ssh command given as an example in the previous section of this
document (the option -N is here again perfectly optional):
ssh -N tunnel &
Arguments provided with the -L option used on the command line will here be
specified with the directive LocalForward that takes two arguments:
- the first,
[address:]port(here3033) is the optional IP address (or the DNS name resolving to a IP address set up on the local host) and the TCP port of the entrance of the tunnel; - the second,
address:port, is the IP address (or a DNS name that will be resolved at the end of the tunnel) and the TCP port to which the data that went through the tunnel will be forwarded to at the exit of the tunnel.
Remote forwarding tunnel
The directive corresponding to the -R option on the command line is
RemoteForward and operates the same way as LocalForward shown above, except
that the entrance of the tunnel will be located on the remote host (the SSH
gateway in our example) and its exit will be on the local host.
The remote forwarding tunnel defined on the command line in the example of the
earlier section can thus be defined using the following entry in the
configuration file ~/.ssh/config:
Host tunnel
Hostname remote.example.org
User hkrustofski
IdentityFile ~/.ssh/id_hkrustofki
RemoteForward 3033 host2.example.net:80
Opening a dynamic tunnel
Difference with a static tunnel
As we have seen before, a static SSH tunnel created with the option -L only
allows you to connect to a remote address and port statically specified at the
tunnel creation.
In contrast, as we already discussed in the definition section above, a dynamic tunnel consists of a kind of generalization of this type of SSH tunnel: instead of specifying, when the tunnel is created, the recipient of all data that will pass through the tunnel, it is the programs sending data into the tunnel that must use the SOCKS protocol to specify the host and port to which OpenSSH will transmit their data at the exit of the tunnel. This is the reason why a dynamic SSH tunnel can be considered like a SOCKS proxy.
Just like with a static tunnel, to the destination host, everything happens as if the connection had been initiated from the host where the tunnel exit is located.
Command to open the tunnel
The dynamic tunnel, which will thus act as a SOCKS proxy, can be opened using
the -D option of the OpenSSH client, which means running a command of the
following type:
ssh -D 1080 hkrustofski@passerelle.example.org
In this example, the -D option only provides a port without specifying the
local network interface to use. The TCP port 1080 of the loopback interface
(of address 127.0.0.1) will then be used, as if the argument localhost:1080
was provided. To listen on another network interface of the host, a DNS name or
a corresponding IP address bound to the host must be provided, for example
-D 192.168.1.27:1080 (specify * to listen to all the network interfaces,
for example -D '*:1080').
The directive DynamicForward 1080 (the equivalent of the -D option) can
also be used in an entry of the OpenSSH client configuration file
~/.ssh/config like in the example below (where no DNS name nor IP address of
a network interface of the host is specified, which is here again equivalent to
specifying DynamicForward localhost:1080):
Host passerelle.example.org
User hkrustofski
DynamicForward 1080
Below is an extract of the documentation related to this directive, provided by the manpage ssh_config(5):
DynamicForward
Specifies that a TCP port on the local machine be forwarded over
the secure channel, and the application protocol is then used to
determine where to connect to from the remote machine.
The argument must be [bind_address:]port. (...)
Currently the SOCKS4 and SOCKS5 protocols are supported, and
ssh(1) will act as a SOCKS server. Multiple forwardings may be
specified, and additional forwardings can be given on the command
line. Only the superuser can forward privileged ports.
After opening the tunnel, the SOCKS proxy (supporting the versions 4 or 5 of
the protocol) of our example is then available on port TCP 1080 on the
loopback interface (using the address localhost or 127.0.0.1) for use by
any applications supporting this type of proxy (see below).
Use of the SOCKS proxy
General use: the example of tsocks
If you want to use the SOCKS proxy of an OpenSSH dynamic tunnel within an application (such as a web browser for instance), first check in its settings whether it can use a proxy of this type. It is the case with many web browsers, like Firefox for instance (whose configuration is briefly covered below).
Applications that do not natively support SOCKS proxies can usually still be forced to use such a proxy (on GNU/Linux, and probably on other systems too) through tools like proxychains or tsocks that will intercept system calls relative to network connections initiated by the application and transparently redirect them to the configured proxy.
To use tsocks for example, you will first need to specify the SOCKS proxy you
want to use in its configuration file /etc/tsocks.conf. Here is a minimal
example:
5
1080
Attention : with tsocks, the DNS lookup is performed on your local host,
not on the other side of the tunnel. You might therefore need to use an IP
address to designate the service you want to reach through the tunnel instead
of the DNS name, if this name is not visible from your local host.
After configuration, you can then run the desired application using three different (but strictly equivalent in terms of results) ways:
- prefix the command of your application with the command
tsocks: - modify by yourself the environment variable
LD_PRELOAD, in the environment of your application: - run the command “
. tsocks -on” (do not forget the leading dot, synonym of thesourceshell function) in your shell before running your application, to modify theLD_PRELOADvariable in the environment of your current shell (this environment is inherited by applications spawn from this shell). “. tsocks -off” can be used to revert the configuration:
(Of course, running ssh using tsocks is purely demonstrative here, as the
OpenSSH client can use a SOCKS proxy directly, as described further below.)
Update: the tsocks software is nowadays no longer officially maintained and could be unavailable on some recent systems. It can be replaced by other products that provide a similar or equivalent functionality like ts-wrap or proxychains-ng (I have not tested them and I cannot guarantee neither their functionalities nor their quality).
Using the proxy with Firefox
To use Firefox through a SOCKS proxy (which means all network connections will be forwarded to the proxy), its settings must be modified as follows (the instructions are given for Firefox 144 and may have changed in later versions):
- in the tab
General, go down to the bottom of the page, sectionNetwork Settingsand click on the buttonSettings; - in the configuration window that pops up, choose the option
Manual proxy configurationand specify the name or IP address of your proxy in the boxSOCKS Hostand the port in the boxPorton the same line (in the example given above, the host would belocalhostand the port1080).
The DNS is by default proxied on Firefox (meaning the names are resolved on the
other side of the tunnel), which is usually what you want to do. To disable the
proxying of the DNS (and have DNS names looked up on the local host), uncheck
the box Proxy DNS when using SOCKS v5 (a similar box exists for SOCKS v4) at
the bottom of the Connection Settings window.
Once these parameters are set up, everything will work as if your browser were on the remote machine, on the other side of the dynamic tunnel.
Transparent use of the tunnel with OpenSSH
The OpenSSH client can easily and transparently use a SOCKS proxy (provided by a dynamic tunnel), which makes it possible to easily connect, using SSH, to hosts only reachable through a tunnel.
This is possible through the ProxyCommand directive in the configuration file
~/.ssh/config (or the option -o "ProxyCommand ..." on the command line, as
no dedicated option exists) as demonstrated in the example below. Combined with
the option -W of the OpenSSH client, it is even possible to automatically
open the dynamic tunnel when OpenSSH initiates a connection. This enables the
transparent opening and closing of the tunnel when using a connection that
requires it.
To illustrate these possibilities and to continue with the example already
developed above, we will define two entries in the OpenSSH client configuration
file ~/.ssh/config:
- the first one represents the dynamic tunnel connected to our SSH getaway
(here
getaway.example.org); - the second one represents a host (named
host1.example.org) located behind the remote firewall, reachable through the tunnel/proxy:
Host gateaway.example.org
User hkrustofski
DynamicForward 1080
Host host1.example.org
User hkrustofski
ProxyCommand ssh -W %h:%p getaway.example.org
In the arguments given to ProxyCommand above, the values %h and %p are
replaced by OpenSSH respectively by the host name and port number of the SOCKS
proxy the OpenSSH client should use. For more details, refer to the
documentation on ProxyCommand in the manual page
ssh_config(5).
With this configuration, an OpenSSH connection to the host host1.example.org
can then be transparently opened using the command:
ssh host1.example.org
This configuration can also be used with other OpenSSH client commands, such as
scp:
scp fichier.dat host1.example.org:vers/le/repertoire/
Note that the DNS lookup of the provided host name will always be performed on the other side of the tunnel, which means, in our example, on the SSH getaway.
If you would like to connect to more than one host in the protected network,
you would not have to create an entry for each of them like we did for the host
host1.example.org above.
Instead, you could define an entry valid for all the host names that match the
pattern *.example.org which would trigger the opening and the use of the
tunnel/proxy. An example of such a configuration is described in the following
section.
Note: on older versions of OpenSSH where the client had no option -W,
it was required to use the tool netcat (in its openbsd version, then
provided on Debian by the package netcat-openbsd) to forward network
connections to the tunnel/proxy, and the tunnel had to be manually opened
before using an OpenSSH connection that required it.
The configuration then looked like this:
Host getaway.example.org
User hkrustofski
DynamicForward 1080
Host host1.example.org
User hkrustofski
ProxyCommand /bin/nc.openbsd -X 5 -x 127.0.0.1:1080 %h %p
And it had to be used with two commands similar to the ones below (the first one opens the tunnel before the second one initiates the OpenSSH connection that requires it):
ssh -N gateway.example.org &
ssh host1.example.org
Using the tunnel for a set of SSH connections
When we have a set of hosts for which the SSH connection requires the use of a tunnel (like in the example given above where a SSH gateway is used to connect to hosts in a protected network), it is possible to automate the use of the tunnel for the whole set of hosts using a single entry in the configuration file. This makes it unnecessary to create an entry for each and every host whose access requires the use of the tunnel.
For this, we take advantage of the possibility offered by the OpenSSH
configuration file to apply a common configuration to a set of machines
matching a specific criterion. In the simplest case we are presenting here, we
can define an entry that will be applied to hosts having a specific common part
in their DNS name, like, for example, a name matching the pattern
*.example.org.
Below is an example of the ~/.ssh/config file setting up the transparent use
of a dynamic tunnel (playing the role of a SOCKS proxy) and applying its use to
connections to any host whose name ends with .example.org:
Host gateway
Hostname getaway.example.org
User hkrustofski
IdentityFile ~/.ssh/id_hkrustofki
DynamicForward 1080
Host *.example.org !getaway.example.org
User hkrustofski
IdentityFile ~/.ssh/id_hkrustofki
ProxyCommand ssh -W %h:%p getaway
In the example above:
- the first entry defines the access to a SSH server defining the dynamic tunnel allowing the access to hosts on a remote internal network (the server we earlier called the SSH getaway);
- the second entry will be applied to all hosts whose name ends in
example.org, except forgetaway.example.orgthat represents the SSH getaway where the tunnel ends and that is by definition directly reachable (see the note below); - this second entry will automatically open and use the tunnel thanks to the
ProxyCommanddirective we described earlier; - the directive
IdentifyFileis optional and designates a file holding a specific SSH key used to connect to the relevant hosts (useful when using a key different from the default one).
Once this configuration is set, any connection to a host such as
host.example.org will open and use the tunnel/SOCKS proxy which will relay
connections through the host passerelle.example.org.
Note concerning the exclusion of gateway.example.org in the second entry :
in the example above, we had to exclude the host gateway.example.org from the
second configuration entry (using a pattern beginning with !) as, starting
with OpenSSH version 6.6 (and perhaps some other versions before), OpenSSH
re-applies the configuration defined in ~/.ssh/config for the host name
corresponding to an alias, like for gateaway.example.org above. Without this
exception, any connection to a host having a name ending in .example.org
would generate an infinite loop as the client would try to open the tunnel
through the getaway to connect to the getaway itself.
About the criteria selecting the affected hosts: if you cannot define a
criterion on the DNS name to designate the hosts that require the use of the
tunnel (i.e. in cases where all hosts do not end with a common suffix such as
.example.org or where only a subset of the hosts matching this criterion
require the use of the tunnel), OpenSSH makes it possible to define more
complex criteria to select the hosts for which a particular configuration is
applied (up to the use of a customized external command responsible for
deciding whether the connection to a host should use the corresponding
configuration block). For more details, refer to the documentation of the
keywords Host and Match of the OpenSSH client configuration in the manual
page ssh_config(5).
Tip for an everyday use
Although a little out of context, this small complementary tip is provided for the sake of completeness in the description of the tools and configurations used in the concrete case which serves as an example throughout this document (that of a connection to a remote protected network accessible via a SSH gateway).
Advanced users comfortable with the Unix command line can skip this section.
In our example above, we use the DNS name to determinate whether the SSH access
to a host requires the use of the tunnel (it is applied to hosts whose name
matches the pattern *.example.org). When using the OpenSSH client, we
therefore must use the full host name to trigger the opening and use of the
tunnel.
In the everyday use of a command interpreter, typing the full name can be burdensome. To avoid typing it on each connection, a little bash function can be useful, like the following one that completes its last argument with the domain name of the hosts that use the tunnel:
The body of the bash function sshi above might seem abstruse, but is in
reality rather simple in principle. It calls the ssh command while reusing
the arguments given to the function:
- it first reuses as-is the arguments given to the function, except the last
one (the term
${@:1:$(($#-1))}selects in the array$@that holds the arguments of the function, the elements1until$# - 1, meaning until the last but one, included); - it then replaces the last argument (which is argument number
$#whose value can be obtained, in bash, using the expression${!#}, which is similar to the commandeval echo '$#') by suffixing it with the domain name${!#}.example.org.
If this kind of syntax or functionalities brings you joy and make you drool, you can find out some more on my pages tagged “bash”.
Once this function definition is added to your file .bash_aliases or
.bashrc, you only need to run the following command to connect to the host
scruffy.example.org :
As for the client command scp, the following function, although perfectible,
should work in most cases (be aware though that is replaces in all its
arguments the character “:” by “.exemple.org”, which could cause a
problem):
It works just like the scp command, but will suffix the name just before the
“:” by “.exemple.org”. Here are some usage examples: