❄️ Latest: Snowflake customers — stream your data to Kafka! Learn more! ❄️

follow or visit us on
Learning

Ockam Routing

Nazmul Idris
Nazmul Idris
Published 2023-07-31
LearningOckam Routing

Ockam is a suite of programming libraries, command line tools, and managed cloud services to orchestrate end-to-end encryption, mutual authentication, key management, credential management, and authorization policy enforcement — all at massive scale. Ockam's end-to-end secure channels guarantee authenticity, integrity, and confidentiality of all data-in-motion at the application layer.

One of the key features that makes this possible is Ockam Routing. Routing allows us to create secure channels over multi-hop, multi-protocol routes which can span various network topologies (servers behind NAT firewalls with no external ports open, etc) and transport protocols (TCP, UDP, WebSockets, BLE, etc).

In this blog post we will explore the Ockam Rust Library and see how routing works in Ockam. We will work with Rust code and look at some code examples that demonstrate the simple case, and more advanced use cases.

Mitigating risk

Before we get started, let's quickly discuss the pitfalls of using existing approaches to securing communications within applications. Security is not something that most of us think about when we are building systems and are focused on getting things working and shipping.

Traditional secure communication implementations are typically tightly coupled with transport protocols in a way that all their security is limited to the length and duration of one underlying transport connection.

  1. For example, most TLS implementations are tightly coupled with the underlying TCP connection. If your application's data and requests travel over two TCP connection hops (TCP → TCP), then all TLS guarantees break at the bridge between the two networks. This bridge, gateway, or load balancer then becomes a point of weakness for application data.
  2. Traditional secure communication protocols are also unable to protect your application's data if it travels over multiple different transport protocols. They can't guarantee data authenticity or data integrity if your application's communication path is UDP → TCP or BLE → TCP.

In other words using traditional secure communication implementations you may be opening the doors to losing trust in the data that your apps are working on. Here are some aspects of your apps that may be at risk:

  1. Lack of trust in the data your app receives.
    • Who sent it to my app?
    • Is it actually the data they sent my app?
    • Missing authentication, data integrity.
  2. Lack of trust in the data your app sends.
    • Who am I sending the data to?
    • Would someone else, other than them, be able to see it?

Our journey

In this blog post we will create two examples of Ockam nodes communicating with each other using Ockam Routing and Ockam Transports. We will use the Rust library to create these Ockam nodes and setup routing. Ockam Routing and transports enable other Ockam protocols to provide end-to-end guarantees like trust, security, privacy, reliable delivery, and ordering at the application layer.

  • Ockam Routing: is a simple and lightweight message-based protocol that makes it possible to bidirectionally exchange messages over a large variety of communication topologies: TCP -> TCP or TCP -> TCP -> TCP or BLE -> UDP -> TCP or BLE -> TCP -> TCP or TCP -> Kafka -> TCP or any other topology you can imagine.
  • Ockam Transports: adapt Ockam Routing to various transport protocols.

An Ockam node is any running application that can communicate with other applications using various Ockam protocols like Routing, Relays, and Portals, Secure Channels, etc.

An Ockam node can be defined as any independent process which provides an API supporting the Ockam Routing protocol. We can create Ockam nodes using the Ockam command line interface (CLI) (ockam command) or using various Ockam programming libraries like our Rust and Elixir libraries. We will be using the Rust library in this blog post.

Let's dive in

Simple example

For our first example, we will create a simple Ockam node that will send a message over some hops (in the same node) to a worker (in the same node) that just echoes the message back. There are no TCP transports involved and all the messages are being passed back and forth inside the same node. This will give us a feel for building workers and routing at a basic level.

When a worker is started on a node, it is given one or more addresses. The node maintains a mailbox for each address and whenever a message arrives for a specific address it delivers that message to the corresponding registered worker.

We will need to create a Rust source file with a main() program, and two other Rust source files with two workers: Hopper and Echoer. We can then send a string message and see if we can get it echoed back.

