Compare commits

..
Author SHA1 Message Date
215bc5166b wip 2025-11-30 18:32:43 +01:00
8 changed files with 31 additions and 124 deletions

View file

@ -53,16 +53,6 @@ Path found, destination <0e1fb61e3b38e332868b331740642f41> is 4 hops away via <1
For now, UDP ports are allocated to links in order from `42000` to `65535`. For now, UDP ports are allocated to links in order from `42000` to `65535`.
The graph can also be read from a text file listing edges, using option `-G <path>`. An edge is a space-separated list of two node indices. Separation can be any whitespace character. Example:
```
0 1
1 2
2 0
1 3
3 4
```
## License ## License
[Support me via LiberaPay](https://liberapay.com/tuxmain/donate) [Support me via LiberaPay](https://liberapay.com/tuxmain/donate)

View file

35
src/generator.rs Normal file
View file

@ -0,0 +1,35 @@
use rand::Rng;
fn get_neighbors(node: usize, edges: &[(usize, usize)]) -> impl Iterator<Item = usize> {
edges.iter().filter_map(move |(n1, n2)| {
if *n1 == node {
Some(*n2)
} else if *n2 == node {
Some(*n1)
} else {
None
}
})
}
/// Generate a random social graph
pub fn generate(n: usize, proba: f32, mut rng: impl Rng) -> Vec<(usize, usize)> {
let mut edges = Vec::new();
for friend in 1..n {
// Select a random anchor node in the graph.
let anchor = rng.gen_range(0..friend);
// The anchor is calling a friend to join the graph.
let mut new_friends = vec![anchor];
// Each other anchor's friend is also a friend of the new one with some probability.
for neighbor in get_neighbors(anchor, &edges) {
if rng.r#gen::<f32>() < proba {
new_friends.push(neighbor);
}
}
for new_friend in new_friends {
edges.push((new_friend, friend));
}
}
edges
}

View file

@ -1,20 +0,0 @@
use std::{
io::{BufRead, BufReader},
path::Path,
};
pub fn read_graph_file(path: &Path) -> Vec<(usize, usize)> {
let file = std::fs::OpenOptions::new().read(true).open(path).unwrap();
let reader = BufReader::new(file);
let mut edges = Vec::new();
for line in reader.lines() {
let line = line.unwrap();
let mut cols = line.split_whitespace();
if let (Some(n1), Some(n2)) = (cols.next(), cols.next()) {
if let (Ok(n1), Ok(n2)) = (n1.parse(), n2.parse()) {
edges.push((n1, n2));
}
}
}
edges
}

View file

