mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-08 10:56:13 -08:00
Share a named TUN across instances while keeping unnamed devices dedicated. Track member addresses, routes, and MTU, and reject overlapping destinations except the common Magic DNS host route. Dispatch packets between the physical TUN and member tunnels. Keep IPv4 and ordinary IPv6 source translation for mobile wrong-source traffic. Update platform configuration and runtime host integration, with unit and network namespace coverage for routing and lifecycle. Add the packet dependency and keep core CI matrix jobs independent.
283 lines
7.4 KiB
Rust
283 lines
7.4 KiB
Rust
#[cfg(target_os = "linux")]
|
|
mod three_node;
|
|
|
|
#[cfg(all(target_os = "linux", feature = "tun"))]
|
|
mod shared_virtual_nic;
|
|
|
|
mod ipv6_test;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
mod credential_tests;
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[cfg(feature = "upnp")]
|
|
mod upnp_test;
|
|
|
|
use crate::instance::test_instance::TestInstance as Instance;
|
|
use easytier_core::config::PeerId;
|
|
|
|
trait InstanceTestExt {
|
|
fn add_connector_url(&self, url: url::Url);
|
|
|
|
fn peer_id(&self) -> PeerId;
|
|
|
|
fn ring_listener_url(&self) -> url::Url;
|
|
}
|
|
|
|
impl InstanceTestExt for Instance {
|
|
fn add_connector_url(&self, url: url::Url) {
|
|
self.get_core_instance()
|
|
.add_connector(url)
|
|
.expect("test connector URL should be supported");
|
|
}
|
|
|
|
fn peer_id(&self) -> PeerId {
|
|
self.get_core_instance().peer_id()
|
|
}
|
|
|
|
fn ring_listener_url(&self) -> url::Url {
|
|
self.get_core_instance()
|
|
.running_listeners()
|
|
.into_iter()
|
|
.find(|url| url.scheme() == "ring")
|
|
.expect("test instance has no running Ring listener")
|
|
}
|
|
}
|
|
|
|
pub fn set_env_var<K: AsRef<std::ffi::OsStr>, V: AsRef<std::ffi::OsStr>>(key: K, value: V) {
|
|
unsafe { std::env::set_var(key, value) }
|
|
}
|
|
|
|
pub fn remove_env_var<K: AsRef<std::ffi::OsStr>>(key: K) {
|
|
unsafe { std::env::remove_var(key) }
|
|
}
|
|
|
|
pub fn get_guest_veth_name(net_ns: &str) -> &str {
|
|
Box::leak(format!("veth_{}_g", net_ns).into_boxed_str())
|
|
}
|
|
|
|
pub fn get_host_veth_name(net_ns: &str) -> &str {
|
|
Box::leak(format!("veth_{}_h", net_ns).into_boxed_str())
|
|
}
|
|
|
|
pub fn del_netns(name: &str) {
|
|
// del veth host
|
|
let _ = std::process::Command::new("ip")
|
|
.args(["link", "del", get_host_veth_name(name)])
|
|
.output();
|
|
|
|
let _ = std::process::Command::new("ip")
|
|
.args(["netns", "del", name])
|
|
.output();
|
|
}
|
|
|
|
pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) {
|
|
// create netns
|
|
let _ = std::process::Command::new("ip")
|
|
.args(["netns", "add", name])
|
|
.output()
|
|
.unwrap();
|
|
|
|
// set lo up
|
|
let _ = std::process::Command::new("ip")
|
|
.args(["netns", "exec", name, "ip", "link", "set", "lo", "up"])
|
|
.output()
|
|
.unwrap();
|
|
|
|
let _ = std::process::Command::new("ip")
|
|
.args([
|
|
"link",
|
|
"add",
|
|
get_host_veth_name(name),
|
|
"type",
|
|
"veth",
|
|
"peer",
|
|
"name",
|
|
get_guest_veth_name(name),
|
|
])
|
|
.output()
|
|
.unwrap();
|
|
|
|
let _ = std::process::Command::new("ip")
|
|
.args(["link", "set", get_guest_veth_name(name), "netns", name])
|
|
.output()
|
|
.unwrap();
|
|
|
|
let _ = std::process::Command::new("ip")
|
|
.args([
|
|
"netns",
|
|
"exec",
|
|
name,
|
|
"ip",
|
|
"link",
|
|
"set",
|
|
get_guest_veth_name(name),
|
|
"up",
|
|
])
|
|
.output()
|
|
.unwrap();
|
|
|
|
let _ = std::process::Command::new("ip")
|
|
.args(["link", "set", get_host_veth_name(name), "up"])
|
|
.output()
|
|
.unwrap();
|
|
|
|
for ip in [ipv4, ipv6] {
|
|
let _ = std::process::Command::new("ip")
|
|
.args([
|
|
"netns",
|
|
"exec",
|
|
name,
|
|
"ip",
|
|
"addr",
|
|
"add",
|
|
ip,
|
|
"dev",
|
|
get_guest_veth_name(name),
|
|
])
|
|
.output()
|
|
.unwrap();
|
|
}
|
|
}
|
|
|
|
pub fn prepare_bridge(name: &str) {
|
|
// del bridge with brctl
|
|
let _ = std::process::Command::new("brctl")
|
|
.args(["delbr", name])
|
|
.output();
|
|
|
|
// create new br
|
|
let _ = std::process::Command::new("brctl")
|
|
.args(["addbr", name])
|
|
.output();
|
|
}
|
|
|
|
pub fn add_ns_to_bridge(br_name: &str, ns_name: &str) {
|
|
// use brctl to add ns to bridge
|
|
let _ = std::process::Command::new("brctl")
|
|
.args(["addif", br_name, get_host_veth_name(ns_name)])
|
|
.output()
|
|
.unwrap();
|
|
|
|
// set bridge up
|
|
let _ = std::process::Command::new("ip")
|
|
.args(["link", "set", br_name, "up"])
|
|
.output()
|
|
.unwrap();
|
|
}
|
|
|
|
fn check_route(
|
|
ipv4: &str,
|
|
dst_peer_id: PeerId,
|
|
routes: Vec<easytier_proto::core_peer::peer::Route>,
|
|
) {
|
|
let mut found = false;
|
|
for r in routes.iter() {
|
|
if r.ipv4_addr == Some(ipv4.parse().unwrap()) {
|
|
found = true;
|
|
assert_eq!(r.peer_id, dst_peer_id, "{:?}", routes);
|
|
}
|
|
}
|
|
assert!(
|
|
found,
|
|
"routes: {:?}, dst_peer_id: {}, ipv4: {}",
|
|
routes, dst_peer_id, ipv4
|
|
);
|
|
}
|
|
|
|
fn check_route_ex(
|
|
routes: Vec<easytier_proto::core_peer::peer::Route>,
|
|
peer_id: PeerId,
|
|
checker: impl Fn(&easytier_proto::core_peer::peer::Route) -> bool,
|
|
) {
|
|
let mut found = false;
|
|
for r in routes.iter() {
|
|
if r.peer_id == peer_id {
|
|
found = true;
|
|
assert!(checker(r), "{:?}", routes);
|
|
}
|
|
}
|
|
assert!(found, "routes: {:?}, dst_peer_id: {}", routes, peer_id);
|
|
}
|
|
|
|
async fn wait_proxy_route_appear(
|
|
core: &std::sync::Arc<crate::instance::composition::NativeCoreInstance>,
|
|
ipv4: &str,
|
|
dst_peer_id: PeerId,
|
|
proxy_cidr: &str,
|
|
) {
|
|
let now = std::time::Instant::now();
|
|
loop {
|
|
for r in core.route_snapshots().await.iter() {
|
|
if r.proxy_cidrs.contains(&proxy_cidr.to_owned()) {
|
|
assert_eq!(r.peer_id, dst_peer_id);
|
|
assert_eq!(r.ipv4_addr, Some(ipv4.parse().unwrap()));
|
|
return;
|
|
}
|
|
}
|
|
if now.elapsed().as_secs() > 5 {
|
|
panic!("wait proxy route appear timeout");
|
|
}
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
}
|
|
}
|
|
|
|
fn set_link_status(net_ns: &str, up: bool) {
|
|
let ret = std::process::Command::new("ip")
|
|
.args([
|
|
"netns",
|
|
"exec",
|
|
net_ns,
|
|
"ip",
|
|
"link",
|
|
"set",
|
|
get_guest_veth_name(net_ns),
|
|
if up { "up" } else { "down" },
|
|
])
|
|
.output()
|
|
.unwrap();
|
|
tracing::info!("set link status: {:?}, net_ns: {}, up: {}", ret, net_ns, up);
|
|
}
|
|
|
|
pub async fn drop_insts(insts: Vec<Instance>) {
|
|
let mut set = tokio::task::JoinSet::new();
|
|
for mut inst in insts {
|
|
set.spawn(async move {
|
|
inst.clear_resources().await;
|
|
let core = std::sync::Arc::downgrade(&inst.get_core_instance());
|
|
drop(inst);
|
|
let now = std::time::Instant::now();
|
|
while now.elapsed().as_secs() < 5 && core.strong_count() > 0 {
|
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
|
}
|
|
assert_eq!(core.strong_count(), 0, "CoreInstance should be dropped");
|
|
});
|
|
}
|
|
while set.join_next().await.is_some() {}
|
|
}
|
|
|
|
pub async fn ping_test(from_netns: &str, target_ip: &str, payload_size: Option<usize>) -> bool {
|
|
use crate::common::netns::{NetNS, ROOT_NETNS_NAME};
|
|
let _g = NetNS::new(Some(ROOT_NETNS_NAME.to_owned())).guard();
|
|
let code = tokio::process::Command::new("ip")
|
|
.args([
|
|
"netns",
|
|
"exec",
|
|
from_netns,
|
|
"ping",
|
|
"-c",
|
|
"1",
|
|
"-s",
|
|
payload_size.unwrap_or(56).to_string().as_str(),
|
|
"-W",
|
|
"1",
|
|
target_ip.to_string().as_str(),
|
|
])
|
|
.stdout(std::process::Stdio::null())
|
|
.stderr(std::process::Stdio::null())
|
|
.status()
|
|
.await
|
|
.unwrap();
|
|
code.code().unwrap() == 0
|
|
}
|