Before we begin let's consider routing. When we send a message inside of a node it carries with it 2 metadata fields, onward_route and return_route, where a route is simply a list of addresses. Each worker gets an address in a node.

So, if we wanted to send a message from the app address to the echoer address, with 3 hops in the middle, we can build a route like the following.


_16
┌───────────────────────┐
_16
│ Node 1 │
_16
├───────────────────────┤
_16
│ ┌────────────────┐ │
_16
│ │ Address: │ │
_16
│ │ 'app' │ │
_16
│ └─┬────────────▲─┘ │
_16
│ ┌─▼────────────┴─┐ │
_16
│ │ Address: │ │
_16
│ │ 'hopper1..3' │x3 │
_16
│ └─┬────────────▲─┘ │
_16
│ ┌─▼────────────┴─┐ │
_16
│ │ Address: │ │
_16
│ │ 'echoer' │ │
_16
│ └────────────────┘ │
_16
└───────────────────────┘

Here's the Rust code to build this route.


_10
/// Send a message to the echoer worker via the "hopper1", "hopper2", and "hopper3" workers.
_10
let route = route!["hopper1", "hopper2", "hopper3", "echoer"];

Let's add some source code to make this happen next. The first thing we will do is add one more dependency to this empty hello_ockam project. The colored crate will give us colorized console output which will make the output from our examples so much easier to read and understand.


_10
cargo add colored

Then we add the echoer worker (in our hello_ockam project) by creating a new /src/echoer.rs file and copy / pasting the following code in it.


_37
use colored::Colorize;
_37
use ockam::{Context, Result, Routed, Worker};
_37
_37
pub struct Echoer;
_37
_37
/// When a worker is started on a node, it is given one or more addresses. The node
_37
/// maintains a mailbox for each address and whenever a message arrives for a specific
_37
/// address it delivers that message to the corresponding registered worker.
_37
///
_37
/// Workers can handle messages from other workers running on the same or a different
_37
/// node. In response to a message, an worker can: make local decisions, change its
_37
/// internal state, create more workers, or send more messages to other workers running on
_37
/// the same or a different node.
_37
#[ockam::worker]
_37
impl Worker for Echoer {
_37
type Context = Context;
_37
type Message = String;
_37
_37
async fn handle_message(&mut self, ctx: &mut Context, msg: Routed<String>) -> Result<()> {
_37
// Echo the message body back on its return_route.
_37
let addr_str = ctx.address().to_string();
_37
let msg_str = msg.as_body().to_string();
_37
let new_msg_str = format!("👈 echo back: {}", msg);
_37
_37
// Formatting stdout output.
_37
let lines = [
_37
format!("📣 'echoer' worker → Address: {}", addr_str.bright_yellow()),
_37
format!(" Received: '{}'", msg_str.green()),
_37
format!(" Sent: '{}'", new_msg_str.cyan()),
_37
];
_37
lines
_37
.iter()
_37
.for_each(|line| println!("{}", line.white().on_black()));
_37
_37
ctx.send(msg.return_route(), new_msg_str).await
_37
}
_37
}

Next we add the hopper worker (in our hello_ockam project) by creating a new /src/hopper.rs file and copy / pasting the following code in it.

Note how this worker manipulates the onward_route & return_route fields of the message to send it to the next hop. We will actually see this in the console output when we run this code soon.


