An open API service indexing awesome lists of open source software.

https://github.com/encodeous/root

An abstract, I/O-free routing framework inspired by RFC 8966
https://github.com/encodeous/root

Last synced: 6 months ago
JSON representation

An abstract, I/O-free routing framework inspired by RFC 8966

Awesome Lists containing this project

README

          

# root

[root](https://github.com/encodeous/root) is an abstract I/O-free routing framework inspired by the [Babel Routing Protocol](https://datatracker.ietf.org/doc/html/rfc8966). It provides a high-level framework to design dynamic, fault-tolerant networks.

The application is solely responsible for providing I/O and scheduling events, meaning that users of root are free to use any platform, framework, or architecture they desire.

For a complete example routing application that uses TCP as transport, see `/examples/simple-mesh`.

To get started with root, run:
`cargo add root`, use the `serde` feature for serialization.

# Why I/O-free?

root is designed from the ground up to offer a platform, network, and protocol agnostic way to do routing.
- Compared to traditional implementations that rely on a **specific network stack**, root is able to work for **any situation** where a graph of nodes are joined together with bidirectional links.
- Decisions about how to physically forward packets are left to the **application**, allowing hybrid routing over **multiple protocols** (i.e routing over IPv4, IPv6, Serial & Bluetooth all at once)
- An I/O-free implementation allows root to be thoroughly tested, and operate with deterministic state at all times.

For more motivations, you can read [this set of articles](https://sans-io.readthedocs.io/index.html#).

# Concepts

root tries its best to abstract the complexity of networking, while maintaining compatibility with low level concepts.

## Templating

When building a routing network using the root framework, the architect can specify a set of pre-defined parameters that defines it.

## The `NodeAddress` type

The NodeAddress is a globally (on each network) unique identifier that is attached to each node.

## The `Link` type

The link type represents a physical bidirectional connection between two nodes. This is not sent to other nodes, and should be unique on each node.

# Example Usage

> [!CAUTION]
> These examples do not implement MAC, meaning that routes/packets can be forged. The root crate implicitly trusts the authenticity of such packets.

## Basic Example

To demonstrate the use of the root crate, here is a super simple example where we have 3 nodes, `bob`, `eve`, and `alice`.

We have: `bob <-> eve <-> alice`, but not `bob <-> alice`.

We want the routing system to figure out how to reach `alice` from `bob`

We can start off by defining the routing parameters. This is a compile-time constant shared across all nodes.

```rust
use root::framework::RoutingSystem;
use root::router::NoMACSystem;

struct SimpleExample {} // just a type to inform root of your network parameters
impl RoutingSystem for SimpleExample{
type NodeAddress = String; // our nodes have string names
type Link = i32;
type MACSystem = NoMACSystem; // we won't use MAC for this example
}
```

Now, for each node, we can create a router:
```rust
// we have the following connection: bob <-> eve <-> alice

let mut nodes = HashMap::new();

let mut bob = Router::::new("bob".to_string());
bob.links.insert(1, Neighbour::new("eve".to_string()));
nodes.insert("bob", bob);

let mut eve = Router::::new("eve".to_string());
eve.links.insert(1, Neighbour::new("bob".to_string()));
eve.links.insert(2, Neighbour::new("alice".to_string()));
nodes.insert("eve", eve);

let mut alice = Router::::new("alice".to_string());
alice.links.insert(2, Neighbour::new("eve".to_string()));
nodes.insert("alice", alice);
```

Now we can let root take over, and have it automatically discover the route.

We simply let root generate routing packets, and simulate sending them to the other nodes. In a real network, these packets need to be serialized and sent over the network.

```rust
// lets simulate routing!

for step in 0..3 {
// collect all of our packets, if any
let packets: Vec> = nodes.iter_mut().flat_map(|(_id, node)| node.outbound_packets.drain(..)).collect();

for OutboundPacket{link, dest, packet} in packets{
// deliver the routing packet. in this simple example, the link isn't really used. in a real network, this link will give us information on how to send the packet
if let Some(node) = nodes.get_mut(dest.as_str()){
node.handle_packet(&packet, &link, &dest).expect("Failed to handle packet");
}
}

for node in nodes.values_mut(){
node.full_update(); // performs route table calculations, and writes routing updates into outbound_packets
}

// lets observe bob's route table:
println!("Bob's routes in step {step}:");
for (neigh, Route::{ metric, next_hop, .. }) in &nodes["bob"].routes{
println!(" - {neigh}: metric: {metric}, next_hop: {next_hop}")
}
}
```

Here is the output for this example:
```
Bob's routes in step 0:
Bob's routes in step 1:
- eve: metric: 1, next_hop: eve
Bob's routes in step 2:
- eve: metric: 1, next_hop: eve
- alice: metric: 2, next_hop: eve
```
> [!NOTE]
> You can try running this example yourself, its files are located in `./examples/super-simple`

## Network Example

> [!NOTE]
> To demonstrate the root crate working over real network connections, a complete example is provided in `./examples/simple-mesh`.

This example uses TCP streams as the transport, and is based on a event/channel pattern.