---
title: Open a tunnel
layout: page
parent: Tutorial
nav_order: 6
---
# {{ page.title }}
SSH is most commonly used to execute commands on a remote server, but another important use of the protocol is for tunnelling TCP/IP connections. There are two ways to open a tunnel:
- _Local forwarding_ (aka [`ssh -L`][ssh-l]): the client asks the server to open a TCP connection to another host. In Makiko, this is implemented by [`Client::connect_tunnel()`][client-connect-tunnel]. In this chapter, we will learn how to use this method.
- _Remote forwarding_ (aka [`ssh -R`][ssh-r]): the client asks the server to listen on a port, and the server will open a tunnel for every TCP connection on this port. In Makiko, this is implemented by [`Client::bind_tunnel()`][client-bind-tunnel], [`Client::unbind_tunnel()`][client-unbind-tunnel] and the [`ClientEvent::Tunnel`][client-event-tunnel] variant of `ClientEvent`. We won't cover remote forwarding in this tutorial, please refer to the API documentation for details.
[ssh-l]: https://man.openbsd.org/ssh#L
[client-connect-tunnel]: https://docs.rs/makiko/latest/makiko/struct.Client.html#method.connect_tunnel
[ssh-r]: https://man.openbsd.org/ssh#R
[client-bind-tunnel]: https://docs.rs/makiko/latest/makiko/struct.Client.html#method.bind_tunnel
[client-unbind-tunnel]: https://docs.rs/makiko/latest/makiko/struct.Client.html#method.unbind_tunnel
[client-event-tunnel]: https://docs.rs/makiko/latest/makiko/enum.ClientEvent.html#variant.Tunnel
## Open a tunnel
To demonstrate the use of tunnels, we will open a TCP/IP connection from the server to [httpbin.org][httpbin] and we will manually send a simple HTTP request over this connection. To open the tunnel, we use the method [`Client::connect_tunnel()`][client-connect-tunnel], which needs:
- A [`ChannelConfig`][channel-config], which configures the underlying SSH channel. Similar to the previous chapter, you can change the configuration to tune performance, but the default configuration should be sufficient for now.
- The address that the server will connect to, given as a pair of host and port. The host can be specified as an IP address or as a domain name. We will connect to `"httpbin.org"` on port `80`.
- The address of the "originator" of the connection. This is also specified as a pair of host and port, but the host should be an IP address. For example, [`ssh -L`][ssh-l] will set this to the remote address of the local connection that is forwarded to the server, but we will use the null IP address and port in this tutorial.
[httpbin]: https://httpbin.org/
[channel-config]: https://docs.rs/makiko/latest/makiko/struct.ChannelConfig.html
```rust
// Open a tunnel from the server.
let channel_config = makiko::ChannelConfig::default();
let connect_addr = ("httpbin.org".into(), 80);
let origin_addr = ("0.0.0.0".into(), 0);
let (tunnel, mut tunnel_rx) = client.connect_tunnel(channel_config, connect_addr, origin_addr).await
.expect("Could not open a tunnel");
```
In a direct analogy to [`Client::open_session()`][client-open-session], the `Client::connect_tunnel()` method returns a pair of objects: a [`Tunnel`][tunnel] object to send requests to the tunnel, and a [`TunnelReceiver`][tunnel-rx] to receive events from the tunnel.
[client-open-session]: https://docs.rs/makiko/latest/makiko/struct.Client.html#method.open_session
[tunnel]: https://docs.rs/makiko/latest/makiko/struct.Tunnel.html
[tunnel-rx]: https://docs.rs/makiko/latest/makiko/struct.TunnelReceiver.html
## Handle tunnel events
We will use the same pattern as before to handle events from the tunnel: we spawn a task and receive the events, represented as enum [`TunnelEvent`][tunnel-event], using [`TunnelReceiver::recv()`][tunnel-rx-recv]. This method returns `None` when the tunnel closes:
[tunnel-event]: https://docs.rs/makiko/latest/makiko/enum.TunnelEvent.html
[tunnel-rx-recv]: https://docs.rs/makiko/latest/makiko/struct.TunnelReceiver.html#method.recv
```rust
let tunnel_event_task = tokio::task::spawn(async move {
loop {
// Wait for the next event.
let event = tunnel_rx.recv().await
.expect("Error while receiving tunnel event");
// Exit the loop when the tunnel has closed.
let Some(event) = event else {
break
};
match event {
... // Handle the event
}
}
});
```
{: .warning }
As with all `Receiver` objects in Makiko, you must receive the events from the `TunnelReceiver` in a timely manner. Makiko uses a bounded buffer of events, which will become full if you don't receive the event, causing the client to block.
### Data received from the channel
Events on a tunnel are quite simple, you can either get a chunk of data with the `Data` variant, or an end-of-file event with the `Eof` variant:
```rust
match event {
// Handle data received from the tunnel.
makiko::TunnelEvent::Data(data) => {
println!("Received: {:?}", data);
},
// Handle EOF from the tunnel.
makiko::TunnelEvent::Eof => {
println!("Received eof");
break
},
_ => {},
}
```
## Send data to the channel
Back on the main task, we can use the [`Tunnel::send_data()`][tunnel-send-data] method to send bytes over the tunnel. In our case, we send a very simple HTTP request to [`httpbin.org/get`][httpbin-get]:
[tunnel-send-data]: https://docs.rs/makiko/latest/makiko/struct.Tunnel.html#method.send_data
[httpbin-get]: https://httpbin.org/#/HTTP_Methods/get_get
```rust
// Send data to the tunnel
tunnel.send_data("GET /get HTTP/1.0\r\nhost: httpbin.org\r\n\r\n".into()).await
.expect("Could not send data to the tunnel");
```
We can also close the tunnel for sending by calling [`Tunnel::send_eof()`][tunnel-send-eof]. However, the OpenSSH server will close the tunnel prematurely if we do so, so we comment out this call:
[tunnel-send-eof]: https://docs.rs/makiko/latest/makiko/struct.Tunnel.html#method.send_eof
```rust
// Do not close the outbound side of the tunnel, because this causes OpenSSH to prematurely
// close the tunnel.
/*
tunnel.send_eof().await
.expect("Could not send EOF to the tunnel");
*/
```
Finally, we wait until the tunnel is closed and the event handling task terminates:
```rust
// Wait for the task that handles tunnel events
tunnel_event_task.await.unwrap();
```
---
Full code for this tutorial can be found in [`examples/tutorial_6.rs`][tutorial-6]. If you don't use the [example server for this tutorial][example-server], you may need to change the code to use a different username and password.
[tutorial-6]: https://github.com/honzasp/makiko/blob/master/examples/tutorial_6.rs
[example-server]: {% link tutorial/1-connect.md %}#example-server
{% include tutorial_next.html link="tutorial/7-verify-pubkey.md" title="Verify the server key" %}