_52
use colored::Colorize;
_52
use ockam::{Any, Context, Result, Routed, Worker};
_52
_52
pub struct Hopper;
_52
_52
#[ockam::worker]
_52
impl Worker for Hopper {
_52
type Context = Context;
_52
type Message = Any;
_52
_52
/// This handle function takes any incoming message and forwards. it to the next hop
_52
/// in it's onward route.
_52
async fn handle_message(&mut self, ctx: &mut Context, msg: Routed<Any>) -> Result<()> {
_52
// Cast the msg to a Routed<String>
_52
let msg: Routed<String> = msg.cast()?;
_52
_52
let msg_str = msg.to_string().white().on_bright_black();
_52
let addr_str = ctx.address().to_string().white().on_bright_black();
_52
_52
// Some type conversion.
_52
let mut message = msg.into_local_message();
_52
let transport_message = message.transport_mut();
_52
_52
// Remove my address from the onward_route.
_52
let removed_address = transport_message.onward_route.step()?;
_52
let removed_addr_str = removed_address
_52
.to_string()
_52
.white()
_52
.on_bright_black()
_52
.strikethrough();
_52
_52
// Formatting stdout output.
_52
let lines = [
_52
format!("🐇 'hopper' worker → Addr: '{}'", addr_str),
_52
format!(" Received: '{}'", msg_str),
_52
format!(" onward_route -> remove: '{}'", removed_addr_str),
_52
format!(" return_route -> prepend: '{}'", addr_str),
_52
];
_52
lines
_52
.iter()
_52
.for_each(|line| println!("{}", line.black().on_yellow()));
_52
_52
// Insert my address at the beginning return_route.
_52
transport_message
_52
.return_route
_52
.modify()
_52
.prepend(ctx.address());
_52
_52
// Send the message on its onward_route.
_52
ctx.forward(message).await
_52
}
_52
}

And finally let's add a main() to our hello_ockam project. This will be the entry point for our example.

Create an empty file /examples/03-routing-many.hops.rs (note this is in the examples/ folder and not src/ folder like the workers above).


_75
use colored::Colorize;
_75
use hello_ockam::{Echoer, Hopper};
_75
use ockam::{node, route, Context, Result};
_75
_75
#[rustfmt::skip]
_75
const HELP_TEXT: &str =r#"
_75
┌───────────────────────┐
_75
│ Node 1 │
_75
├───────────────────────┤
_75
│ ┌────────────────┐ │
_75
│ │ Address: │ │
_75
│ │ 'app' │ │
_75
│ └─┬────────────▲─┘ │
_75
│ ┌─▼────────────┴─┐ │
_75
│ │ Address: │ │
_75
│ │ 'hopper1..3' │x3 │
_75
│ └─┬────────────▲─┘ │
_75
│ ┌─▼────────────┴─┐ │
_75
│ │ Address: │ │
_75
│ │ 'echoer' │ │
_75
│ └────────────────┘ │
_75
└───────────────────────┘
_75
"#;
_75
_75
/// This node routes a message through many hops.
_75
#[ockam::node]
_75
async fn main(ctx: Context) -> Result<()> {
_75
println!("{}", HELP_TEXT.green());
_75
_75
print_title(vec![
_75
"Run a node w/ 'app', 'echoer' and 'hopper1', 'hopper2', 'hopper3' workers",
_75
"then send a message over 3 hops",
_75
"finally stop the node",
_75
]);
_75
_75
// Create a node with default implementations.
_75
let mut node = node(ctx);
_75
_75
// Start an Echoer worker at address "echoer".
_75
node.start_worker("echoer", Echoer).await?;
_75
_75
// Start 3 hop workers at addresses "hopper1", "hopper2" and "hopper3".
_75
node.start_worker("hopper1", Hopper).await?;
_75
node.start_worker("hopper2", Hopper).await?;
_75
node.start_worker("hopper3", Hopper).await?;
_75
_75
// Send a message to the echoer worker via the "hopper1", "hopper2", and "hopper3" workers.
_75
let route = route!["hopper1", "hopper2", "hopper3", "echoer"];
_75
let route_msg = format!("{:?}", route);
_75
let msg = "Hello Ockam!";
_75
node.send(route, msg.to_string()).await?;
_75
_75
// Wait to receive a reply and print it.
_75
let reply = node.receive::<String>().await?;
_75
_75
// Formatting stdout output.
_75
let lines = [
_75
"🏃 Node 1 →".to_string(),
_75
format!(" sending: {}", msg.green()),
_75
format!(" over route: {}", route_msg.blue()),
_75
format!(" and receiving: '{}'", reply.purple()), // Should print "👈 echo back: Hello Ockam!"
_75
format!(" then {}", "stopping".bold().red()),
_75
];
_75
lines
_75
.iter()
_75
.for_each(|line| println!("{}", line.black().on_white()));
_75
_75
// Stop all workers, stop the node, cleanup and return.
_75
node.stop().await
_75
}
_75
_75
fn print_title(title: Vec<&str>) {
_75
let line = format!("🚀 {}", title.join("\n → ").white());
_75
println!("{}", line.black().on_bright_black())
_75
}