@ -1,14 +1,14 @@
mod graph_generator; mod generator;
mod graph_reader; mod render;
mod graph_render; mod udp;
mod traffic_generator;
use std::{ use std::{
collections::{HashMap, hash_map::Entry}, collections::{hash_map::Entry, HashMap}, io::Write, net::Ipv4Addr, path::{PathBuf, Path}, os::{unix::net::{SocketAddr, UnixStream}, linux::net::SocketAddrExt}
io::Write,
path::{Path, PathBuf},
}; };
use rand::Rng;
use reticulum::{encode::Encode, sign::FixedKeys, packet::Packet, TestInf};
/// Generate configuration for local Reticulum networks /// Generate configuration for local Reticulum networks
#[derive(argp::FromArgs)] #[derive(argp::FromArgs)]
struct Cli { struct Cli {
@ -18,9 +18,6 @@ struct Cli {
/// Generate graph and create Reticulum configs /// Generate graph and create Reticulum configs
#[argp(switch, short = 'g', long = "gen")] #[argp(switch, short = 'g', long = "gen")]
generate: bool, generate: bool,
/// Read graph file instead of generating random (one edge per line, node numbers separated by space)
#[argp(option, short = 'G')]
graph: Option<String>,
/// Number of nodes /// Number of nodes
#[argp(option, short = 'n', default = "5")] #[argp(option, short = 'n', default = "5")]
nodes: usize, nodes: usize,
@ -40,17 +37,13 @@ fn main() {
let cli: Cli = argp::parse_args_or_exit(argp::DEFAULT); let cli: Cli = argp::parse_args_or_exit(argp::DEFAULT);
if cli.generate { if cli.generate {
let edges = if let Some(graph_file) = cli.graph { let mut rng = rand::thread_rng();
graph_reader::read_graph_file(Path::new(&graph_file))
} else {
let mut rng = rand::thread_rng();
assert!( assert!(
cli.proba >= 0.0 && cli.proba <= 1.0, cli.proba >= 0.0 && cli.proba <= 1.0,
"Probability should be between 0 and 1." "Probability should be between 0 and 1."
); );
graph_generator::generate(cli.nodes, cli.proba, &mut rng) let edges = generator::generate(cli.nodes, cli.proba, &mut rng);
};
let mut ports = HashMap::new(); let mut ports = HashMap::new();
let mut port: u16 = 42000; let mut port: u16 = 42000;
@ -58,8 +51,7 @@ fn main() {
#[cfg(feature = "render")] #[cfg(feature = "render")]
if let Some(out_svg) = &cli.render { if let Some(out_svg) = &cli.render {
let mut rng = rand::thread_rng(); render::render_to_svg(cli.nodes, &edges, out_svg, &mut rng);
graph_render::render_to_svg(cli.nodes, &edges, out_svg, &mut rng);
} }
let dir = PathBuf::from(&cli.dir); let dir = PathBuf::from(&cli.dir);
@ -142,6 +134,7 @@ forward_port = {}
} }
if cli.run { if cli.run {
let mut rng = rand::thread_rng();
let mut children = Vec::new(); let mut children = Vec::new();
for node in std::fs::read_dir(cli.dir).unwrap() { for node in std::fs::read_dir(cli.dir).unwrap() {
match node { match node {
@ -158,34 +151,31 @@ forward_port = {}
} }
} }
} }
std::thread::sleep(std::time::Duration::from_secs(4)); std::thread::sleep(std::time::Duration::from_secs(4));
traffic_generator::generate_traffic(cli.nodes); let mut socket = UnixStream::connect_addr(&SocketAddr::from_abstract_name(b"rns/n0").unwrap()).unwrap();
let mut socket2 = UnixStream::connect_addr(&SocketAddr::from_abstract_name(b"rns/n1").unwrap()).unwrap();
/*let mut socket = UnixStream::connect_addr(&SocketAddr::from_abstract_name(b"rns/n1").unwrap()).unwrap();
let mut socket2 = UnixStream::connect_addr(&SocketAddr::from_abstract_name(b"rns/n2").unwrap()).unwrap();
let (identity, secret, sk) = reticulum::identity::Identity::generate(&mut rng); let (identity, secret, sk) = reticulum::identity::Identity::generate(&mut rng);
let keys = FixedKeys::new(secret, sk); let keys = FixedKeys::new(secret, sk);
let dest = reticulum::destination::Destination::single_in(&identity, "synthesis", "idontknow"); let dest = reticulum::destination::Destination::single_in(&identity, "synthesis", "idontknow");
let announce = dest.announce_rnd(&mut rng, Some(b"hello"), &keys); let announce = dest.announce_rnd(&mut rng, None, &keys);
let mut announce_buf = Vec::new(); let mut announce_buf = Vec::new();
announce_buf.push(b'~'); announce.encode(&mut announce_buf);
let packet = Packet::<TestInf>::from_announce(announce); let packet = Packet::<TestInf>::from_announce(announce);
//dbg!(&packet); dbg!(&packet);
packet.encode(&mut announce_buf); packet.encode(&mut announce_buf);
announce_buf.push(b'~'); dbg!(&announce_buf);
//dbg!(&announce_buf);
socket.write_all(&announce_buf).unwrap(); socket.write_all(&announce_buf).unwrap();
socket.flush().unwrap(); let dest_out = reticulum::destination::Destination::plain_out("synthesis", "idonknow");
let dest_out = reticulum::destination::Destination::plain_out("synthesis", "idontknow");
let req = reticulum::path_request::PathRequest::new(dest.hash(), &dest_out, None, None); let req = reticulum::path_request::PathRequest::new(dest.hash(), &dest_out, None, None);
let mut req_buf = Vec::new(); let mut req_buf = Vec::new();
req_buf.push(b'~');
Packet::<TestInf>::from_path_request(req).encode(&mut req_buf); Packet::<TestInf>::from_path_request(req).encode(&mut req_buf);
req_buf.push(b'~');
std::thread::sleep(std::time::Duration::from_secs(4)); std::thread::sleep(std::time::Duration::from_secs(4));
socket2.write_all(&req_buf).unwrap();*/ socket2.write_all(&req_buf).unwrap();
/*loop {
let mut buf = Vec::new();
dbg!(socket2.re(&mut buf)).ok();
dbg!(buf);
}*/
for mut child in children { for mut child in children {
child.wait().ok(); child.wait().ok();
} }

195
src/render.rs Normal file
View file

@ -0,0 +1,195 @@
use forceatlas2::{AbstractNode, Layout};
use rand::Rng;
use std::io::Write;
pub fn render_to_svg(n: usize, edges: &[(usize, usize)], path: &str, mut rng: impl Rng) {
let width = 512;
let height = 512;
let node_radius = 12.0;
let mut layout = Layout::<f32, 2>::from_abstract(
forceatlas2::Settings {
theta: 0.5,
ka: 1.0,
kg: 1.0,
kr: 1.0,
lin_log: false,
prevent_overlapping: None,
speed: 0.1,
strong_gravity: false,
},
(0..n).map(|i| {
(
i,
AbstractNode {
size: 1.0,
mass: 1.0,
},
)
}),
edges.iter().map(|edge| ((edge.0, edge.1), 1.0)).collect(),
&mut rng,
);
// First run with normal forces to grossly form communities
for _ in 0..1000 {
layout.iteration();
}
// Now collapse into a compact disk using strong gravity
layout.set_settings(forceatlas2::Settings {
theta: 0.1,
ka: 1.0,
kg: 1.0,
kr: 1.0,
lin_log: false,
prevent_overlapping: None,
speed: 0.01,
strong_gravity: true,
});
for _ in 0..1000 {
layout.iteration();
}
let mut x_min = 0.0;
let mut x_max = 0.0;
let mut y_min = 0.0;
let mut y_max = 0.0;
for node in layout.nodes.iter() {
if node.pos.x() < x_min {
x_min = node.pos.x();
}
if node.pos.x() > x_max {
x_max = node.pos.x();
}
if node.pos.y() < y_min {
y_min = node.pos.y();
}
if node.pos.y() > y_max {
y_max = node.pos.y();
}
}
let x_range = x_max - x_min;
let y_range = y_max - y_min;
let x_factor = (width as f32 - node_radius * 2.0) / x_range;
let y_factor = (height as f32 - node_radius * 2.0) / y_range;
let node_size = node_radius / x_factor.min(y_factor);
for node in layout.nodes.iter_mut() {
node.size = node_size;
}
// Finally remove overlapping
layout.set_settings(forceatlas2::Settings {
theta: 0.05,
ka: 1.0,
kg: 1.0,
kr: 1.0,
lin_log: false,
prevent_overlapping: Some(100.0),
speed: 0.01,
strong_gravity: true,
});
for _ in 0..100 {
layout.iteration();
}
let mut x_min = 0.0;
let mut x_max = 0.0;
let mut y_min = 0.0;
let mut y_max = 0.0;
for node in layout.nodes.iter() {
if node.pos.x() < x_min {
x_min = node.pos.x();
}
if node.pos.x() > x_max {
x_max = node.pos.x();
}
if node.pos.y() < y_min {
y_min = node.pos.y();
}
if node.pos.y() > y_max {
y_max = node.pos.y();
}
}
let x_range = x_max - x_min;
let y_range = y_max - y_min;
let x_factor = (width as f32 - node_radius * 2.0) / x_range;
let y_factor = (height as f32 - node_radius * 2.0) / y_range;
let x_mid = (x_min + x_max) * 0.5;
let y_mid = (y_min + y_max) * 0.5;
let mut outfile = std::fs::OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(path)
.unwrap();
outfile
.write_all(
format!(
r#"<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="{}" height="{}" viewBox="{} {} {} {}">
<title>Network graph</title>
<style type="text/css">
.node-label {{
text-anchor: middle;
dominant-baseline: central;
font-size: 12px;
}}
</style>
"#,
width,
height,
width as f32 * -0.5,
height as f32 * -0.5,
width,
height
)
.as_bytes(),
)
.unwrap();
for ((n1, n2), _weight) in layout.edges {
let p1 = layout.nodes[n1].pos;
let p2 = layout.nodes[n2].pos;
outfile
.write_all(
format!(
r#"<line x1="{}" y1="{}" x2="{}" y2="{}" stroke="black"/>"#,
(p1.x() - x_mid) * x_factor,
(p1.y() - y_mid) * y_factor,
(p2.x() - x_mid) * x_factor,
(p2.y() - y_mid) * y_factor
)
.as_bytes(),
)
.unwrap();
}
for (i, node) in layout.nodes.iter().enumerate() {
let x = (node.pos.x() - x_mid) * x_factor;
let y = (node.pos.y() - y_mid) * y_factor;
outfile
.write_all(
format!(
r#"<circle cx="{}" cy="{}" r="{}" stroke="black" fill="white"/>"#,
x, y, node_radius
)
.as_bytes(),
)
.unwrap();
outfile
.write_all(
format!(
r#"<text x="{}" y="{}" class="node-label">{}</text>"#,
x, y, i
)
.as_bytes(),
)
.unwrap();
}
outfile.write_all(br#"</svg>"#).unwrap();
outfile.flush().unwrap();
}

View file

@ -1,56 +0,0 @@
use reticulum::{
TestInf, destination::Destination, encode::Encode, identity::Identity, packet::Packet,
sign::FixedKeys,
};
use std::{
io::Write,
os::{
linux::net::SocketAddrExt,
unix::net::{SocketAddr, UnixStream},
},
};
pub fn generate_traffic(nodes: usize) {
let mut rng = rand::thread_rng();
let mut sockets: Vec<_> = (0..nodes)
.map(|i| {
UnixStream::connect_addr(
&SocketAddr::from_abstract_name(format!("rns/n{i}").as_bytes()).unwrap(),
)
.unwrap()
})
.collect();
let identities: Vec<_> = (0..nodes)
.map(|_i| {
let (identity, secret, sk) = Identity::generate(&mut rng);
(identity, FixedKeys::new(secret, sk))
})
.collect();
let destinations: Vec<_> = identities
.iter()
.map(|(identity, keys)| {
(
Destination::single_in(&identity, "synthesis", "idontknow"),
keys,
)
})
.collect();
for (((destination, keys), _identity), socket) in destinations
.iter()
.zip(identities.iter())
.zip(sockets.iter_mut())
{
let announce = destination.announce_rnd(&mut rng, Some(b"hello"), *keys);
let mut announce_buf = Vec::new();
announce_buf.push(b'~');
let packet = Packet::<TestInf>::from_announce(announce);
packet.encode(&mut announce_buf);
announce_buf.push(b'~');
socket.write_all(&announce_buf).unwrap();
socket.flush().unwrap();
}
}

3
src/udp.rs Normal file
View file

@ -0,0 +1,3 @@
pub struct UdpInterface {
}