Implement opt-in overlay TUN runtime

This commit is contained in:
Eric Wendland 2026-05-23 02:08:51 +02:00
commit d9728a326d
15 changed files with 2315 additions and 84 deletions

View file

@ -189,14 +189,25 @@ Current prototype commands:
geth overlay plan home
geth overlay plan home --cidr 172.22.0.0/24
geth overlay join home --secret <resource-secret>
geth overlay interface-plan home --platform linux
geth overlay up home
geth overlay down home
geth overlay send home <node> --packet-base64 <ipv4-packet>
geth overlay recv home
geth overlay leave home
Current limits:
- join/leave are planning stubs; no TUN/Wintun interface is created yet
- host network changes must remain explicit opt-in in future versions
- overlay up creates a real TUN/Wintun-style L3 device and usually needs
privileges or host network entitlements
- host network changes are explicit opt-in only
- discovery can suggest peers, but never grants overlay access
- overlay access must be resource-authorized with overlay.join/overlay.route
- all overlay packets must be carried over Iroh, not SSH or another transport
Bearer invite flow:
geth resource create overlay home
geth secret bearer create resource:overlay:home --capability overlay.join
geth overlay join home --secret <bearer-token>
"#;
const GUIDE_SMOKE_TEST: &str = r#"Minimal smoke test:
@ -540,6 +551,37 @@ pub enum OverlayCommand {
Leave {
name: String,
},
InterfacePlan {
name: String,
#[arg(long)]
platform: Option<String>,
},
Up {
name: String,
#[arg(long)]
bearer_secret: Option<String>,
#[arg(long)]
mtu: Option<u16>,
},
Down {
name: String,
},
Peers {
name: String,
},
Send {
name: String,
node: String,
#[arg(long)]
packet_base64: String,
#[arg(long)]
bearer_secret: Option<String>,
},
Recv {
name: String,
#[arg(long)]
peek: bool,
},
}
#[derive(Debug, Subcommand)]
@ -1297,6 +1339,32 @@ fn request_for_command(command: Command) -> Result<ControlRequest> {
ControlRequest::OverlayJoin { name, secret, cidr }
}
OverlayCommand::Leave { name } => ControlRequest::OverlayLeave { name },
OverlayCommand::InterfacePlan { name, platform } => {
ControlRequest::OverlayInterfacePlan { name, platform }
}
OverlayCommand::Up {
name,
bearer_secret,
mtu,
} => ControlRequest::OverlayUp {
name,
bearer_secret,
mtu,
},
OverlayCommand::Down { name } => ControlRequest::OverlayDown { name },
OverlayCommand::Peers { name } => ControlRequest::OverlayPeers { name },
OverlayCommand::Send {
name,
node,
packet_base64,
bearer_secret,
} => ControlRequest::OverlaySend {
name,
node,
packet_base64,
bearer_secret,
},
OverlayCommand::Recv { name, peek } => ControlRequest::OverlayRecv { name, peek },
},
Command::Resource {
command: ResourceCommand::List,
@ -1989,6 +2057,12 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> {
ControlResponse::OverlayJoined { join } => {
println!("overlay: {}", join.plan.name);
println!("resource: {}", join.plan.resource);
println!("cidr: {}", join.network.cidr);
println!(
"virtual_ip: {}",
join.network.virtual_ip.as_deref().unwrap_or("unassigned")
);
println!("state: {:?}", join.network.state);
println!("enabled: {}", join.enabled);
println!("note: {}", join.note);
}
@ -2001,6 +2075,99 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> {
println!("stopped: {stopped}");
println!("note: {note}");
}
ControlResponse::OverlayInterfacePlanned { plan } => {
println!("overlay: {}", plan.name);
println!("platform: {}", plan.platform);
println!("interface: {}", plan.interface_name);
println!("cidr: {}", plan.cidr);
println!(
"virtual_ip: {}",
plan.virtual_ip.as_deref().unwrap_or("unassigned")
);
println!("requires_privileges: {}", plan.requires_privileges);
for command in plan.commands {
println!("command: {command}");
}
for note in plan.notes {
println!("note: {note}");
}
}
ControlResponse::OverlayRuntimeStarted { status } => {
println!("overlay: {}", status.name);
println!("interface: {}", status.interface_name);
println!("virtual_ip: {}", status.virtual_ip);
println!("cidr: {}", status.cidr);
println!("mtu: {}", status.mtu);
println!("packets_from_tun: {}", status.packets_from_tun);
println!("packets_to_tun: {}", status.packets_to_tun);
println!("packets_to_peers: {}", status.packets_to_peers);
if let Some(error) = status.last_error {
println!("last_error: {error}");
}
println!("note: {}", status.note);
}
ControlResponse::OverlayRuntimeStopped {
name,
stopped,
note,
} => {
println!("overlay: {name}");
println!("stopped: {stopped}");
println!("note: {note}");
}
ControlResponse::OverlayPeers { name, peers, note } => {
println!("overlay: {name}");
if peers.is_empty() {
println!("no overlay peer candidates");
} else {
for peer in peers {
println!(
"{}\t{}\t{}\t{}",
peer.node_id,
peer.endpoint_id.as_deref().unwrap_or("no-endpoint"),
peer.virtual_ip.as_deref().unwrap_or("no-virtual-ip"),
peer.state
);
}
}
println!("note: {note}");
}
ControlResponse::OverlayPacketSent {
peer_node_id,
peer_agent_id,
endpoint_id,
packet,
allowed,
reason,
note,
} => {
println!("peer: {peer_node_id}");
println!("agent: {peer_agent_id}");
println!("endpoint: {endpoint_id}");
println!("allowed: {allowed}");
println!("reason: {reason}");
if let Some(packet) = packet {
println!("packet: {}", packet.id);
println!("size_bytes: {}", packet.size_bytes);
}
println!("note: {note}");
}
ControlResponse::OverlayPackets {
name,
packets,
drained,
note,
} => {
println!("overlay: {name}");
println!("drained: {drained}");
for packet in packets {
println!(
"{}\t{}\t{}\t{} bytes",
packet.id, packet.source_node, packet.destination_node, packet.size_bytes
);
}
println!("note: {note}");
}
ControlResponse::CasAdded { hash, size_bytes } => {
println!("{hash} {size_bytes} bytes");
}