From cdd4e40646608207760e69c081e9bf3300d88c6d Mon Sep 17 00:00:00 2001 From: Adam Chen Date: Thu, 29 Aug 2024 21:10:34 -0400 Subject: [PATCH] add readme example --- Cargo.toml | 2 +- README.md | 88 ++++++++++++++++++++++++- examples/graph-gen/Cargo.toml | 2 +- examples/graph-gen/src/main.rs | 2 +- examples/mesh-vis/src/graph_parse.rs | 7 +- examples/simple-mesh/src/main.rs | 3 +- examples/simple-mesh/src/mesh_router.rs | 8 +-- examples/super-simple/Cargo.toml | 7 ++ examples/super-simple/src/main.rs | 64 ++++++++++++++++++ root/src/concepts/neighbour.rs | 18 +++-- root/src/router.rs | 14 ++-- root/src/util.rs | 6 +- root/tests/common/virtual_network.rs | 5 +- 13 files changed, 195 insertions(+), 31 deletions(-) create mode 100644 examples/super-simple/Cargo.toml create mode 100644 examples/super-simple/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 0015fb4..4a6acc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,4 @@ members = [ "examples/mesh-vis", "examples/simple-mesh", "examples/graph-gen" -] +, "examples/super-simple"] diff --git a/README.md b/README.md index 437ba82..aa8ff2b 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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. @@ -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 @@ -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::::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. \ No newline at end of file diff --git a/examples/graph-gen/Cargo.toml b/examples/graph-gen/Cargo.toml index 15f828a..2e9c063 100644 --- a/examples/graph-gen/Cargo.toml +++ b/examples/graph-gen/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -rand = "0.9.0-alpha.1" +rand = "0.9.0-alpha.1" \ No newline at end of file diff --git a/examples/graph-gen/src/main.rs b/examples/graph-gen/src/main.rs index fe128c6..ed6f173 100644 --- a/examples/graph-gen/src/main.rs +++ b/examples/graph-gen/src/main.rs @@ -1,5 +1,5 @@ use std::cmp::{max, min}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use rand::{Rng}; fn main() { diff --git a/examples/mesh-vis/src/graph_parse.rs b/examples/mesh-vis/src/graph_parse.rs index ec980f4..bbc9a66 100644 --- a/examples/mesh-vis/src/graph_parse.rs +++ b/examples/mesh-vis/src/graph_parse.rs @@ -206,10 +206,9 @@ pub fn load(state: &Yaml) -> anyhow::Result { }; 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); @@ -344,7 +343,7 @@ pub fn save(state: &State) -> Yaml { "{} {} {}", addr, n_addr, - neigh.link_cost + neigh.metric ) .as_str(), )); diff --git a/examples/simple-mesh/src/main.rs b/examples/simple-mesh/src/main.rs index 17027d6..184af71 100644 --- a/examples/simple-mesh/src/main.rs +++ b/examples/simple-mesh/src/main.rs @@ -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(), }, ); diff --git a/examples/simple-mesh/src/mesh_router.rs b/examples/simple-mesh/src/mesh_router.rs index f30aff9..c17fd69 100644 --- a/examples/simple-mesh/src/mesh_router.rs +++ b/examples/simple-mesh/src/mesh_router.rs @@ -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(), }, ); @@ -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 { @@ -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(), }, ); diff --git a/examples/super-simple/Cargo.toml b/examples/super-simple/Cargo.toml new file mode 100644 index 0000000..22860d7 --- /dev/null +++ b/examples/super-simple/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "super-simple" +version = "0.1.0" +edition = "2021" + +[dependencies] +root = { path = "../../root", features = ["serde"] } \ No newline at end of file diff --git a/examples/super-simple/src/main.rs b/examples/super-simple/src/main.rs new file mode 100644 index 0000000..df20311 --- /dev/null +++ b/examples/super-simple/src/main.rs @@ -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::::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); + + // 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 + 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}") + } + } + + // 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 +} diff --git a/root/src/concepts/neighbour.rs b/root/src/concepts/neighbour.rs index 11cca6a..9df5039 100644 --- a/root/src/concepts/neighbour.rs +++ b/root/src/concepts/neighbour.rs @@ -11,14 +11,20 @@ use crate::framework::{RoutingSystem}; #[educe(Clone(bound()))] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(bound = ""))] pub struct Neighbour { - /// 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>, - /// 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 Neighbour{ + pub fn new(addr: T::NodeAddress) -> Neighbour{ + Self{ + addr, + routes: Default::default(), + metric: 1, + } + } } \ No newline at end of file diff --git a/root/src/router.rs b/root/src/router.rs index 94ff74c..3d6a15a 100644 --- a/root/src/router.rs +++ b/root/src/router.rs @@ -68,6 +68,12 @@ impl Router { } } + 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(); @@ -146,7 +152,7 @@ impl Router { 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()); @@ -155,16 +161,16 @@ impl Router { } } 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) { diff --git a/root/src/util.rs b/root/src/util.rs index 1342bbb..4773b6a 100644 --- a/root/src/util.rs +++ b/root/src/util.rs @@ -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 } } diff --git a/root/tests/common/virtual_network.rs b/root/tests/common/virtual_network.rs index 12f557a..429388f 100644 --- a/root/tests/common/virtual_network.rs +++ b/root/tests/common/virtual_network.rs @@ -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, }); } } @@ -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 }); } }