Now it is time to run our program to see what it does! 🎉

In your terminal app, run the following command. Note that OCKAM_LOG=none is used to disable logging output from the Ockam library. This is done to make the output of the example easier to read.


_10
OCKAM_LOG=none cargo run --example 03-routing-many-hops

And you should see something like the following. Our example program creates multiple hop workers (three hopper workers) between the app and the echoer and route our message through them 🚀.

Output from running 03-routing-many-hops

Complex example

In this example, we will introduce TCP transports in between the hops. Instead of passing messages around between workers in the same node, we will spawn multiple nodes. Then we will have a few TCP transports (TCP socket client and listener combos) that will connect the nodes.

An Ockam transport is a plugin for Ockam Routing. It moves Ockam Routing messages using a specific transport protocol like TCP, UDP, WebSockets, Bluetooth, etc.

We will have three nodes:

  1. node_initiator: The first node initiates sending the message over TCP to the middle node (port 3000).
  2. node_middle: Then middle node simply forwards this message on to the last node over TCP again (port 4000 this time).
  3. node_responder: And finally the responder node receives the message and sends a reply back to the initiator node.

The following diagram depicts what we will build next. In this example all these nodes are on the same machine, but they can easy just be nodes on different machines.


_24
┌──────────────────────┐
_24
│node_initiator │
_24
├──────────────────────┤
_24
│ ┌──────────────────┐ │
_24
│ │Address: │ │ ┌───────────────────────────┐
_24
│ │'app' │ │ │node_middle │
_24
│ └──┬────────────▲──┘ │ ├───────────────────────────┤
_24
│ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │
_24
│ │TCP transport └─┼─────┼─►TCP transport │ │
_24
│ │connect to 3000 ◄─┼─────┼─┐listening on 3000 │ │
_24
│ └──────────────────┘ │ │ └──┬────────────▲──┘ │
_24
└──────────────────────┘ │ ┌──▼────────────┴───────┐ │
_24
│ │Address: │ │ ┌──────────────────────┐
_24
│ │'forward_to_responder' │ │ │node_responder │
_24
│ └──┬────────────▲───────┘ │ ├──────────────────────┤
_24
│ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │
_24
│ │TCP transport └──────┼───┼─►TCP transport │ │
_24
│ │connect to 4000 ◄──────┼───┼─┐listening on 4000 │ │
_24
│ └──────────────────┘ │ │ └──┬────────────▲──┘ │
_24
└───────────────────────────┘ │ ┌──▼────────────┴──┐ │
_24
│ │Address: │ │
_24
│ │'echoer' │ │
_24
│ └──────────────────┘ │
_24
└──────────────────────┘

Let's start by creating a new file /examples/04-routing-over-two-transport-hops.rs (in the /examples/ folder and not /src/ folder). Then copy / paste the following code in that file.


