Skip to content
/ root Public

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

License

Notifications You must be signed in to change notification settings

encodeous/root

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

96 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

root

root is an abstract I/O-free routing framework inspired by the Babel Routing Protocol. 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.

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.

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:

// we have the following connection: bob <-> eve <-> alice

let mut nodes = HashMap::new();

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

let mut eve = Router::<SimpleExample>::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::<SimpleExample>::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.

// lets simulate routing!

for step in 0..3 {
    // collect all of our packets, if any
    let packets: Vec<OutboundPacket<SimpleExample>> = 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::<SimpleExample>{ 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.

About

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

Resources

License

Stars

Watchers

Forks

Languages