Add overlay planning commands

This commit is contained in:
Eric Wendland 2026-05-23 01:17:30 +02:00
commit c2dee50dae
18 changed files with 928 additions and 16 deletions

View file

@ -0,0 +1,182 @@
use geth_types::ResourceId;
use serde::{Deserialize, Serialize};
use std::net::Ipv4Addr;
pub const DEFAULT_OVERLAY_CIDR: &str = "172.22.0.0/24";
pub const OVERLAY_ALPN: &str = "/geth/overlay/1";
pub const CAPABILITY_JOIN: &str = "overlay.join";
pub const CAPABILITY_ROUTE: &str = "overlay.route";
pub const CAPABILITY_ADMIN: &str = "overlay.admin";
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum OverlayState {
Planned,
Joined,
Running,
Stopped,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OverlayPlan {
pub name: String,
pub resource: ResourceId,
pub cidr: String,
pub alpn: String,
pub capabilities: Vec<String>,
pub discovery: String,
pub runtime: String,
pub security: Vec<String>,
pub implementation_notes: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OverlayJoinPlan {
pub plan: OverlayPlan,
pub enabled: bool,
pub note: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OverlayPeer {
pub node_id: String,
pub endpoint_id: Option<String>,
pub virtual_ip: Option<String>,
pub state: OverlayState,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct OverlayNetworkStatus {
pub name: String,
pub resource: ResourceId,
pub cidr: String,
pub state: OverlayState,
pub virtual_ip: Option<String>,
pub peers: Vec<OverlayPeer>,
pub note: String,
}
#[derive(Debug, thiserror::Error)]
pub enum OverlayError {
#[error("invalid overlay name `{0}`")]
InvalidName(String),
#[error("invalid overlay IPv4 CIDR `{0}`")]
InvalidCidr(String),
#[error("overlay join requires a non-empty resource secret")]
EmptySecret,
}
pub fn validate_overlay_name(name: &str) -> Result<(), OverlayError> {
if name.is_empty()
|| name.len() > 63
|| !name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-'))
|| name.starts_with('.')
|| name.starts_with('-')
{
return Err(OverlayError::InvalidName(name.to_owned()));
}
Ok(())
}
pub fn validate_overlay_cidr(cidr: &str) -> Result<(), OverlayError> {
let (addr, prefix) = cidr
.split_once('/')
.ok_or_else(|| OverlayError::InvalidCidr(cidr.to_owned()))?;
addr.parse::<Ipv4Addr>()
.map_err(|_| OverlayError::InvalidCidr(cidr.to_owned()))?;
let prefix = prefix
.parse::<u8>()
.map_err(|_| OverlayError::InvalidCidr(cidr.to_owned()))?;
if prefix > 32 {
return Err(OverlayError::InvalidCidr(cidr.to_owned()));
}
Ok(())
}
pub fn validate_overlay_secret(secret: &str) -> Result<(), OverlayError> {
if secret.trim().is_empty() {
Err(OverlayError::EmptySecret)
} else {
Ok(())
}
}
#[must_use]
pub fn overlay_resource_id(name: &str) -> ResourceId {
ResourceId::new(format!("resource:overlay:{name}"))
}
pub fn plan_overlay(
name: &str,
cidr: Option<&str>,
alpn: &str,
) -> Result<OverlayPlan, OverlayError> {
validate_overlay_name(name)?;
let cidr = cidr.unwrap_or(DEFAULT_OVERLAY_CIDR);
validate_overlay_cidr(cidr)?;
Ok(OverlayPlan {
name: name.to_owned(),
resource: overlay_resource_id(name),
cidr: cidr.to_owned(),
alpn: alpn.to_owned(),
capabilities: vec![
CAPABILITY_JOIN.to_owned(),
CAPABILITY_ROUTE.to_owned(),
CAPABILITY_ADMIN.to_owned(),
],
discovery: "future overlay discovery may use mDNS, peer exchange, and resource metadata; discovery remains untrusted".to_owned(),
runtime: "planned only in this prototype; no TUN/Wintun interface is created".to_owned(),
security: vec![
"all overlay packets must be carried over daemon-owned Iroh connections".to_owned(),
"knowing an EndpointID or overlay name must not grant overlay access".to_owned(),
"overlay membership must be resource-authorized with overlay.join/overlay.route capabilities".to_owned(),
"shared overlay secrets are resource-scoped and must not mutate node identity".to_owned(),
],
implementation_notes: vec![
"inspired by iroh-lan's Iroh-carried packet overlay".to_owned(),
"future packet runtime should register /geth/overlay/1 on the shared geth Iroh router".to_owned(),
"future host integration may need TUN/Wintun privileges and must remain explicitly opt-in".to_owned(),
],
})
}
#[must_use]
pub fn overlay_status_note() -> &'static str {
"overlay runtime is scaffolded but inactive; use `geth overlay plan <name>` to inspect the intended resource and capabilities"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validates_overlay_names() {
validate_overlay_name("home-lan").expect("valid name");
validate_overlay_name("dev.mesh_1").expect("valid name");
assert!(validate_overlay_name("").is_err());
assert!(validate_overlay_name("../lan").is_err());
assert!(validate_overlay_name("-lan").is_err());
}
#[test]
fn validates_overlay_cidrs() {
validate_overlay_cidr(DEFAULT_OVERLAY_CIDR).expect("valid cidr");
validate_overlay_cidr("10.44.0.0/16").expect("valid cidr");
assert!(validate_overlay_cidr("10.44.0.0").is_err());
assert!(validate_overlay_cidr("10.44.0.0/33").is_err());
assert!(validate_overlay_cidr("not-a-cidr").is_err());
}
#[test]
fn overlay_plan_is_iroh_only_and_resource_scoped() {
let plan = plan_overlay("home", None, OVERLAY_ALPN).expect("plan");
assert_eq!(plan.resource, ResourceId::new("resource:overlay:home"));
assert_eq!(plan.cidr, DEFAULT_OVERLAY_CIDR);
assert!(plan.capabilities.contains(&CAPABILITY_JOIN.to_owned()));
assert!(plan.security.iter().any(|note| note.contains("Iroh")));
assert!(plan.runtime.contains("planned only"));
}
}