_63
use colored::Colorize;
_63
use hello_ockam::{Echoer, Forwarder};
_63
use ockam::{
_63
node, route, AsyncTryClone, Context, Result, TcpConnectionOptions, TcpListenerOptions,
_63
TcpTransportExtension,
_63
};
_63
_63
#[rustfmt::skip]
_63
const HELP_TEXT: &str =r#"
_63
┌──────────────────────┐
_63
│node_initiator │
_63
├──────────────────────┤
_63
│ ┌──────────────────┐ │
_63
│ │Address: │ │ ┌───────────────────────────┐
_63
│ │'app' │ │ │node_middle │
_63
│ └──┬────────────▲──┘ │ ├───────────────────────────┤
_63
│ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │
_63
│ │TCP transport └─┼─────┼─►TCP transport │ │
_63
│ │connect to 3000 ◄─┼─────┼─┐listening on 3000 │ │
_63
│ └──────────────────┘ │ │ └──┬────────────▲──┘ │
_63
└──────────────────────┘ │ ┌──▼────────────┴───────┐ │
_63
│ │Address: │ │ ┌──────────────────────┐
_63
│ │'forward_to_responder' │ │ │node_responder │
_63
│ └──┬────────────▲───────┘ │ ├──────────────────────┤
_63
│ ┌──▼────────────┴──┐ │ │ ┌──────────────────┐ │
_63
│ │TCP transport └──────┼───┼─►TCP transport │ │
_63
│ │connect to 4000 ◄──────┼───┼─┐listening on 4000 │ │
_63
│ └──────────────────┘ │ │ └──┬────────────▲──┘ │
_63
└───────────────────────────┘ │ ┌──▼────────────┴──┐ │
_63
│ │Address: │ │
_63
│ │'echoer' │ │
_63
│ └──────────────────┘ │
_63
└──────────────────────┘
_63
"#;
_63
_63
#[ockam::node]
_63
async fn main(ctx: Context) -> Result<()> {
_63
println!("{}", HELP_TEXT.green());
_63
_63
let ctx_clone = ctx.async_try_clone().await?;
_63
let ctx_clone_2 = ctx.async_try_clone().await?;
_63
_63
let mut node_responder = create_responder_node(ctx).await.unwrap();
_63
_63
let mut node_middle = create_middle_node(ctx_clone).await.unwrap();
_63
_63
create_initiator_node(ctx_clone_2).await.unwrap();
_63
_63
node_responder.stop().await.ok();
_63
node_middle.stop().await.ok();
_63
_63
println!(
_63
"{}",
_63
"App finished, stopping node_responder & node_middle".red()
_63
);
_63
_63
Ok(())
_63
}
_63
_63
fn print_title(title: Vec<&str>) {
_63
let line = format!("🚀 {}", title.join("\n → ").white());
_63
println!("{}", line.black().on_bright_black())
_63
}

This code won't actually compile, since there are 3 functions missing from this source file. We are just adding this file first in order to stage the rest of the code we will write next.

This main() function creates the three nodes like we see in the diagram above, and it also stops them after the example is done running.

Initiator node

So let's write the function that creates the initiator node first. Copy the following into the source file we created earlier (/examples/04-routing-over-two-transport-hops.rs), and paste it below the existing code there:


_43
/// This node routes a message, to a worker on a different node, over two TCP transport
_43
/// hops.
_43
async fn create_initiator_node(ctx: Context) -> Result<()> {
_43
print_title(vec![
_43
"Create node_initiator that routes a message, over 2 TCP transport hops, to 'echoer' worker on node_responder",
_43
"stop",
_43
]);
_43
_43
// Create a node with default implementations.
_43
let mut node = node(ctx);
_43
_43
// Initialize the TCP transport.
_43
let tcp_transport = node.create_tcp_transport().await?;
_43
_43
// Create a TCP connection to the middle node.
_43
let connection_to_middle_node = tcp_transport
_43
.connect("localhost:3000", TcpConnectionOptions::new())
_43
.await?;
_43
_43
// Send a message to the "echoer" worker, on a different node, over two TCP hops. Wait
_43
// to receive a reply and print it.
_43
let route = route![connection_to_middle_node, "forward_to_responder", "echoer"];
_43
let route_str = format!("{:?}", route);
_43
let msg = "Hello Ockam!";
_43
let reply = node
_43
.send_and_receive::<String>(route, msg.to_string())
_43
.await?;
_43
_43
// Formatting stdout output.
_43
let lines = [
_43
"🏃 node_initiator →".to_string(),
_43
format!(" sending: {}", msg.green()),
_43
format!(" over route: '{}'", route_str.blue()),
_43
format!(" and received: '{}'", reply.purple()), // Should print "👈 echo back: Hello Ockam!"
_43
format!(" then {}", "stopping".bold().red()),
_43
];
_43
lines
_43
.iter()
_43
.for_each(|line| println!("{}", line.black().on_white()));
_43
_43
// Stop all workers, stop the node, cleanup and return.
_43
node.stop().await
_43
}

