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

@ -5,7 +5,10 @@ use geth_discovery::{DiscoveredPeer, PeerCard};
use geth_document::{DocumentResource, DocumentState};
use geth_keychain::{KeychainOp, KeychainOpSignature, NodeEnrollmentRequest, NodeRecord};
use geth_kv::{KvEntry, KvResource, KvSyncEntry};
use geth_overlay::{OverlayJoinPlan, OverlayNetworkStatus, OverlayPlan};
use geth_overlay::{
OverlayInterfacePlan, OverlayJoinPlan, OverlayNetworkStatus, OverlayPacket, OverlayPlan,
OverlayRuntimeStatus,
};
use geth_pipe::{PipeConnection, PipeListener, PipeMessage};
use geth_pubsub::PubsubMessage;
use geth_resource::ResourceDescriptor;
@ -57,6 +60,31 @@ pub enum ControlRequest {
OverlayLeave {
name: String,
},
OverlayInterfacePlan {
name: String,
platform: Option<String>,
},
OverlayUp {
name: String,
bearer_secret: Option<String>,
mtu: Option<u16>,
},
OverlayDown {
name: String,
},
OverlayPeers {
name: String,
},
OverlaySend {
name: String,
node: String,
packet_base64: String,
bearer_secret: Option<String>,
},
OverlayRecv {
name: String,
peek: bool,
},
CasAdd {
path: PathBuf,
},
@ -501,6 +529,37 @@ pub enum ControlResponse {
stopped: bool,
note: String,
},
OverlayInterfacePlanned {
plan: OverlayInterfacePlan,
},
OverlayRuntimeStarted {
status: OverlayRuntimeStatus,
},
OverlayRuntimeStopped {
name: String,
stopped: bool,
note: String,
},
OverlayPeers {
name: String,
peers: Vec<OverlayPeer>,
note: String,
},
OverlayPacketSent {
peer_node_id: String,
peer_agent_id: String,
endpoint_id: String,
packet: Option<OverlayPacket>,
allowed: bool,
reason: String,
note: String,
},
OverlayPackets {
name: String,
packets: Vec<OverlayPacket>,
drained: bool,
note: String,
},
CasAdded {
hash: BlobHash,
size_bytes: u64,
@ -990,6 +1049,14 @@ pub struct CasProvider {
pub last_seen_ms: i64,
}
#[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: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum PeerControlRequest {
@ -1408,6 +1475,38 @@ pub enum PipeWireResponse {
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum OverlayWireRequest {
Packet {
peer_card: PeerCard,
network: String,
packet_base64: String,
nonce: String,
bearer_proof: Option<BearerProof>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum OverlayWireResponse {
PacketAccepted {
node_id: String,
agent_id: String,
endpoint_id: String,
remote_endpoint_id: String,
packet: Option<Box<OverlayPacket>>,
allowed: bool,
reason: String,
evaluated_ops: usize,
nonce: String,
note: String,
},
Error {
message: String,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SyncWatermark {
pub stream: String,
@ -1518,6 +1617,28 @@ pub fn decode_pipe_wire_response(line: &str) -> Result<PipeWireResponse, Control
serde_json::from_str(line).map_err(ControlError::from)
}
pub fn encode_overlay_wire_request(request: &OverlayWireRequest) -> Result<String, ControlError> {
let mut line = serde_json::to_string(request)?;
line.push('\n');
Ok(line)
}
pub fn decode_overlay_wire_request(line: &str) -> Result<OverlayWireRequest, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
pub fn encode_overlay_wire_response(
response: &OverlayWireResponse,
) -> Result<String, ControlError> {
let mut line = serde_json::to_string(response)?;
line.push('\n');
Ok(line)
}
pub fn decode_overlay_wire_response(line: &str) -> Result<OverlayWireResponse, ControlError> {
serde_json::from_str(line).map_err(ControlError::from)
}
#[cfg(test)]
mod tests {
use super::*;
@ -1587,6 +1708,27 @@ mod tests {
request
);
let request = ControlRequest::OverlayUp {
name: "home-lan".to_owned(),
bearer_secret: Some("gbt_route".to_owned()),
mtu: Some(1280),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let request = ControlRequest::OverlaySend {
name: "home-lan".to_owned(),
node: "node:peer".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
bearer_secret: Some("gbt_invite".to_owned()),
};
assert_eq!(
decode_request(&encode_request(&request).expect("encode")).expect("decode"),
request
);
let response = ControlResponse::CasHas {
hash: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef".into(),
present: true,
@ -1605,6 +1747,69 @@ mod tests {
response
);
let response = ControlResponse::OverlayJoined {
join: geth_overlay::OverlayJoinPlan {
plan: geth_overlay::plan_overlay("home-lan", None, geth_overlay::OVERLAY_ALPN)
.expect("overlay plan"),
network: geth_overlay::membership_status(
&geth_overlay::joined_overlay_membership(
"home-lan",
None,
"node:local",
"invite",
10,
)
.expect("overlay membership"),
),
enabled: true,
note: "membership persisted".to_owned(),
},
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::OverlayRuntimeStarted {
status: geth_overlay::OverlayRuntimeStatus {
name: "home-lan".to_owned(),
interface_name: "geth-homelan".to_owned(),
virtual_ip: "172.22.0.10".to_owned(),
cidr: "172.22.0.0/24".to_owned(),
mtu: 1280,
started_at_ms: 12,
packets_from_tun: 1,
packets_to_tun: 2,
packets_to_peers: 3,
last_error: None,
note: "active".to_owned(),
},
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::OverlayPackets {
name: "home-lan".to_owned(),
packets: vec![geth_overlay::OverlayPacket {
id: "overlay-packet:test".to_owned(),
network: "home-lan".to_owned(),
source_node: "node:peer".to_owned(),
destination_node: "node:local".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
size_bytes: 20,
received_at_ms: 11,
note: "received".to_owned(),
}],
drained: true,
note: "packets".to_owned(),
};
assert_eq!(
decode_response(&encode_response(&response).expect("encode")).expect("decode"),
response
);
let response = ControlResponse::NodeList {
nodes: vec![NodeRecord {
id: geth_types::NodeId::new("node:local"),
@ -2641,6 +2846,63 @@ mod tests {
response
);
let request = OverlayWireRequest::Packet {
peer_card: PeerCard {
node_id: "node:caller".into(),
agent_id: "agent:caller".into(),
endpoints: Vec::new(),
issued_at: geth_types::UnixMillis(1),
signature: geth_discovery::SignatureMetadata {
namespace: "geth.peer-card.v1@geth.local".to_owned(),
signer: "agent:caller".to_owned(),
public_key: "key".to_owned(),
signature: "sig".to_owned(),
},
},
network: "home".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
nonce: "nonce".to_owned(),
bearer_proof: Some(BearerProof {
secret: "bearer:test".into(),
resource: "resource:overlay:home".into(),
capabilities: vec!["overlay.route".into()],
nonce: "nonce".to_owned(),
response: "response".to_owned(),
}),
};
assert_eq!(
decode_overlay_wire_request(&encode_overlay_wire_request(&request).expect("encode"))
.expect("decode"),
request
);
let response = OverlayWireResponse::PacketAccepted {
node_id: "node:peer".to_owned(),
agent_id: "agent:peer".to_owned(),
endpoint_id: "endpoint:peer".to_owned(),
remote_endpoint_id: "endpoint:caller".to_owned(),
packet: Some(Box::new(geth_overlay::OverlayPacket {
id: "overlay-packet:test".to_owned(),
network: "home".to_owned(),
source_node: "node:caller".to_owned(),
destination_node: "node:peer".to_owned(),
packet_base64: "RQAAFAAAQABAAQAAqBYAAawWAAI=".to_owned(),
size_bytes: 20,
received_at_ms: 12,
note: "overlay".to_owned(),
})),
allowed: true,
reason: "direct grant".to_owned(),
evaluated_ops: 1,
nonce: "nonce".to_owned(),
note: "overlay wire".to_owned(),
};
assert_eq!(
decode_overlay_wire_response(&encode_overlay_wire_response(&response).expect("encode"))
.expect("decode"),
response
);
let request = PipeWireRequest::TcpConnect {
peer_card: PeerCard {
node_id: "node:caller".into(),