Initial commit: waste-rs mesh daemon, relay, and proto crates

Full Rust implementation with Ed25519 identity, X25519 ECDH handshake,
ChaCha20-Poly1305 encrypted peer connections, IPC server, and relay server.
Builds on Windows (MSVC), Linux, and macOS.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Fredrik Johansson
2026-06-21 16:32:36 +02:00
commit d2c06680f7
21 changed files with 2797 additions and 0 deletions

19
relay/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "waste-relay"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "waste-relay"
path = "src/main.rs"
[dependencies]
proto = { path = "../proto" }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }
chrono = { workspace = true }
clap = { workspace = true }

153
relay/src/main.rs Normal file
View File

@@ -0,0 +1,153 @@
/// waste-relay: minimal relay server.
///
/// Run this on your Hetzner VPS. Peers register with their public key + address,
/// and can ask the relay to forward encrypted envelopes to other peers.
/// The relay NEVER sees plaintext — it only forwards opaque blobs.
use anyhow::Result;
use chrono::Utc;
use clap::Parser;
use proto::{GossipEntry, PeerId, RelayClientMessage, RelayServerMessage};
use std::{collections::HashMap, sync::Arc};
use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
net::{TcpListener, TcpStream},
sync::{mpsc, RwLock},
};
use tracing::{info, warn};
#[derive(Parser)]
#[command(name = "waste-relay", about = "WASTE-rs relay/bootstrap server")]
struct Args {
#[arg(long, default_value = "0.0.0.0:17339")]
bind: String,
}
// ── Peer registry ─────────────────────────────────────────────────────────────
struct RegisteredPeer {
gossip: GossipEntry,
/// Channel to forward envelopes to this peer's connection handler.
tx: mpsc::Sender<RelayServerMessage>,
}
type Registry = Arc<RwLock<HashMap<PeerId, RegisteredPeer>>>;
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter("waste_relay=debug,info")
.init();
let args = Args::parse();
let registry: Registry = Arc::new(RwLock::new(HashMap::new()));
let listener = TcpListener::bind(&args.bind).await?;
info!("waste-relay listening on {}", args.bind);
loop {
let (stream, addr) = listener.accept().await?;
info!("Client connected: {addr}");
let registry = registry.clone();
tokio::spawn(async move {
if let Err(e) = handle_client(stream, registry).await {
warn!("Client error: {e:#}");
}
});
}
}
async fn handle_client(stream: TcpStream, registry: Registry) -> Result<()> {
let peer_addr = stream.peer_addr()?.to_string();
let (reader, mut writer) = stream.into_split();
let mut reader = BufReader::new(reader);
let (tx, mut rx) = mpsc::channel::<RelayServerMessage>(64);
// Writer task
tokio::spawn(async move {
while let Some(msg) = rx.recv().await {
if let Ok(line) = serde_json::to_string(&msg) {
if writer.write_all((line + "\n").as_bytes()).await.is_err() {
break;
}
}
}
});
let mut my_peer_id: Option<PeerId> = None;
let mut line = String::new();
loop {
line.clear();
let n = reader.read_line(&mut line).await?;
if n == 0 {
break;
}
let cmd: RelayClientMessage = match serde_json::from_str(line.trim()) {
Ok(c) => c,
Err(e) => {
warn!("Bad relay message from {peer_addr}: {e}");
continue;
}
};
match cmd {
RelayClientMessage::Register { peer, signature: _ } => {
// TODO: verify signature against peer.public_key
info!("Registered peer {} ({}) at {peer_addr}", peer.id, peer.alias);
my_peer_id = Some(peer.id.clone());
let entry = GossipEntry {
peer: peer.clone(),
addr_hint: peer_addr.clone(),
last_seen: Utc::now(),
};
registry.write().await.insert(
peer.id,
RegisteredPeer {
gossip: entry,
tx: tx.clone(),
},
);
}
RelayClientMessage::ListPeers => {
let peers: Vec<GossipEntry> = registry
.read()
.await
.values()
.map(|p| p.gossip.clone())
.collect();
let _ = tx.send(RelayServerMessage::PeerList { peers }).await;
}
RelayClientMessage::Forward { to, envelope } => {
let reg = registry.read().await;
if let Some(dest) = reg.get(&to) {
let from = my_peer_id
.clone()
.unwrap_or_else(|| PeerId("unknown".into()));
let _ = dest
.tx
.send(RelayServerMessage::Forwarded { from, envelope })
.await;
} else {
let _ = tx
.send(RelayServerMessage::Error {
message: format!("Peer {to} not registered"),
})
.await;
}
}
}
}
// Clean up on disconnect
if let Some(pid) = my_peer_id {
registry.write().await.remove(&pid);
info!("Peer {pid} ({peer_addr}) unregistered");
}
Ok(())
}