#![cfg(target_os = "linux")]
#![allow(clippy::similar_names)]
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::Duration;
use ipnet::IpNet;
use zlayer_overlay::config::OverlayConfig;
use zlayer_overlay::transport::OverlayTransport;
use zlayer_overlay::PeerInfo;
fn test_config(
private_key: String,
public_key: String,
listen_port: u16,
overlay_cidr: &str,
uapi_sock_dir: std::path::PathBuf,
) -> OverlayConfig {
OverlayConfig {
local_endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), listen_port),
private_key,
public_key,
overlay_cidr: overlay_cidr.to_string(),
uapi_sock_dir,
..OverlayConfig::default()
}
}
fn dump_contains_allowed_ip_for_peer(dump: &str, peer_pub: &str, cidr: &IpNet) -> bool {
let needle = format!("allowed_ip={cidr}");
let mut in_target_peer = false;
for line in dump.lines() {
if let Some(rest) = line.strip_prefix("public_key=") {
in_target_peer = rest == peer_pub || rest.eq_ignore_ascii_case(peer_pub);
continue;
}
if let Some(rest) = line.strip_prefix("public_key_b64=") {
if rest == peer_pub {
in_target_peer = true;
}
continue;
}
if in_target_peer && line.trim() == needle {
return true;
}
}
false
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[ignore = "requires CAP_NET_ADMIN to create real TUN devices and bind UDP sockets"]
async fn two_node_service_subnets_propagate_via_add_allowed_ip() {
let tmp_a = tempfile::tempdir().expect("tempdir A");
let tmp_b = tempfile::tempdir().expect("tempdir B");
let (priv_a, pub_a) = OverlayTransport::generate_keys()
.await
.expect("generate_keys A");
let (priv_b, pub_b) = OverlayTransport::generate_keys()
.await
.expect("generate_keys B");
let port_a: u16 = 53691;
let port_b: u16 = 53692;
let iface_a = "zl-ta-g".to_string();
let iface_b = "zl-tb-g".to_string();
let cfg_a = test_config(
priv_a,
pub_a.clone(),
port_a,
"10.220.0.1/32",
tmp_a.path().to_path_buf(),
);
let cfg_b = test_config(
priv_b,
pub_b.clone(),
port_b,
"10.220.0.2/32",
tmp_b.path().to_path_buf(),
);
let mut node_a = OverlayTransport::new(cfg_a, iface_a.clone());
let mut node_b = OverlayTransport::new(cfg_b, iface_b.clone());
node_a.create_interface().await.expect("create_interface A");
node_b.create_interface().await.expect("create_interface B");
let peer_a_on_b = PeerInfo::new(
pub_a.clone(),
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port_a),
"10.220.0.1/32",
Duration::from_secs(25),
);
let peer_b_on_a = PeerInfo::new(
pub_b.clone(),
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port_b),
"10.220.0.2/32",
Duration::from_secs(25),
);
node_a
.configure(std::slice::from_ref(&peer_b_on_a))
.await
.expect("configure A peers");
node_b
.configure(std::slice::from_ref(&peer_a_on_b))
.await
.expect("configure B peers");
let subnet_a: IpNet = "10.220.1.0/28".parse().expect("parse subnet_a");
let subnet_b: IpNet = "10.220.2.0/28".parse().expect("parse subnet_b");
node_b
.add_allowed_ip(&pub_a, subnet_a)
.await
.expect("B apply AssignServiceSubnet(svc-1, A)");
node_a
.add_allowed_ip(&pub_b, subnet_b)
.await
.expect("A apply AssignServiceSubnet(svc-2, B)");
let dump_a = node_a.status().await.expect("A status");
let dump_b = node_b.status().await.expect("B status");
let node_a_pub_hex = base64_to_hex(&pub_a).expect("decode pub_a");
let node_b_pub_hex = base64_to_hex(&pub_b).expect("decode pub_b");
assert!(
dump_contains_allowed_ip_for_peer(&dump_a, &node_b_pub_hex, &subnet_b),
"node A's cluster transport should have B's pubkey with svc-2 subnet \
({subnet_b}) in AllowedIPs after the propagated AssignServiceSubnet;\n\
A's UAPI dump:\n{dump_a}"
);
assert!(
dump_contains_allowed_ip_for_peer(&dump_b, &node_a_pub_hex, &subnet_a),
"node B's cluster transport should have A's pubkey with svc-1 subnet \
({subnet_a}) in AllowedIPs after the propagated AssignServiceSubnet;\n\
B's UAPI dump:\n{dump_b}"
);
node_a.shutdown();
node_b.shutdown();
}
fn base64_to_hex(b64: &str) -> Result<String, String> {
use base64::{engine::general_purpose::STANDARD, Engine as _};
let raw = STANDARD
.decode(b64.trim())
.map_err(|e| format!("base64 decode: {e}"))?;
if raw.len() != 32 {
return Err(format!("expected 32-byte key, got {}", raw.len()));
}
Ok(hex::encode(raw))
}