This (initiator) node will send a message to the responder using the following route.


_10
let route = route![connection_to_middle_node, "forward_to_responder", "echoer"];

Middle node

Let's create the middle node next, which will run the worker Forwarder on this address: forward_to_responder.

Copy and paste the following into the source file we created above (/examples/04-routing-over-two-transport-hops.rs).

  • This middle node simply forwards whatever comes into its TCP listener (on 3000) to port 4000.
  • This node has a Forwarder worker on address forward_to_responder, so that's how the initiator can reach this address specified in its route at the start of this example.

_42
/// - Starts a TCP listener at 127.0.0.1:3000.
_42
/// - This node creates a TCP connection to a node at 127.0.0.1:4000.
_42
/// - Starts a forwarder worker to forward messages to 127.0.0.1:4000.
_42
/// - Then runs forever waiting to route messages.
_42
async fn create_middle_node(ctx: Context) -> Result<ockam::Node> {
_42
print_title(vec![
_42
"Create node_middle that listens on 3000 and forwards to 4000",
_42
"wait for messages until stopped",
_42
]);
_42
_42
// Create a node with default implementations.
_42
let node = node(ctx);
_42
_42
// Initialize the TCP transport.
_42
let tcp_transport = node.create_tcp_transport().await?;
_42
_42
// Create a TCP connection to the responder node.
_42
let connection_to_responder = tcp_transport
_42
.connect("127.0.0.1:4000", TcpConnectionOptions::new())
_42
.await?;
_42
_42
// Create a Forwarder worker.
_42
node.start_worker(
_42
"forward_to_responder",
_42
Forwarder {
_42
address: connection_to_responder.into(),
_42
},
_42
)
_42
.await?;
_42
_42
// Create a TCP listener and wait for incoming connections.
_42
let listener = tcp_transport
_42
.listen("127.0.0.1:3000", TcpListenerOptions::new())
_42
.await?;
_42
_42
// Allow access to the Forwarder via TCP connections from the TCP listener.
_42
node.flow_controls()
_42
.add_consumer("forward_to_responder", listener.flow_control_id());
_42
_42
// Don't call node.stop() here so this node runs forever.
_42
Ok(node)
_42
}

We will also need the Forwarder worker that we use in the middle node. Copy and paste the following into a new source file /src/forwarder.rs. This file goes in the src folder and not the examples folder.


