Skip to content

Commit

Permalink
add readme example
Browse files Browse the repository at this point in the history
  • Loading branch information
encodeous committed Aug 30, 2024
1 parent 2996aef commit cdd4e40
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ members = [
"examples/mesh-vis",
"examples/simple-mesh",
"examples/graph-gen"
]
, "examples/super-simple"]
88 changes: 87 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ The application is solely responsible for providing I/O and scheduling events, m

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.
Expand All @@ -15,6 +18,7 @@ root is designed from the ground up to offer a platform, network, and protocol a

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.
Expand All @@ -33,9 +37,23 @@ The link type represents a physical bidirectional connection between two nodes.

# Example Usage

To create a bare-bones network using root, you can use the following example:
> [!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
Expand All @@ -44,3 +62,71 @@ impl RoutingSystem for SimpleExample{
}
```

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::<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.

```rust
// 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.
2 changes: 1 addition & 1 deletion examples/graph-gen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ version = "0.1.0"
edition = "2021"

[dependencies]
rand = "0.9.0-alpha.1"
rand = "0.9.0-alpha.1"
2 changes: 1 addition & 1 deletion examples/graph-gen/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::cmp::{max, min};
use std::collections::HashSet;
use std::collections::{HashMap, HashSet};
use rand::{Rng};

fn main() {
Expand Down
7 changes: 3 additions & 4 deletions examples/mesh-vis/src/graph_parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,9 @@ pub fn load(state: &Yaml) -> anyhow::Result<State> {
};
for (_, neigh, metric) in adj.iter().filter(|x| x.0 == *node) {
sys.router.links.insert(*neigh, Neighbour{
link_cost: *metric,
metric: *metric,
addr: *neigh,
routes: HashMap::new(),
link: *neigh
routes: HashMap::new()
});
}
nodes.push(sys);
Expand Down Expand Up @@ -344,7 +343,7 @@ pub fn save(state: &State) -> Yaml {
"{} {} {}",
addr,
n_addr,
neigh.link_cost
neigh.metric
)
.as_str(),
));
Expand Down
3 changes: 1 addition & 2 deletions examples/simple-mesh/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,7 @@ async fn main() -> anyhow::Result<()> {
*link,
Neighbour {
addr: netlink.neigh_node.clone(),
link: netlink.link,
link_cost: INF,
metric: INF,
routes: HashMap::new(),
},
);
Expand Down
8 changes: 3 additions & 5 deletions examples/simple-mesh/src/mesh_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,7 @@ fn handle_packet(
link_id,
Neighbour {
addr: node_id.clone(),
link: link_id,
link_cost: INF,
metric: INF,
routes: HashMap::new(),
},
);
Expand Down Expand Up @@ -427,7 +426,7 @@ fn update_link_health(
new_ping: Duration,
) -> anyhow::Result<()> {
if let Some(neigh) = ps.router.links.get_mut(&link) {
neigh.link_cost = {
neigh.metric = {
if new_ping == Duration::MAX {
INF
} else {
Expand Down Expand Up @@ -655,8 +654,7 @@ fn handle_command(
netlink.link,
Neighbour {
addr: netlink.neigh_node.clone(),
link: netlink.link,
link_cost: INF,
metric: INF,
routes: HashMap::new(),
},
);
Expand Down
7 changes: 7 additions & 0 deletions examples/super-simple/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "super-simple"
version = "0.1.0"
edition = "2021"

[dependencies]
root = { path = "../../root", features = ["serde"] }
64 changes: 64 additions & 0 deletions examples/super-simple/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use std::collections::HashMap;
use root::concepts::neighbour::Neighbour;
use root::concepts::packet::OutboundPacket;
use root::concepts::route::Route;
use root::framework::RoutingSystem;
use root::router::{NoMACSystem, Router};

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
}

fn main() {
// 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);

// 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
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}")
}
}

// OUTPUT:
// 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
}
18 changes: 12 additions & 6 deletions root/src/concepts/neighbour.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ use crate::framework::{RoutingSystem};
#[educe(Clone(bound()))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))]
pub struct Neighbour<T: RoutingSystem + ?Sized> {
/// the physical network link id, the pair (link, addr) should be unique
pub link: T::Link,
/// the routing network address
pub addr: T::NodeAddress,
// pub hello_interval: Duration,
// pub timer_last_ihu: Instant,
pub routes: HashMap<T::NodeAddress, ExternalRoute<T>>,
/// Direct Link-cost to this neighbour, 0xFFFF for Infinity. Lower is better.
/// Direct Link-metric to this neighbour, 0xFFFF for Infinity. Lower is better.
/// INF if the link is down
pub link_cost: u16
pub metric: u16
}

impl<T: RoutingSystem + ?Sized> Neighbour<T>{
pub fn new(addr: T::NodeAddress) -> Neighbour<T>{
Self{
addr,
routes: Default::default(),
metric: 1,
}
}
}
14 changes: 10 additions & 4 deletions root/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ impl<T: RoutingSystem> Router<T> {
}
}

