Reverse SSH tunnel with libssh2
Recently, I had to fix a problem in a mobile library that uses libssh2 to open a reverse tunnel with a remote server. The implementation was based in the tcpip-forward.c example provided in the libssh2 code package. The problem was that in this example the SSH tunnel is closed at the end of each client connection. This article will show how to keep the tunnel permanently opened.
1. The libssh2 library
libssh2 is a lean client-only C library that implements the SSH2 protocol. Given its small size, stability, and good performance, it’s well suitable for embedded systems and is also used in many open source projects like curl.
Unfortunately, there is not much documentation about how to use the library. Despite the API docs and some example files on the project’s website, I didn’t find any tutorial or a good blog post about how to use it. So I decided to write this one to share what I have learned about libssh2 :-)
Also, note that there is another open source library for SSH called libssh which, despite the similar name, has nothing to do with libssh2. This ends up causing a lot of confusion when searching for information in the web.
2. Reverse tunnels
SSH is an encrypted network protocol widely used for remote command execution and secure data communication. In a SSH tunnel, a unencrypted traffic is wrapped around the SSH protocol and sent over an encrypted and secure connection.
A reverse tunnel is a technique used to access a server in an internal network which is not accessible from the outside world. For example, a server behind a firewall that denies incoming connections.
To set up a reverse tunnel, you need to have another server which is publicly accessible and have SSH access to it. Then, from your server, you open a SSH connection to the publicly accessible server and tell it to forward all data and connection requests from a specific port to a local port on your server.
This way you can, for example, forward all HTTP requests to the port 4000 on the accessible server to your web application running on port 8080 on your server.
3. Permanent reverse tunnel with libssh2
Following, I will show you step-by-step how to create a permanent reverse tunnel with libssh2. As already mentioned, this code was base on the tcpip-forward.c example which comes in the libssh2 package.
3.1 Initialization
First of all, you should call libssh2_init
to initialize the libssh2 functions.
The 0
passed to libssh2_init
will ask it to also initialize the crypto library. By default, libssh2 will attempt to locate the crypto libraries automatically. The supported crypto libraries are OpenSSL, Libgcrypt, WinCNG, and mbedTLS.
3.2 Connecting to the remote SSH server
Next, you should open a socket connection to the remote SSH server.
3.3 Creating a SSH session
The next step is to create and start a SSH session under the socket connection.
3.4 Authenticating the connection
Once connected, you should authenticate yourself to receive access to several resources such as port forwarding, shell, sftp, and so on.
For simplicity, the code above shows only how to authenticate with password, but libssh2 also supports key-based authentication.
3.5 Listening for inbound TCP/IP connections
Now, you can start to listen for inbound TCP/IP connections on a specific port of the remote server.
Besides the session, the libssh2_channel_forward_listen_ex
method receives:
- the
remote_listenhost
specifying the address to bind to on the remote host. In this case, the remote host address itself (localhost). - the
remote_wantport
which specifies the port that we want to listen on the remote server. When0
is passed, the remote host will select the first available dynamic port. - the
remote_listenport
which will be populated with the actual port bound on the remote host. In this case, the value will be the same passed onremote_wantport
. - and a number indicating the maximum number of pending connections to queue before rejecting further attempts.
New connections will be queued by the library until accepted by libssh2_channel_forward_accept
as shown in the next step.
3.6 Creating a SSH Channel
Whenever the remote server receives a TCP/IP connection request, a new SSH channel is created using the libssh2_channel_forward_accept
, and the forward_tunnel
function is called to forward the request to the local service.
libssh2_channel_forward_accept
is a blocking function. This means that a channel instance will be created (and the next instruction processed) only when the listener enqueues/receives a connection.
3.7 Forwarding connection to local port
The forward_tunnel
function is responsible for reading the content received in the channel and forward it to the local port. It also forwards all local service’s responses to the channel.
First, it opens a socket connection to the local port to be able to send and receive data from it:
Next, it starts a loop where it first reads (recv
) from the forwardsock
, saves the content in a buffer, and then writes all buffer’s content in the channel (libssh2_channel_write
). Another while loop inside the first one does the opposite. It reads from the channel (libssh2_channel_read
) and writes (send
) the content to the local socket.
One important point to be noticed is the calls to libssh2_session_set_blocking
. According to the libssh2 API doc:
This function is responsible for setting the session’s block mode. If a read is performed on a session with no data currently available, a blocking session will wait for data to arrive and return what it receives. A non-blocking session will return immediately with an empty buffer. If a write is performed on a session with no room for more data, a blocking session will wait for room. A non-blocking session will return immediately without writing anything.
So in this case, I’m setting the session to non-blocking before the while loop because I don’t want it to be blocked on reading or writing operations.
When the client disconnects (libssh2_channel_eof
), the shutdown
instructions will be called. The forwardsock
is then closed and the session is set back to the blocking mode. This way the libssh2_channel_forward_accept
will be blocked on waiting for a new connection.
4. Source code
You can found the complete code for the reverse tunnel in the libssh2-tunnel-example repository. Enjoy! :D