_61
use colored::Colorize;
_61
use ockam::{Address, Any, Context, LocalMessage, Result, Routed, Worker};
_61
_61
pub struct Forwarder {
_61
pub address: Address,
_61
}
_61
_61
#[ockam::worker]
_61
impl Worker for Forwarder {
_61
type Context = Context;
_61
type Message = Any;
_61
_61
/// This handle function takes any incoming message and forwards it to the next hop in
_61
/// it's onward route.
_61
async fn handle_message(&mut self, ctx: &mut Context, msg: Routed<Any>) -> Result<()> {
_61
let address_str = ctx.address().to_string();
_61
let msg_str = msg.to_string();
_61
_61
// Formatting stdout output.
_61
let lines = [
_61
format!("👉 'forwarder' worker → Address: {}", address_str.blue()),
_61
format!(" message: {}", msg_str.purple()),
_61
];
_61
lines
_61
.iter()
_61
.for_each(|line| println!("{}", line.black().on_cyan()));
_61
_61
// Do some type conversion.
_61
let mut transport_message = msg.into_local_message().into_transport_message();
_61
_61
transport_message
_61
.onward_route
_61
.modify()
_61
.pop_front() // Remove my address from the onward_route.
_61
.prepend(self.address.clone()); // Prepend predefined address to the onward_route.
_61
_61
let prev_hop = transport_message.return_route.next()?.clone();
_61
_61
// Wipe all local info (e.g. transport types).
_61
let message = LocalMessage::new(transport_message, vec![]);
_61
_61
if let Some(info) = ctx
_61
.flow_controls()
_61
.find_flow_control_with_producer_address(&self.address)
_61
{
_61
ctx.flow_controls()
_61
.add_consumer(prev_hop.clone(), info.flow_control_id());
_61
}
_61
_61
if let Some(info) = ctx
_61
.flow_controls()
_61
.find_flow_control_with_producer_address(&prev_hop)
_61
{
_61
ctx.flow_controls()
_61
.add_consumer(self.address.clone(), info.flow_control_id());
_61
}
_61
_61
// Send the message on its onward_route.
_61
ctx.forward(message).await
_61
}
_61
}

Responder node

Finally, we will create the responder node. This node will run the worker echoer which actually echoes the message back to the initiator. Copy and paste the following into the source file above (/examples/04-routing-over-two-transport-hops.rs).

  • This node has an Echoer worker on address echoer, so that's how the initiator can reach this address specified in its route at the start of this example.

_28
/// This node starts a TCP listener and an echoer worker. It then runs forever waiting for
_28
/// messages.
_28
async fn create_responder_node(ctx: Context) -> Result<ockam::Node> {
_28
print_title(vec![
_28
"Create node_responder that runs tcp listener on 4000 and 'echoer' worker",
_28
"wait for messages until stopped",
_28
]);
_28
_28
// Create a node with default implementations.
_28
let node = node(ctx);
_28
_28
// Initialize the TCP transport.
_28
let tcp_transport = node.create_tcp_transport().await?;
_28
_28
// Create an echoer worker.
_28
node.start_worker("echoer", Echoer).await?;
_28
_28
// Create a TCP listener and wait for incoming connections.
_28
let listener = tcp_transport
_28
.listen("127.0.0.1:4000", TcpListenerOptions::new())
_28
.await?;
_28
_28
// Allow access to the Echoer via TCP connections from the TCP listener.
_28
node.flow_controls()
_28
.add_consumer("echoer", listener.flow_control_id());
_28
_28
Ok(node)
_28
}

Let's run this example to see what it does 🎉.

In your terminal app, run the following command. Note that OCKAM_LOG=none is used to disable logging output from the Ockam library. This is done to make the output of the example easier to read.


_10
cargo run --example 04-routing-over-two-transport-hops

This should produce output similar to the following. Our example program creates a route that traverses multiple nodes and TCP transports from the app to the echoer and routes our message through them 🚀.

Output from running 04-routing-over-two-transport-hops

Next steps

Ockam Routing and transports are extremely powerful and flexible. They are one of the key features that enables Ockam Secure Channels to be implemented. By layering Ockam Secure Channels and other protocols over Ockam Routing, we can provide end-to-end guarantees over arbitrary transport topologies that span many networks and clouds.

In a future blog post we will be covering Ockam Secure Channels and how they can be used to provide end-to-end guarantees over arbitrary transport topologies. So stay tuned!

In the meantime here are some good jumping off points to learn more about Ockam:

Previous Article

Authenticate & authorize every access decision

Next Article

Rewriting Ockam in Rust

Edit on Github

Build Trust

Learn

Get Started

Ockam Command

Programming Libraries

Cryptographic & Messaging Protocols

Documentation

Blog

© 2024 Ockam.io All Rights Reserved