pub fn set_link_metric(&mut self, link: &T::Link, new_metric: u16){
if let Some(neigh) = self.links.get_mut(link){
neigh.metric = new_metric;
}
}

/// updates the state of the router, does not broadcast routes
pub fn update(&mut self){
self.update_routes();
Expand Down Expand Up @@ -146,7 +152,7 @@ impl<T: RoutingSystem> Router<T> {
for (_addr, route) in &mut self.routes {
let link = &route.link;
// check if link still exists
if !self.links.contains_key(link) || self.links.get(link).unwrap().link_cost == INF{
if !self.links.contains_key(link) || self.links.get(link).unwrap().metric == INF{
route.metric = INF;
if !route.retracted{
retractions.push(route.source.clone());
Expand All @@ -155,16 +161,16 @@ impl<T: RoutingSystem> Router<T> {
}
}
for (link, neigh) in &mut self.links {
if neigh.link_cost == 0 {
if neigh.metric == 0 {
self.warnings.push_back(MetricIsZero {link: link.clone()});
neigh.link_cost = 1;
neigh.metric = 1;
}
for (src, neigh_route) in &neigh.routes {
if *src == self.address{
continue; // we can safely ignore a route to ourself
}

let metric = sum_inf(neigh.link_cost, neigh_route.metric);
let metric = sum_inf(neigh.metric, neigh_route.metric);

// if the table has the route
if let Some(table_route) = self.routes.get_mut(src) {
Expand Down
6 changes: 3 additions & 3 deletions root/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ pub fn increment_by(x: u16, y: u16) -> u16 {
x.overflowing_add(y).0
}

pub fn sum_inf(cost_a: u16, cost_b: u16) -> u16 {
if cost_a == INF || cost_b == INF {
pub fn sum_inf(metric_a: u16, metric_b: u16) -> u16 {
if metric_a == INF || metric_b == INF {
INF
} else {
min((INF - 1) as u32, cost_a as u32 + cost_b as u32) as u16
min((INF - 1) as u32, metric_a as u32 + metric_b as u32) as u16
}
}
5 changes: 2 additions & 3 deletions root/tests/common/virtual_network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ impl VirtualSystem{
if a == id {b} else {a}
};
router.links.insert(*lid, Neighbour{
link: *lid,
addr: nid.to_string(),
routes: Default::default(),
link_cost: *metric,
metric: *metric,
});
}
}
Expand All @@ -39,7 +38,7 @@ impl VirtualSystem{
pub fn update_edge(&mut self, edge_id: i32, metric: u16){
for router in &mut self.routers{
router.links.entry(edge_id).and_modify(|edge| {
edge.link_cost = metric
edge.metric = metric
});
}
}
Expand Down

0 comments on commit cdd4e40

Please sign in to comment.