From 1649ec00135e12103e05bf543e0ed313d7e190f8 Mon Sep 17 00:00:00 2001 From: KKRainbow <5665404+KKRainbow@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:09:52 +0800 Subject: [PATCH] ci: parallelize Linux tests and skip HAR packaging on PRs (#2678) --- .config/nextest.toml | 25 + .github/CI.md | 10 +- .github/CI.zh-CN.md | 8 +- .github/workflows/ohos.yml | 53 +- .github/workflows/test.yml | 21 +- easytier/src/tests/credential_tests.rs | 493 +++++++++++----- easytier/src/tests/mod.rs | 97 ++- easytier/src/tests/network.rs | 95 +++ easytier/src/tests/three_node.rs | 785 +++++++++++++++---------- 9 files changed, 1032 insertions(+), 555 deletions(-) create mode 100644 easytier/src/tests/network.rs diff --git a/.config/nextest.toml b/.config/nextest.toml index 208a4d98..91723a3a 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -3,3 +3,28 @@ slow-timeout = { period = "60s", terminate-after = 1 } [profile.default.junit] path = "junit.xml" + +[profile.ci] +test-threads = 32 + +[test-groups.subnet-proxy] +max-threads = 8 + +[[profile.default.overrides]] +filter = 'test(tests::three_node::subnet_proxy_three_node_test)' +test-group = 'subnet-proxy' + +# These tests modify or inspect host-wide interfaces, routes, or BPF state. +# They must exclude all other tests, including the isolated network fixtures. +# Run exclusive tests first so the parallel pool does not repeatedly drain. +[[profile.default.overrides]] +filter = 'test(tests::upnp_test) | test(common::ifcfg::netlink::tests) | test(instance::public_ipv6_provider::platform::tests) | test(instance::dns_server::tests) | test(socket::fake_tcp::netfilter::linux_bpf::tests)' +threads-required = 'num-test-threads' +priority = 100 + +# These assertions require a reply within 100 ms, on the first ping, or a +# minimum measured throughput. Competing tests can delay packet processing. +[[profile.default.overrides]] +filter = 'test(tests::three_node::p2p_only_test) | test(tests::three_node::relay_peer_e2e_encryption) | test(tests::three_node::whitelist_test) | test(relay_bps_limit_test) | test(instance_recv_bps_limit_test)' +threads-required = 'num-test-threads' +priority = 100 diff --git a/.github/CI.md b/.github/CI.md index 123b1aaa..bdc564f6 100644 --- a/.github/CI.md +++ b/.github/CI.md @@ -11,6 +11,12 @@ Test runs CI script tests, fmt, clippy, WASI, Cargo.lock validation, and Linux tests for `easytier`/`easytier-core`, including `three_node`. Full mode also checks each `easytier` feature except `macos-ne`. +Linux tests run on one runner with 32 concurrent nextest processes. Network +integration tests use separate namespaces and bridges for each test. Subnet +proxy cases run at most eight at a time. Tests that use host-wide network state, +measure bandwidth, or make time-sensitive connectivity assertions run first, +exclusively, before the parallel tests. + These four workflows skip build/test jobs for draft PRs. Path and duplicate content checks can skip ordinary builds and Rust jobs. The result jobs are `core-result`, `gui-result`, `mobile-result`, and `test`. @@ -51,6 +57,8 @@ It downloads their artifacts and creates a draft release. ## Other workflows -- OHOS runs independently; relevant PRs can still upload HAR artifacts. +- OHOS runs independently. Relevant PRs run bridge tests and check the bindings + for the host and ARM64 OHOS. Non-PR runs build and upload HAR artifacts; + publishing and downstream builds retain their existing main/manual conditions. - Go, JavaScript Hosts, and Nix use their own path filters. - Docker is manual; it downloads a specified Core run's artifacts and pushes images. diff --git a/.github/CI.zh-CN.md b/.github/CI.zh-CN.md index 263560f6..4809f7ef 100644 --- a/.github/CI.zh-CN.md +++ b/.github/CI.zh-CN.md @@ -11,6 +11,11 @@ Test 运行 CI 脚本测试、fmt、clippy、WASI、Cargo.lock 检查,以及 `easytier`/`easytier-core` 的 Linux 测试(含 `three_node`)。全量模式还逐个 检查 `easytier` 的 feature,排除 `macos-ne`。 +Linux 测试使用一个 runner,nextest 总并发度为 32。网络集成测试为每个测试 +创建独立的 namespace 和网桥。子网代理用例最多同时运行八个;带宽测试、 +使用宿主机全局网络状态的测试和时间敏感的连通性测试优先独占 +执行,完成后再运行并发测试。 + 这四个 workflow 的构建和测试任务跳过 draft PR。普通构建和 Rust 任务可因 无关路径或重复内容而跳过。汇总检查为 `core-result`、`gui-result`、 `mobile-result` 和 `test`。 @@ -49,6 +54,7 @@ release。 ## 其他 workflow -- OHOS 独立运行,相关 PR 仍可上传 HAR。 +- OHOS 独立运行。相关 PR 运行桥接单测,并检查宿主平台和 ARM64 OHOS 的绑定。 + 非 PR 构建生成并上传 HAR;发布和下游构建仍按原有主线/手动触发条件执行。 - Go、JavaScript Hosts、Nix 使用各自的路径过滤。 - Docker 手动触发,下载指定 Core 构建的产物并推送镜像。 diff --git a/.github/workflows/ohos.yml b/.github/workflows/ohos.yml index 3dfb37e7..b003cd8f 100644 --- a/.github/workflows/ohos.yml +++ b/.github/workflows/ohos.yml @@ -80,29 +80,62 @@ jobs: uses: ErBWs/setup-ohos@v1 - name: Install ohrs + if: github.event_name != 'pull_request' uses: taiki-e/install-action@v2 with: tool: ohrs - - name: Build HAR - id: package - env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + - name: Prepare OHOS tools run: | set -euo pipefail sudo apt-get install -qqy \ pkg-config curl libgl1-mesa-dev expect llvm clang lldb lld rustup component add rustfmt + + sudo mkdir -p "$OHOS_NDK_HOME/native/llvm" + sudo tee "$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh" >/dev/null <<'EOF' + #!/bin/sh + exec "$OHOS_NDK_HOME/native/llvm/bin/clang" \ + -target aarch64-linux-ohos \ + --sysroot="$OHOS_NDK_HOME/native/sysroot" \ + -D__MUSL__ "$@" + EOF + sudo chmod +x \ + "$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh" + + - name: Check formatting + run: | cargo fmt --all --manifest-path \ easytier-contrib/easytier-ohrs/Cargo.toml -- --check + + - name: Test OHOS bridge + run: | cargo test --locked --manifest-path \ easytier-contrib/easytier-ohrs/Cargo.toml \ -p easytier-ohos-core -p easytier-ohos-features \ --lib -- --test-threads=1 + + - name: Check OHOS bindings + run: | cargo check --locked --manifest-path \ easytier-contrib/easytier-ohrs/Cargo.toml \ -p easytier-ohrs --tests + - name: Check OHOS target + if: github.event_name == 'pull_request' + working-directory: easytier-contrib/easytier-ohrs + run: | + source env.sh + cargo check --locked -p easytier-ohrs --tests \ + --target aarch64-unknown-linux-ohos + + - name: Build HAR + if: github.event_name != 'pull_request' + id: package + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + set -euo pipefail cargo_version=$(cargo metadata --format-version 1 --no-deps \ --manifest-path easytier/Cargo.toml | jq -r '.packages[0].version') last_tag=$(git describe --tags --abbrev=0 HEAD 2>/dev/null || true) @@ -149,17 +182,6 @@ jobs: echo } > "$package_dir/CHANGELOG.md" - sudo mkdir -p "$OHOS_NDK_HOME/native/llvm" - sudo tee "$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh" >/dev/null <<'EOF' - #!/bin/sh - exec "$OHOS_NDK_HOME/native/llvm/bin/clang" \ - -target aarch64-linux-ohos \ - --sysroot="$OHOS_NDK_HOME/native/sysroot" \ - -D__MUSL__ "$@" - EOF - sudo chmod +x \ - "$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh" - cd easytier-contrib/easytier-ohrs source env.sh ohrs build --release --arch aarch @@ -167,6 +189,7 @@ jobs: mv package.har "$package_name.har" - name: Upload HAR + if: github.event_name != 'pull_request' uses: actions/upload-artifact@v5 with: name: ${{ steps.package.outputs.name }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ecee55ae..bbec4e13 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,6 +43,7 @@ jobs: env: RELEVANT_PATHS: >- ["Cargo.toml", "Cargo.lock", "rust-toolchain.toml", ".cargo/**", + ".config/nextest.toml", "pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "easytier/**", "easytier-core/**", "easytier-proto/**", "easytier-rpc-build/**", "easytier-web/**", "easytier-gui/src-tauri/**", @@ -166,23 +167,11 @@ jobs: path: tests.tar.zst retention-days: 1 - test_matrix: - name: Test (${{ matrix.name }}) + run-tests: + name: Run tests runs-on: ubuntu-latest needs: [ pre_job, pre-test ] if: needs.pre_job.outputs.should_skip != 'true' - strategy: - fail-fast: false - matrix: - include: - - name: "easytier" - opts: "-E 'not test(tests::three_node)' --test-threads 1 --no-fail-fast" - - - name: "three_node" - opts: "-E 'test(tests::three_node) and not test(subnet_proxy_three_node_test)' --test-threads 1 --no-fail-fast" - - - name: "three_node::subnet_proxy_three_node_test" - opts: "-E 'test(subnet_proxy_three_node_test)' --test-threads 1 --no-fail-fast" steps: - uses: actions/checkout@v5 @@ -217,11 +206,11 @@ jobs: run: | sudo prlimit --pid $$ --nofile=1048576:1048576 sudo -E env "PATH=$PATH" EASYTIER_LINUX_BPF_INTEGRATION=required \ - cargo nextest run --archive-file tests.tar.zst ${{ matrix.opts }} + cargo nextest run --archive-file tests.tar.zst --profile ci --no-fail-fast test: runs-on: ubuntu-latest - needs: [ pre_job, ci-scripts, check, pre-test, test_matrix ] + needs: [ pre_job, ci-scripts, check, pre-test, run-tests ] if: ${{ always() }} steps: - name: Mark result as failed diff --git a/easytier/src/tests/credential_tests.rs b/easytier/src/tests/credential_tests.rs index 3b86efa0..d1d0e6c3 100644 --- a/easytier/src/tests/credential_tests.rs +++ b/easytier/src/tests/credential_tests.rs @@ -36,9 +36,7 @@ use defguard_wireguard_rs::key::Key; #[cfg(feature = "wireguard")] use easytier_core::gateway::vpn_portal::PortalClientState; -use super::{ - InstanceTestExt as _, add_ns_to_bridge, create_netns, del_netns, drop_insts, ping_test, -}; +use super::{InstanceTestExt as _, drop_insts, network::TestNetwork, ping_test}; use rstest::rstest; @@ -218,62 +216,35 @@ async fn disconnect_connector(inst: &Instance, url: &url::Url, peer_id: u32) { /// br_a (10.1.1.0/24): ns_adm (10.1.1.1), ns_c1 (10.1.1.2), ns_c2 (10.1.1.3), ns_c3 (10.1.1.4), ns_c4 (10.1.1.5) /// br_b (10.1.2.0/24): ns_adm2 (10.1.2.1) - for multi-admin tests /// Note: Using short names (max 15 chars for veth interfaces) -pub fn prepare_credential_network() { - // Clean up any existing namespaces - for ns in ["ns_adm", "ns_c1", "ns_c2", "ns_c3", "ns_c4", "ns_adm2"] { - del_netns(ns); - } - +fn prepare_credential_network() -> TestNetwork { + let mut network = TestNetwork::new(); // Create bridge br_a for admin and credentials - let _ = std::process::Command::new("ip") - .args(["link", "del", "br_a"]) - .output(); - let _ = std::process::Command::new("brctl") - .args(["delbr", "br_a"]) - .output(); - let _ = std::process::Command::new("brctl") - .args(["addbr", "br_a"]) - .output() - .expect("Failed to create br_a"); - let _ = std::process::Command::new("ip") - .args(["link", "set", "br_a", "up"]) - .output(); + network.create_bridge("br_a"); // Create namespaces and add to bridge - create_netns("ns_adm", "10.1.1.1/24", "fd11::1/64"); - add_ns_to_bridge("br_a", "ns_adm"); + network.create_namespace("ns_adm", "10.1.1.1/24", "fd11::1/64"); + network.add_to_bridge("br_a", "ns_adm"); - create_netns("ns_c1", "10.1.1.2/24", "fd11::2/64"); - add_ns_to_bridge("br_a", "ns_c1"); + network.create_namespace("ns_c1", "10.1.1.2/24", "fd11::2/64"); + network.add_to_bridge("br_a", "ns_c1"); - create_netns("ns_c2", "10.1.1.3/24", "fd11::3/64"); - add_ns_to_bridge("br_a", "ns_c2"); + network.create_namespace("ns_c2", "10.1.1.3/24", "fd11::3/64"); + network.add_to_bridge("br_a", "ns_c2"); // Create ns_c3 for relay tests (needs 4 nodes) - create_netns("ns_c3", "10.1.1.4/24", "fd11::4/64"); - add_ns_to_bridge("br_a", "ns_c3"); + network.create_namespace("ns_c3", "10.1.1.4/24", "fd11::4/64"); + network.add_to_bridge("br_a", "ns_c3"); // Create ns_c4 for multi-admin credential tests (needs 5 nodes) - create_netns("ns_c4", "10.1.1.5/24", "fd11::5/64"); - add_ns_to_bridge("br_a", "ns_c4"); + network.create_namespace("ns_c4", "10.1.1.5/24", "fd11::5/64"); + network.add_to_bridge("br_a", "ns_c4"); // Create bridge br_b for second admin (multi-admin tests) - let _ = std::process::Command::new("ip") - .args(["link", "del", "br_b"]) - .output(); - let _ = std::process::Command::new("brctl") - .args(["delbr", "br_b"]) - .output(); - let _ = std::process::Command::new("brctl") - .args(["addbr", "br_b"]) - .output() - .expect("Failed to create br_b"); - let _ = std::process::Command::new("ip") - .args(["link", "set", "br_b", "up"]) - .output(); + network.create_bridge("br_b"); - create_netns("ns_adm2", "10.1.2.1/24", "fd12::1/64"); - add_ns_to_bridge("br_b", "ns_adm2"); + network.create_namespace("ns_adm2", "10.1.2.1/24", "fd12::1/64"); + network.add_to_bridge("br_b", "ns_adm2"); + network } fn credential_private_key_from_secret(credential_secret: &str) -> x25519_dalek::StaticSecret { @@ -411,11 +382,11 @@ fn create_shared_config( config } -fn create_public_server_config() -> TomlConfigLoader { +fn create_public_server_config(network: &TestNetwork) -> TomlConfigLoader { let config = TomlConfigLoader::default(); config.set_inst_name(PUBLIC_SERVER_NETWORK_NAME.to_string()); config.set_hostname(Some("public-server".to_string())); - config.set_netns(Some("ns_adm".to_string())); + config.set_netns(Some(network.namespace("ns_adm").to_string())); config.set_listeners(vec!["udp://0.0.0.0:11010".parse().unwrap()]); config.set_network_identity(NetworkIdentity::new( PUBLIC_SERVER_NETWORK_NAME.to_string(), @@ -433,11 +404,11 @@ fn create_public_server_config() -> TomlConfigLoader { config } -fn create_need_p2p_admin_config(listener_scheme: &str) -> TomlConfigLoader { +fn create_need_p2p_admin_config(network: &TestNetwork, listener_scheme: &str) -> TomlConfigLoader { let config = TomlConfigLoader::default(); config.set_inst_name(NEED_P2P_ADMIN_NETWORK_NAME.to_string()); config.set_hostname(Some("need-p2p-admin".to_string())); - config.set_netns(Some("ns_c3".to_string())); + config.set_netns(Some(network.namespace("ns_c3").to_string())); config.set_listeners(vec![ format!("{listener_scheme}://0.0.0.0:0").parse().unwrap(), ]); @@ -583,15 +554,17 @@ async fn wait_foreign_network_count(inst: &Instance, expected: usize, timeout: D async fn credential_peers_p2p_to_need_p2p_admin_through_public_server( #[case] admin_listener_scheme: &str, ) { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); - let mut public_server_inst = - Instance::new_with_process_runtime(create_public_server_config(), process_runtime.clone()); + let mut public_server_inst = Instance::new_with_process_runtime( + create_public_server_config(&network), + process_runtime.clone(), + ); public_server_inst.run().await.unwrap(); let mut admin_inst = Instance::new_with_process_runtime( - create_need_p2p_admin_config(admin_listener_scheme), + create_need_p2p_admin_config(&network, admin_listener_scheme), process_runtime.clone(), ); admin_inst.run().await.unwrap(); @@ -637,7 +610,7 @@ async fn credential_peers_p2p_to_need_p2p_admin_through_public_server( &credential_a_secret, "credential-peer-a", "credential-a", - "ns_c1", + network.namespace("ns_c1"), "10.154.0.1", "fd00::1/64", 11030, @@ -651,7 +624,7 @@ async fn credential_peers_p2p_to_need_p2p_admin_through_public_server( &credential_b_secret, "credential-peer-b", "credential-b", - "ns_c2", + network.namespace("ns_c2"), "10.154.0.2", "fd00::2/64", 11040, @@ -732,10 +705,10 @@ async fn credential_peers_p2p_to_need_p2p_admin_through_public_server( #[tokio::test] #[serial_test::serial] async fn credential_peer_reconnects_to_admin_with_portal_client_online() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); - let public_server_config = create_public_server_config(); + let public_server_config = create_public_server_config(&network); let mut public_server_flags = public_server_config.get_flags(); public_server_flags.disable_relay_data = true; public_server_config.set_flags(public_server_flags); @@ -743,7 +716,7 @@ async fn credential_peer_reconnects_to_admin_with_portal_client_online() { Instance::new_with_process_runtime(public_server_config, process_runtime.clone()); public_server_inst.run().await.unwrap(); - let admin_config = create_need_p2p_admin_config("udp"); + let admin_config = create_need_p2p_admin_config(&network, "udp"); admin_config.set_vpn_portal_config(VpnPortalConfig { enabled: None, wireguard_listen: "0.0.0.0:22121".parse().unwrap(), @@ -777,7 +750,7 @@ async fn credential_peer_reconnects_to_admin_with_portal_client_online() { &credential_secret, "portal-credential-peer", "portal-credential-peer", - "ns_c1", + network.namespace("ns_c1"), "10.154.0.1", "fd00::1/64", 11030, @@ -822,7 +795,7 @@ async fn credential_peer_reconnects_to_admin_with_portal_client_online() { let (server_public, client_private) = test_wireguard_keys(&portal_config, "portal-client").unwrap(); { - let net_ns = crate::common::netns::NetNS::new(Some("ns_c4".to_owned())); + let net_ns = crate::common::netns::NetNS::new(Some(network.namespace("ns_c4").to_owned())); let _guard = net_ns.guard(); run_wireguard_client( &wireguard_ifname("wg0"), @@ -839,7 +812,7 @@ async fn credential_peer_reconnects_to_admin_with_portal_client_online() { forwarded_data_packets_for_network(&public_server_inst, NEED_P2P_ADMIN_NETWORK_NAME); wait_for_condition( || async { - ping_test("ns_c4", "10.154.0.1", None).await; + ping_test(network.namespace("ns_c4"), "10.154.0.1", None).await; admin_inst .get_core_instance() .vpn_portal_info() @@ -901,9 +874,15 @@ async fn credential_peer_reconnects_to_admin_with_portal_client_online() { ) .await; - wait_ping_reachability("ns_c1", "10.154.0.10", true, Duration::from_secs(10)).await; + wait_ping_reachability( + network.namespace("ns_c1"), + "10.154.0.10", + true, + Duration::from_secs(10), + ) + .await; for _ in 0..3 { - assert!(ping_test("ns_c1", "10.154.0.10", None).await); + assert!(ping_test(network.namespace("ns_c1"), "10.154.0.10", None).await); } assert_eq!( forwarded_data_packets_for_network(&public_server_inst, NEED_P2P_ADMIN_NETWORK_NAME), @@ -975,6 +954,7 @@ async fn wait_route_presence_on_admins( } async fn assert_shared_visibility_stable( + network: &TestNetwork, admin_a_inst: &Instance, admin_c_inst: &Instance, peer_id: u32, @@ -1005,8 +985,10 @@ async fn assert_shared_visibility_stable( ); } - let ping_from_admin_a = ping_test("ns_adm", peer_ip, None).await; - let ping_from_admin_c = ping_test("ns_c3", peer_ip, None).await; + let (ping_from_admin_a, ping_from_admin_c) = tokio::join!( + ping_test(network.namespace("ns_adm"), peer_ip, None), + ping_test(network.namespace("ns_c3"), peer_ip, None), + ); if should_exist { assert!(ping_from_admin_a, "admin_a should reach {}", label); assert!(ping_from_admin_c, "admin_c should reach {}", label); @@ -1159,11 +1141,16 @@ async fn wait_stable_failover_visibility_on_admins( #[tokio::test] #[serial_test::serial] async fn credential_basic_connectivity(#[case] pin_admin: bool) { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); // Create admin node - let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_config = create_admin_config( + "admin", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let admin_public_key = admin_config .get_secure_mode() .and_then(|config| config.local_public_key) @@ -1175,7 +1162,7 @@ async fn credential_basic_connectivity(#[case] pin_admin: bool) { let cred_config = create_credential_config( &admin_inst, "cred", - Some("ns_c1"), + Some(network.namespace("ns_c1")), "10.144.144.2", "fd00::2/64", ) @@ -1241,13 +1228,13 @@ async fn credential_basic_connectivity(#[case] pin_admin: bool) { // Verify connectivity wait_for_condition( - || async { ping_test("ns_adm", "10.144.144.2", None).await }, + || async { ping_test(network.namespace("ns_adm"), "10.144.144.2", None).await }, Duration::from_secs(10), ) .await; wait_for_condition( - || async { ping_test("ns_c1", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("ns_c1"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; @@ -1259,10 +1246,15 @@ async fn credential_basic_connectivity(#[case] pin_admin: bool) { #[tokio::test] #[serial_test::serial] async fn credential_rejects_incorrect_admin_pin() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); - let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_config = create_admin_config( + "admin", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let admin_public_key = admin_config .get_secure_mode() .and_then(|config| config.local_public_key) @@ -1273,7 +1265,7 @@ async fn credential_rejects_incorrect_admin_pin() { let cred_config = create_credential_config( &admin_inst, "cred", - Some("ns_c1"), + Some(network.namespace("ns_c1")), "10.144.144.2", "fd00::2/64", ) @@ -1326,11 +1318,16 @@ async fn credential_rejects_incorrect_admin_pin() { #[tokio::test] #[serial_test::serial] async fn prefer_peer_relay_single_admin(#[case] allow_relay: bool) { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); // Create admin node - let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_config = create_admin_config( + "admin", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); admin_inst.run().await.unwrap(); @@ -1376,7 +1373,7 @@ async fn prefer_peer_relay_single_admin(#[case] allow_relay: bool) { let private = x25519_dalek::StaticSecret::from(privkey_bytes); let config = TomlConfigLoader::default(); config.set_inst_name("cred_a".to_string()); - config.set_netns(Some("ns_c1".to_string())); + config.set_netns(Some(network.namespace("ns_c1").to_string())); config.set_ipv4(Some("10.144.144.2".parse().unwrap())); config.set_ipv6(Some("fd00::2/64".parse().unwrap())); config.set_network_identity(NetworkIdentity::new_credential( @@ -1405,7 +1402,7 @@ async fn prefer_peer_relay_single_admin(#[case] allow_relay: bool) { let private = x25519_dalek::StaticSecret::from(privkey_bytes); let config = TomlConfigLoader::default(); config.set_inst_name("cred_b".to_string()); - config.set_netns(Some("ns_c2".to_string())); + config.set_netns(Some(network.namespace("ns_c2").to_string())); config.set_ipv4(Some("10.144.144.3".parse().unwrap())); config.set_ipv6(Some("fd00::3/64".parse().unwrap())); config.set_network_identity(NetworkIdentity::new_credential( @@ -1434,7 +1431,7 @@ async fn prefer_peer_relay_single_admin(#[case] allow_relay: bool) { let private = x25519_dalek::StaticSecret::from(privkey_bytes); let config = TomlConfigLoader::default(); config.set_inst_name("cred_c".to_string()); - config.set_netns(Some("ns_c3".to_string())); + config.set_netns(Some(network.namespace("ns_c3").to_string())); config.set_ipv4(Some("10.144.144.4".parse().unwrap())); config.set_ipv6(Some("fd00::4/64".parse().unwrap())); // C has listener so A and B can connect to it @@ -1591,7 +1588,7 @@ async fn prefer_peer_relay_single_admin(#[case] allow_relay: bool) { (&admin_inst, &cred_c_inst) }; assert_ping_forwarded_by( - "ns_c1", + network.namespace("ns_c1"), "10.144.144.3", expected_forwarder, &[forbidden_forwarder], @@ -1618,17 +1615,25 @@ async fn prefer_peer_relay_single_admin(#[case] allow_relay: bool) { #[tokio::test] #[serial_test::serial] async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); - let admin_a_config = - create_admin_config("admin_a", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_a_config = create_admin_config( + "admin_a", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_a_inst = Instance::new_with_process_runtime(admin_a_config, process_runtime.clone()); admin_a_inst.run().await.unwrap(); - let admin_b_config = - create_admin_config("admin_b", Some("ns_c3"), "10.144.144.4", "fd00::4/64"); + let admin_b_config = create_admin_config( + "admin_b", + Some(network.namespace("ns_c3")), + "10.144.144.4", + "fd00::4/64", + ); let mut admin_b_inst = Instance::new_with_process_runtime(admin_b_config, process_runtime.clone()); admin_b_inst.run().await.unwrap(); @@ -1681,7 +1686,7 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { network_name.clone(), &relay_secret, "relay", - Some("ns_c1"), + Some(network.namespace("ns_c1")), "10.144.144.2", "fd00::2/64", ); @@ -1692,7 +1697,7 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { network_name.clone(), &target_secret, "target", - Some("ns_c2"), + Some(network.namespace("ns_c2")), "10.144.144.3", "fd00::3/64", ); @@ -1702,7 +1707,7 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { network_name, &source_secret, "source", - Some("ns_c4"), + Some(network.namespace("ns_c4")), "10.144.144.5", "fd00::5/64", ); @@ -1782,7 +1787,13 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { "source uses an admin direct target edge", ) .await; - wait_ping_reachability("ns_c4", "10.144.144.3", true, Duration::from_secs(10)).await; + wait_ping_reachability( + network.namespace("ns_c4"), + "10.144.144.3", + true, + Duration::from_secs(10), + ) + .await; for admin in [&admin_a_inst, &admin_b_inst] { set_prefer_peer_relay(admin, true).await; @@ -1838,7 +1849,7 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { ) .await; assert_ping_forwarded_by( - "ns_c4", + network.namespace("ns_c4"), "10.144.144.3", &relay_inst, &[&admin_a_inst, &admin_b_inst], @@ -1880,7 +1891,13 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { "dual-admin return path remains on relay after one admin stops", ) .await; - assert_ping_forwarded_by("ns_c4", "10.144.144.3", &relay_inst, &[&surviving_admin]).await; + assert_ping_forwarded_by( + network.namespace("ns_c4"), + "10.144.144.3", + &relay_inst, + &[&surviving_admin], + ) + .await; wait_route_cost( &surviving_admin, target_peer_id, @@ -1922,7 +1939,13 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { "relay edge removal restores the surviving admin return path", ) .await; - assert_ping_forwarded_by("ns_c4", "10.144.144.3", &surviving_admin, &[&relay_inst]).await; + assert_ping_forwarded_by( + network.namespace("ns_c4"), + "10.144.144.3", + &surviving_admin, + &[&relay_inst], + ) + .await; target_inst.add_connector_url(relay_listener_url.clone()); wait_direct_peer( @@ -1955,7 +1978,13 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { "relay edge restoration restores the Peer Relay return path", ) .await; - assert_ping_forwarded_by("ns_c4", "10.144.144.3", &relay_inst, &[&surviving_admin]).await; + assert_ping_forwarded_by( + network.namespace("ns_c4"), + "10.144.144.3", + &relay_inst, + &[&surviving_admin], + ) + .await; drop_insts(vec![surviving_admin, relay_inst, target_inst, source_inst]).await; } @@ -1966,11 +1995,16 @@ async fn prefer_peer_relay_uses_forwarded_coverage_across_two_admins() { #[tokio::test] #[serial_test::serial] async fn credential_two_credentials_communicate_tcp() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); // Create admin node - let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_config = create_admin_config( + "admin", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); admin_inst.run().await.unwrap(); @@ -1978,7 +2012,7 @@ async fn credential_two_credentials_communicate_tcp() { let cred1_config = create_credential_config( &admin_inst, "cred1", - Some("ns_c1"), + Some(network.namespace("ns_c1")), "10.144.144.2", "fd00::2/64", ) @@ -1990,7 +2024,7 @@ async fn credential_two_credentials_communicate_tcp() { let cred2_config = create_credential_config( &admin_inst, "cred2", - Some("ns_c2"), + Some(network.namespace("ns_c2")), "10.144.144.3", "fd00::3/64", ) @@ -2018,13 +2052,13 @@ async fn credential_two_credentials_communicate_tcp() { // Verify admin can ping both credentials wait_for_condition( - || async { ping_test("ns_adm", "10.144.144.2", None).await }, + || async { ping_test(network.namespace("ns_adm"), "10.144.144.2", None).await }, Duration::from_secs(10), ) .await; wait_for_condition( - || async { ping_test("ns_adm", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("ns_adm"), "10.144.144.3", None).await }, Duration::from_secs(10), ) .await; @@ -2038,11 +2072,16 @@ async fn credential_two_credentials_communicate_tcp() { #[tokio::test] #[serial_test::serial] async fn credential_revocation_propagates() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); // Create admin on ns_adm (10.1.1.1) - let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_config = create_admin_config( + "admin", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); admin_inst.run().await.unwrap(); @@ -2068,7 +2107,7 @@ async fn credential_revocation_propagates() { let config = TomlConfigLoader::default(); config.set_inst_name("cred".to_string()); - config.set_netns(Some("ns_c1".to_string())); + config.set_netns(Some(network.namespace("ns_c1").to_string())); config.set_ipv4(Some("10.144.144.2".parse().unwrap())); config.set_ipv6(Some("fd00::2/64".parse().unwrap())); config.set_listeners(vec![]); @@ -2107,7 +2146,7 @@ async fn credential_revocation_propagates() { // Verify connectivity before revocation wait_for_condition( - || async { ping_test("ns_adm", "10.144.144.2", None).await }, + || async { ping_test(network.namespace("ns_adm"), "10.144.144.2", None).await }, Duration::from_secs(10), ) .await; @@ -2141,13 +2180,13 @@ async fn credential_revocation_propagates() { .await; wait_for_condition( - || async { !ping_test("ns_adm", "10.144.144.2", None).await }, + || async { !ping_test(network.namespace("ns_adm"), "10.144.144.2", None).await }, Duration::from_secs(10), ) .await; wait_for_condition( - || async { !ping_test("ns_c1", "10.144.144.1", None).await }, + || async { !ping_test(network.namespace("ns_c1"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; @@ -2159,10 +2198,15 @@ async fn credential_revocation_propagates() { #[tokio::test] #[serial_test::serial] async fn credential_non_reusable_allows_only_one_peer() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); - let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_config = create_admin_config( + "admin", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); admin_inst.run().await.unwrap(); @@ -2186,7 +2230,7 @@ async fn credential_non_reusable_allows_only_one_peer() { network_name.clone(), &cred_secret, "cred1_single", - Some("ns_c1"), + Some(network.namespace("ns_c1")), "10.144.144.2", "fd00::2/64", ); @@ -2194,7 +2238,7 @@ async fn credential_non_reusable_allows_only_one_peer() { network_name, &cred_secret, "cred2_single", - Some("ns_c2"), + Some(network.namespace("ns_c2")), "10.144.144.3", "fd00::3/64", ); @@ -2222,7 +2266,13 @@ async fn credential_non_reusable_allows_only_one_peer() { Duration::from_secs(10), ) .await; - wait_ping_reachability("ns_adm", "10.144.144.2", true, Duration::from_secs(10)).await; + wait_ping_reachability( + network.namespace("ns_adm"), + "10.144.144.2", + true, + Duration::from_secs(10), + ) + .await; let mut cred2_inst = Some(Instance::new_with_process_runtime( cred2_config, @@ -2274,7 +2324,7 @@ async fn credential_non_reusable_allows_only_one_peer() { admin_routes.iter().map(|r| r.peer_id).collect::>() ); assert!( - ping_test("ns_adm", winner_ip, None).await, + ping_test(network.namespace("ns_adm"), winner_ip, None).await, "admin should still reach the winning credential peer" ); tokio::time::sleep(Duration::from_secs(1)).await; @@ -2288,7 +2338,7 @@ async fn credential_non_reusable_allows_only_one_peer() { admin_routes.iter().map(|r| r.peer_id).collect::>() ); assert!( - !ping_test("ns_adm", loser_ip, None).await, + !ping_test(network.namespace("ns_adm"), loser_ip, None).await, "admin should not reach the losing credential peer" ); tokio::time::sleep(Duration::from_secs(1)).await; @@ -2305,7 +2355,13 @@ async fn credential_non_reusable_allows_only_one_peer() { Duration::from_secs(20), ) .await; - wait_ping_reachability("ns_adm", loser_ip, true, Duration::from_secs(20)).await; + wait_ping_reachability( + network.namespace("ns_adm"), + loser_ip, + true, + Duration::from_secs(20), + ) + .await; drop_insts(vec![admin_inst, loser_inst.take().unwrap()]).await; } @@ -2316,11 +2372,16 @@ async fn credential_non_reusable_allows_only_one_peer() { #[tokio::test] #[serial_test::serial] async fn credential_unknown_rejected() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); // Create admin node - let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_config = create_admin_config( + "admin", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); admin_inst.run().await.unwrap(); @@ -2329,7 +2390,7 @@ async fn credential_unknown_rejected() { let cred_config = { let config = TomlConfigLoader::default(); config.set_inst_name("cred".to_string()); - config.set_netns(Some("ns_c1".to_string())); + config.set_netns(Some(network.namespace("ns_c1").to_string())); config.set_ipv4(Some("10.144.144.2".parse().unwrap())); config.set_ipv6(Some("fd00::2/64".parse().unwrap())); config.set_listeners(vec![]); @@ -2363,7 +2424,7 @@ async fn credential_unknown_rejected() { ); // Verify no connectivity - let ping_result = ping_test("ns_adm", "10.144.144.2", None).await; + let ping_result = ping_test(network.namespace("ns_adm"), "10.144.144.2", None).await; assert!( !ping_result, "Should NOT be able to ping unknown credential node" @@ -2379,23 +2440,35 @@ async fn credential_unknown_rejected() { #[tokio::test] #[serial_test::serial] async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revoke: bool) { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); - let admin_a_config = - create_admin_config("admin_a", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_a_config = create_admin_config( + "admin_a", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_a_inst = Instance::new_with_process_runtime(admin_a_config, process_runtime.clone()); admin_a_inst.run().await.unwrap(); - let shared_b_config = - create_shared_config("shared_b", Some("ns_c1"), "10.144.144.2", "fd00::2/64"); + let shared_b_config = create_shared_config( + "shared_b", + Some(network.namespace("ns_c1")), + "10.144.144.2", + "fd00::2/64", + ); let mut shared_b_inst = Instance::new_with_process_runtime(shared_b_config, process_runtime.clone()); shared_b_inst.run().await.unwrap(); - let admin_c_config = - create_admin_config("admin_c", Some("ns_c3"), "10.144.144.4", "fd00::4/64"); + let admin_c_config = create_admin_config( + "admin_c", + Some(network.namespace("ns_c3")), + "10.144.144.4", + "fd00::4/64", + ); let mut admin_c_inst = Instance::new_with_process_runtime(admin_c_config, process_runtime.clone()); admin_c_inst.run().await.unwrap(); @@ -2419,7 +2492,7 @@ async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revo let (config, cred_id) = create_generated_credential_config( &admin_a_inst, "cred_d", - Some("ns_c2"), + Some(network.namespace("ns_c2")), "10.144.144.5", "fd00::5/64", ) @@ -2434,7 +2507,7 @@ async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revo .network_name .clone(), "unknown_d", - Some("ns_c2"), + Some(network.namespace("ns_c2")), "10.144.144.5", "fd00::5/64", ), @@ -2460,8 +2533,20 @@ async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revo Duration::from_secs(30), ) .await; - wait_ping_reachability("ns_adm", "10.144.144.5", true, Duration::from_secs(20)).await; - wait_ping_reachability("ns_c3", "10.144.144.5", true, Duration::from_secs(20)).await; + wait_ping_reachability( + network.namespace("ns_adm"), + "10.144.144.5", + true, + Duration::from_secs(20), + ) + .await; + wait_ping_reachability( + network.namespace("ns_c3"), + "10.144.144.5", + true, + Duration::from_secs(20), + ) + .await; assert!( admin_a_inst @@ -2482,12 +2567,25 @@ async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revo Duration::from_secs(30), ) .await; - wait_ping_reachability("ns_adm", "10.144.144.5", false, Duration::from_secs(5)).await; - wait_ping_reachability("ns_c3", "10.144.144.5", false, Duration::from_secs(5)).await; + wait_ping_reachability( + network.namespace("ns_adm"), + "10.144.144.5", + false, + Duration::from_secs(5), + ) + .await; + wait_ping_reachability( + network.namespace("ns_c3"), + "10.144.144.5", + false, + Duration::from_secs(5), + ) + .await; unknown_inst.add_connector_url("tcp://10.1.1.2:11010".parse().unwrap()); assert_shared_visibility_stable( + &network, &admin_a_inst, &admin_c_inst, unknown_peer_id, @@ -2498,6 +2596,7 @@ async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revo .await; } else { assert_shared_visibility_stable( + &network, &admin_a_inst, &admin_c_inst, unknown_peer_id, @@ -2525,30 +2624,42 @@ async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revo async fn credential_admin_shared_admin_credential_connectivity( #[values(true, false)] connect_to_admin: bool, ) { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); // Keep traffic on the shared/admin relay path; direct P2P would bypass it. // 10.1.1.1 - let admin_a_config = - create_admin_config("admin_a", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_a_config = create_admin_config( + "admin_a", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); disable_p2p(&admin_a_config); let mut admin_a_inst = Instance::new_with_process_runtime(admin_a_config, process_runtime.clone()); admin_a_inst.run().await.unwrap(); // 10.1.1.2 - let shared_b_config = - create_shared_config("shared_b", Some("ns_c1"), "10.144.144.2", "fd00::2/64"); + let shared_b_config = create_shared_config( + "shared_b", + Some(network.namespace("ns_c1")), + "10.144.144.2", + "fd00::2/64", + ); disable_p2p(&shared_b_config); let mut shared_b_inst = Instance::new_with_process_runtime(shared_b_config, process_runtime.clone()); shared_b_inst.run().await.unwrap(); // 10.1.1.4 - let admin_c_config = - create_admin_config("admin_c", Some("ns_c3"), "10.144.144.4", "fd00::4/64"); + let admin_c_config = create_admin_config( + "admin_c", + Some(network.namespace("ns_c3")), + "10.144.144.4", + "fd00::4/64", + ); disable_p2p(&admin_c_config); let mut admin_c_inst = Instance::new_with_process_runtime(admin_c_config, process_runtime.clone()); @@ -2582,7 +2693,7 @@ async fn credential_admin_shared_admin_credential_connectivity( let cred_d_config = create_credential_config( &admin_a_inst, "cred_d", - Some("ns_c2"), + Some(network.namespace("ns_c2")), "10.144.144.5", "fd00::5/64", ) @@ -2621,19 +2732,19 @@ async fn credential_admin_shared_admin_credential_connectivity( .await; wait_for_condition( - || async { ping_test("ns_c3", "10.144.144.5", None).await }, + || async { ping_test(network.namespace("ns_c3"), "10.144.144.5", None).await }, Duration::from_secs(15), ) .await; wait_for_condition( - || async { ping_test("ns_adm", "10.144.144.5", None).await }, + || async { ping_test(network.namespace("ns_adm"), "10.144.144.5", None).await }, Duration::from_secs(15), ) .await; wait_for_condition( - || async { ping_test("ns_c2", "10.144.144.4", None).await }, + || async { ping_test(network.namespace("ns_c2"), "10.144.144.4", None).await }, Duration::from_secs(15), ) .await; @@ -2644,23 +2755,35 @@ async fn credential_admin_shared_admin_credential_connectivity( #[tokio::test] #[serial_test::serial] async fn credential_non_reusable_across_two_admins_allows_only_one_peer() { - prepare_credential_network(); + let network = prepare_credential_network(); let process_runtime = CoreProcessRuntime::new(); - let admin_a_config = - create_admin_config("admin_a", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); + let admin_a_config = create_admin_config( + "admin_a", + Some(network.namespace("ns_adm")), + "10.144.144.1", + "fd00::1/64", + ); let mut admin_a_inst = Instance::new_with_process_runtime(admin_a_config, process_runtime.clone()); admin_a_inst.run().await.unwrap(); - let shared_b_config = - create_shared_config("shared_b", Some("ns_c1"), "10.144.144.2", "fd00::2/64"); + let shared_b_config = create_shared_config( + "shared_b", + Some(network.namespace("ns_c1")), + "10.144.144.2", + "fd00::2/64", + ); let mut shared_b_inst = Instance::new_with_process_runtime(shared_b_config, process_runtime.clone()); shared_b_inst.run().await.unwrap(); - let admin_c_config = - create_admin_config("admin_c", Some("ns_c3"), "10.144.144.4", "fd00::4/64"); + let admin_c_config = create_admin_config( + "admin_c", + Some(network.namespace("ns_c3")), + "10.144.144.4", + "fd00::4/64", + ); let mut admin_c_inst = Instance::new_with_process_runtime(admin_c_config, process_runtime.clone()); admin_c_inst.run().await.unwrap(); @@ -2703,7 +2826,7 @@ async fn credential_non_reusable_across_two_admins_allows_only_one_peer() { network_name.clone(), &cred_secret, "cred_left", - Some("ns_c2"), + Some(network.namespace("ns_c2")), "10.144.144.5", "fd00::5/64", ); @@ -2711,7 +2834,7 @@ async fn credential_non_reusable_across_two_admins_allows_only_one_peer() { network_name, &cred_secret, "cred_right", - Some("ns_c4"), + Some(network.namespace("ns_c4")), "10.144.144.6", "fd00::6/64", ); @@ -2764,12 +2887,37 @@ async fn credential_non_reusable_across_two_admins_allows_only_one_peer() { ) }; - wait_ping_reachability("ns_adm", winner_ip, true, Duration::from_secs(20)).await; - wait_ping_reachability("ns_c3", winner_ip, true, Duration::from_secs(20)).await; - wait_ping_reachability("ns_adm", loser_ip, false, Duration::from_secs(5)).await; - wait_ping_reachability("ns_c3", loser_ip, false, Duration::from_secs(5)).await; + wait_ping_reachability( + network.namespace("ns_adm"), + winner_ip, + true, + Duration::from_secs(20), + ) + .await; + wait_ping_reachability( + network.namespace("ns_c3"), + winner_ip, + true, + Duration::from_secs(20), + ) + .await; + wait_ping_reachability( + network.namespace("ns_adm"), + loser_ip, + false, + Duration::from_secs(5), + ) + .await; + wait_ping_reachability( + network.namespace("ns_c3"), + loser_ip, + false, + Duration::from_secs(5), + ) + .await; assert_shared_visibility_stable( + &network, &admin_a_inst, &admin_c_inst, winner_peer_id, @@ -2779,6 +2927,7 @@ async fn credential_non_reusable_across_two_admins_allows_only_one_peer() { ) .await; assert_shared_visibility_stable( + &network, &admin_a_inst, &admin_c_inst, loser_peer_id, @@ -2803,12 +2952,37 @@ async fn credential_non_reusable_across_two_admins_allows_only_one_peer() { ) .await; - wait_ping_reachability("ns_adm", loser_ip, true, Duration::from_secs(20)).await; - wait_ping_reachability("ns_c3", loser_ip, true, Duration::from_secs(20)).await; - wait_ping_reachability("ns_adm", winner_ip, false, Duration::from_secs(5)).await; - wait_ping_reachability("ns_c3", winner_ip, false, Duration::from_secs(5)).await; + wait_ping_reachability( + network.namespace("ns_adm"), + loser_ip, + true, + Duration::from_secs(20), + ) + .await; + wait_ping_reachability( + network.namespace("ns_c3"), + loser_ip, + true, + Duration::from_secs(20), + ) + .await; + wait_ping_reachability( + network.namespace("ns_adm"), + winner_ip, + false, + Duration::from_secs(5), + ) + .await; + wait_ping_reachability( + network.namespace("ns_c3"), + winner_ip, + false, + Duration::from_secs(5), + ) + .await; assert_shared_visibility_stable( + &network, &admin_a_inst, &admin_c_inst, loser_peer_id, @@ -2818,6 +2992,7 @@ async fn credential_non_reusable_across_two_admins_allows_only_one_peer() { ) .await; assert_shared_visibility_stable( + &network, &admin_a_inst, &admin_c_inst, winner_peer_id, diff --git a/easytier/src/tests/mod.rs b/easytier/src/tests/mod.rs index 623ca559..4f6c45a1 100644 --- a/easytier/src/tests/mod.rs +++ b/easytier/src/tests/mod.rs @@ -1,6 +1,9 @@ #[cfg(target_os = "linux")] mod three_node; +#[cfg(target_os = "linux")] +mod network; + mod ipv6_test; #[cfg(target_os = "linux")] @@ -68,21 +71,35 @@ pub fn del_netns(name: &str) { .output(); } +fn run_network_command(program: &str, args: &[&str]) { + let output = std::process::Command::new(program) + .args(args) + .output() + .unwrap(); + assert!( + output.status.success(), + "{} {:?} failed ({}): stdout={}, stderr={}", + program, + args, + output.status, + String::from_utf8_lossy(&output.stdout), + String::from_utf8_lossy(&output.stderr) + ); +} + pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) { // create netns - let _ = std::process::Command::new("ip") - .args(["netns", "add", name]) - .output() - .unwrap(); + run_network_command("ip", &["netns", "add", name]); // set lo up - let _ = std::process::Command::new("ip") - .args(["netns", "exec", name, "ip", "link", "set", "lo", "up"]) - .output() - .unwrap(); + run_network_command( + "ip", + &["netns", "exec", name, "ip", "link", "set", "lo", "up"], + ); - let _ = std::process::Command::new("ip") - .args([ + run_network_command( + "ip", + &[ "link", "add", get_host_veth_name(name), @@ -91,17 +108,17 @@ pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) { "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(); + run_network_command( + "ip", + &["link", "set", get_guest_veth_name(name), "netns", name], + ); - let _ = std::process::Command::new("ip") - .args([ + run_network_command( + "ip", + &[ "netns", "exec", name, @@ -110,18 +127,15 @@ pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) { "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(); + run_network_command("ip", &["link", "set", get_host_veth_name(name), "up"]); for ip in [ipv4, ipv6] { - let _ = std::process::Command::new("ip") - .args([ + run_network_command( + "ip", + &[ "netns", "exec", name, @@ -131,36 +145,17 @@ pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) { 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(); + run_network_command("brctl", &["addif", br_name, get_host_veth_name(ns_name)]); // set bridge up - let _ = std::process::Command::new("ip") - .args(["link", "set", br_name, "up"]) - .output() - .unwrap(); + run_network_command("ip", &["link", "set", br_name, "up"]); } fn check_route( diff --git a/easytier/src/tests/network.rs b/easytier/src/tests/network.rs new file mode 100644 index 00000000..cf27fc29 --- /dev/null +++ b/easytier/src/tests/network.rs @@ -0,0 +1,95 @@ +use crate::common::netns::{NetNS, ROOT_NETNS_NAME}; + +use super::{add_ns_to_bridge, create_netns, del_netns, run_network_command}; + +/// Owns one test's host links and namespaces. Keep this alive until its +/// instances and background tasks have stopped, including on setup failures. +pub(super) struct TestNetwork { + directory: tempfile::TempDir, + namespaces: Vec<(&'static str, String)>, + links: Vec<(&'static str, String)>, +} + +impl TestNetwork { + pub(super) fn new() -> Self { + Self { + directory: tempfile::Builder::new() + .prefix("et") + .rand_bytes(4) + .tempdir() + .unwrap(), + namespaces: Vec::new(), + links: Vec::new(), + } + } + + fn prefix(&self) -> &str { + self.directory.path().file_name().unwrap().to_str().unwrap() + } + + pub(super) fn namespace(&self, name: &str) -> &str { + &self + .namespaces + .iter() + .find(|(key, _)| *key == name) + .unwrap() + .1 + } + + pub(super) fn link(&self, name: &str) -> &str { + &self.links.iter().find(|(key, _)| *key == name).unwrap().1 + } + + fn add_namespace(&mut self, name: &'static str) { + // veth__h must fit Linux's 15-byte interface name limit. + let actual = format!("{}{:02x}", self.prefix(), self.namespaces.len()); + assert!(actual.len() <= 8); + assert!(!self.namespaces.iter().any(|(key, _)| *key == name)); + self.namespaces.push((name, actual)); + } + + pub(super) fn create_namespace(&mut self, name: &'static str, ipv4: &str, ipv6: &str) { + self.add_namespace(name); + create_netns(self.namespace(name), ipv4, ipv6); + } + + pub(super) fn create_empty_namespace(&mut self, name: &'static str) { + self.add_namespace(name); + let actual = self.namespace(name); + run_network_command("ip", &["netns", "add", actual]); + run_network_command( + "ip", + &["netns", "exec", actual, "ip", "link", "set", "lo", "up"], + ); + } + + pub(super) fn add_link(&mut self, name: &'static str) { + let actual = format!("{}l{:02x}", self.prefix(), self.links.len()); + assert!(!self.links.iter().any(|(key, _)| *key == name)); + self.links.push((name, actual)); + } + + pub(super) fn create_bridge(&mut self, name: &'static str) { + self.add_link(name); + run_network_command("brctl", &["addbr", self.link(name)]); + run_network_command("ip", &["link", "set", self.link(name), "up"]); + } + + pub(super) fn add_to_bridge(&self, bridge: &str, namespace: &str) { + add_ns_to_bridge(self.link(bridge), self.namespace(namespace)); + } +} + +impl Drop for TestNetwork { + fn drop(&mut self) { + let _guard = NetNS::new(Some(ROOT_NETNS_NAME.to_owned())).guard(); + for (_, namespace) in &self.namespaces { + del_netns(namespace); + } + for (_, link) in self.links.iter().rev() { + let _ = std::process::Command::new("ip") + .args(["link", "del", link]) + .output(); + } + } +} diff --git a/easytier/src/tests/three_node.rs b/easytier/src/tests/three_node.rs index 1ea68ee9..5d07ba06 100644 --- a/easytier/src/tests/three_node.rs +++ b/easytier/src/tests/three_node.rs @@ -20,6 +20,7 @@ use rand::{Rng, rngs::OsRng}; use tokio::{net::UdpSocket, task::JoinSet}; use x25519_dalek::StaticSecret; +use super::network::TestNetwork; use super::*; // TODO: 需要加一个单测,确保 socks5 + exit node == self || proxy_cidr == 0.0.0.0/0 时,可以实现出口节点的能力。 @@ -114,30 +115,26 @@ use easytier_proto::api::{ manage::VpnPortalClientConfig as VpnPortalClientConfigPb, }; -pub fn prepare_linux_namespaces() { - del_netns("net_a"); - del_netns("net_b"); - del_netns("net_c"); - del_netns("net_d"); - del_netns("net_e"); - del_netns("net_f"); +fn prepare_linux_namespaces() -> TestNetwork { + let mut network = TestNetwork::new(); - create_netns("net_a", "10.1.1.1/24", "fd11::1/64"); - create_netns("net_b", "10.1.1.2/24", "fd11::2/64"); - create_netns("net_c", "10.1.2.3/24", "fd12::3/64"); - create_netns("net_d", "10.1.2.4/24", "fd12::4/64"); - create_netns("net_e", "10.1.1.3/24", "fd11::3/64"); - create_netns("net_f", "10.1.2.5/24", "fd12::5/64"); + network.create_namespace("net_a", "10.1.1.1/24", "fd11::1/64"); + network.create_namespace("net_b", "10.1.1.2/24", "fd11::2/64"); + network.create_namespace("net_c", "10.1.2.3/24", "fd12::3/64"); + network.create_namespace("net_d", "10.1.2.4/24", "fd12::4/64"); + network.create_namespace("net_e", "10.1.1.3/24", "fd11::3/64"); + network.create_namespace("net_f", "10.1.2.5/24", "fd12::5/64"); - prepare_bridge("br_a"); - prepare_bridge("br_b"); + network.create_bridge("br_a"); + network.create_bridge("br_b"); - add_ns_to_bridge("br_a", "net_a"); - add_ns_to_bridge("br_a", "net_b"); - add_ns_to_bridge("br_a", "net_e"); - add_ns_to_bridge("br_b", "net_c"); - add_ns_to_bridge("br_b", "net_d"); - add_ns_to_bridge("br_b", "net_f"); + network.add_to_bridge("br_a", "net_a"); + network.add_to_bridge("br_a", "net_b"); + network.add_to_bridge("br_a", "net_e"); + network.add_to_bridge("br_b", "net_c"); + network.add_to_bridge("br_b", "net_d"); + network.add_to_bridge("br_b", "net_f"); + network } pub fn get_inst_config( @@ -162,19 +159,21 @@ pub fn get_inst_config( config } -pub async fn init_three_node(proto: &str) -> Vec { - init_three_node_with_process_runtime(proto, CoreProcessRuntime::new()).await +pub async fn init_three_node(network: &TestNetwork, proto: &str) -> Vec { + init_three_node_with_process_runtime(network, proto, CoreProcessRuntime::new()).await } async fn init_three_node_with_process_runtime( + network: &TestNetwork, proto: &str, process_runtime: Arc, ) -> Vec { init_three_node_ex_with_inst3( + network, proto, |cfg| cfg, false, - "net_c", + network.namespace("net_c"), "10.144.144.3", "fd00::3/64", process_runtime, @@ -183,6 +182,7 @@ async fn init_three_node_with_process_runtime( } async fn init_three_node_ex_with_inst3 TomlConfigLoader>( + network: &TestNetwork, proto: &str, cfg_cb: F, use_public_server: bool, @@ -191,12 +191,10 @@ async fn init_three_node_ex_with_inst3 TomlConfigLoad inst3_ipv6: &str, process_runtime: Arc, ) -> Vec { - prepare_linux_namespaces(); - let mut inst1 = Instance::new_with_process_runtime( cfg_cb(get_inst_config( "inst1", - Some("net_a"), + Some(network.namespace("net_a")), "10.144.144.1", "fd00::1/64", )), @@ -205,7 +203,7 @@ async fn init_three_node_ex_with_inst3 TomlConfigLoad let mut inst2 = Instance::new_with_process_runtime( cfg_cb(get_inst_config( "inst2", - Some("net_b"), + Some(network.namespace("net_b")), "10.144.144.2", "fd00::2/64", )), @@ -284,15 +282,17 @@ async fn init_three_node_ex_with_inst3 TomlConfigLoad } pub async fn init_three_node_ex TomlConfigLoader>( + network: &TestNetwork, proto: &str, cfg_cb: F, use_public_server: bool, ) -> Vec { init_three_node_ex_with_inst3( + network, proto, cfg_cb, use_public_server, - "net_c", + network.namespace("net_c"), "10.144.144.3", "fd00::3/64", CoreProcessRuntime::new(), @@ -301,14 +301,16 @@ pub async fn init_three_node_ex TomlConfigLoader>( } async fn init_lazy_p2p_three_node_ex TomlConfigLoader>( + network: &TestNetwork, proto: &str, cfg_cb: F, ) -> Vec { init_three_node_ex_with_inst3( + network, proto, cfg_cb, false, - "net_e", + network.namespace("net_e"), "10.144.144.3", "fd00::3/64", CoreProcessRuntime::new(), @@ -433,28 +435,27 @@ fn run_sysctl_in_ns(ns: &str, assignment: &str) { run_cmd("ip", &["netns", "exec", ns, "sysctl", "-qw", assignment]); } -fn create_empty_netns(name: &str) { - del_netns(name); - run_ip(&["netns", "add", name]); - run_ip(&["netns", "exec", name, "ip", "link", "set", "lo", "up"]); -} - fn connect_ns_to_bridge(ns: &str, guest_if: &str, host_if: &str, bridge: &str) { - let _ = std::process::Command::new("ip") - .args(["link", "del", host_if]) - .status(); + let temporary_guest = format!("{}g", host_if); run_ip(&[ - "link", "add", host_if, "type", "veth", "peer", "name", guest_if, + "link", + "add", + host_if, + "type", + "veth", + "peer", + "name", + &temporary_guest, ]); - run_ip(&["link", "set", guest_if, "netns", ns]); + run_ip(&["link", "set", &temporary_guest, "netns", ns]); + run_ip_in_ns(ns, &["link", "set", &temporary_guest, "name", guest_if]); run_ip(&["link", "set", host_if, "up"]); run_cmd("brctl", &["addif", bridge, host_if]); run_ip(&["netns", "exec", ns, "ip", "link", "set", guest_if, "up"]); } struct PublicIpv6Lab { - extra_namespaces: [&'static str; 2], - extra_bridges: [&'static str; 2], + network: TestNetwork, } #[derive(Clone, Copy)] @@ -483,53 +484,43 @@ impl PublicIpv6Lab { const SERVER_IP: &'static str = "2001:db8:ffff:2::100"; fn setup_with_topology(topology: PublicIpv6LabTopology) -> Self { - prepare_linux_namespaces(); - - del_netns(Self::UPSTREAM_NS); - del_netns(Self::SERVER_NS); - let _ = std::process::Command::new("ip") - .args(["link", "del", Self::WAN_BRIDGE]) - .status(); - let _ = std::process::Command::new("ip") - .args(["link", "del", Self::SERVER_BRIDGE]) - .status(); - let _ = std::process::Command::new("brctl") - .args(["delbr", Self::WAN_BRIDGE]) - .status(); - let _ = std::process::Command::new("brctl") - .args(["delbr", Self::SERVER_BRIDGE]) - .status(); - - create_empty_netns(Self::UPSTREAM_NS); - create_empty_netns(Self::SERVER_NS); - prepare_bridge(Self::WAN_BRIDGE); - prepare_bridge(Self::SERVER_BRIDGE); - run_ip(&["link", "set", Self::WAN_BRIDGE, "up"]); - run_ip(&["link", "set", Self::SERVER_BRIDGE, "up"]); - - connect_ns_to_bridge( - Self::PROVIDER_NS, - "pubwan0", + let mut network = prepare_linux_namespaces(); + network.create_empty_namespace(Self::UPSTREAM_NS); + network.create_empty_namespace(Self::SERVER_NS); + network.create_bridge(Self::WAN_BRIDGE); + network.create_bridge(Self::SERVER_BRIDGE); + for link in [ "veth_pubwan_p", - Self::WAN_BRIDGE, - ); - connect_ns_to_bridge( - Self::UPSTREAM_NS, - "upwan0", "veth_pubwan_u", - Self::WAN_BRIDGE, - ); - connect_ns_to_bridge( - Self::UPSTREAM_NS, - "upsrv0", "veth_pubsrv_u", - Self::SERVER_BRIDGE, + "veth_pubsrv_s", + ] { + network.add_link(link); + } + + connect_ns_to_bridge( + network.namespace(Self::PROVIDER_NS), + "pubwan0", + network.link("veth_pubwan_p"), + network.link(Self::WAN_BRIDGE), ); connect_ns_to_bridge( - Self::SERVER_NS, + network.namespace(Self::UPSTREAM_NS), + "upwan0", + network.link("veth_pubwan_u"), + network.link(Self::WAN_BRIDGE), + ); + connect_ns_to_bridge( + network.namespace(Self::UPSTREAM_NS), + "upsrv0", + network.link("veth_pubsrv_u"), + network.link(Self::SERVER_BRIDGE), + ); + connect_ns_to_bridge( + network.namespace(Self::SERVER_NS), "srv0", - "veth_pubsrv_s", - Self::SERVER_BRIDGE, + network.link("veth_pubsrv_s"), + network.link(Self::SERVER_BRIDGE), ); let (provider_wan_addr, upstream_wan_addr) = match topology { @@ -543,31 +534,34 @@ impl PublicIpv6Lab { }; run_ip_in_ns( - Self::PROVIDER_NS, + network.namespace(Self::PROVIDER_NS), &["addr", "add", provider_wan_addr, "dev", "pubwan0"], ); run_ip_in_ns( - Self::UPSTREAM_NS, + network.namespace(Self::UPSTREAM_NS), &["addr", "add", upstream_wan_addr, "dev", "upwan0"], ); run_ip_in_ns( - Self::UPSTREAM_NS, + network.namespace(Self::UPSTREAM_NS), &["addr", "add", Self::UPSTREAM_SERVER_ADDR, "dev", "upsrv0"], ); run_ip_in_ns( - Self::SERVER_NS, + network.namespace(Self::SERVER_NS), &["addr", "add", Self::SERVER_ADDR, "dev", "srv0"], ); match topology { PublicIpv6LabTopology::DelegatedPrefix => { run_ip_in_ns( - Self::PROVIDER_NS, + network.namespace(Self::PROVIDER_NS), &["link", "add", "pubprefix0", "type", "dummy"], ); - run_ip_in_ns(Self::PROVIDER_NS, &["link", "set", "pubprefix0", "up"]); run_ip_in_ns( - Self::PROVIDER_NS, + network.namespace(Self::PROVIDER_NS), + &["link", "set", "pubprefix0", "up"], + ); + run_ip_in_ns( + network.namespace(Self::PROVIDER_NS), &[ "-6", "route", @@ -578,7 +572,7 @@ impl PublicIpv6Lab { ], ); run_ip_in_ns( - Self::PROVIDER_NS, + network.namespace(Self::PROVIDER_NS), &[ "-6", "route", @@ -595,7 +589,7 @@ impl PublicIpv6Lab { } PublicIpv6LabTopology::OnLinkPrefix => { run_ip_in_ns( - Self::PROVIDER_NS, + network.namespace(Self::PROVIDER_NS), &[ "-6", "route", @@ -611,7 +605,7 @@ impl PublicIpv6Lab { } run_ip_in_ns( - Self::SERVER_NS, + network.namespace(Self::SERVER_NS), &[ "-6", "route", @@ -625,7 +619,7 @@ impl PublicIpv6Lab { ); if matches!(topology, PublicIpv6LabTopology::DelegatedPrefix) { run_ip_in_ns( - Self::UPSTREAM_NS, + network.namespace(Self::UPSTREAM_NS), &[ "-6", "route", @@ -639,29 +633,16 @@ impl PublicIpv6Lab { ); } - run_sysctl_in_ns(Self::PROVIDER_NS, "net.ipv6.conf.all.forwarding=1"); - run_sysctl_in_ns(Self::UPSTREAM_NS, "net.ipv6.conf.all.forwarding=1"); + run_sysctl_in_ns( + network.namespace(Self::PROVIDER_NS), + "net.ipv6.conf.all.forwarding=1", + ); + run_sysctl_in_ns( + network.namespace(Self::UPSTREAM_NS), + "net.ipv6.conf.all.forwarding=1", + ); - Self { - extra_namespaces: [Self::UPSTREAM_NS, Self::SERVER_NS], - extra_bridges: [Self::WAN_BRIDGE, Self::SERVER_BRIDGE], - } - } -} - -impl Drop for PublicIpv6Lab { - fn drop(&mut self) { - for ns in self.extra_namespaces { - del_netns(ns); - } - for bridge in self.extra_bridges { - let _ = std::process::Command::new("ip") - .args(["link", "del", bridge]) - .status(); - let _ = std::process::Command::new("brctl") - .args(["delbr", bridge]) - .status(); - } + Self { network } } } @@ -704,7 +685,7 @@ async fn init_public_ipv6_two_node_with_topology( let provider_cfg = get_public_ipv6_config( "provider_public_ipv6", - PublicIpv6Lab::PROVIDER_NS, + lab.network.namespace(PublicIpv6Lab::PROVIDER_NS), "10.144.144.1", PublicIpv6Lab::PROVIDER_TUN, uuid::Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(), @@ -713,7 +694,7 @@ async fn init_public_ipv6_two_node_with_topology( let client_cfg = get_public_ipv6_config( "client_public_ipv6", - PublicIpv6Lab::CLIENT_NS, + lab.network.namespace(PublicIpv6Lab::CLIENT_NS), "10.144.144.2", PublicIpv6Lab::CLIENT_TUN, client_inst_id, @@ -794,7 +775,7 @@ fn ndp_proxy_exists_in_ns(ns: &str, dev: &str, addr: std::net::Ipv6Addr) -> bool #[serial_test::serial] pub async fn public_ipv6_auto_addr_end_to_end() { let client_id = uuid::Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(); - let (_lab, _process_runtime, provider, client) = init_public_ipv6_two_node(client_id).await; + let (lab, _process_runtime, provider, client) = init_public_ipv6_two_node(client_id).await; wait_for_condition( || async { @@ -875,14 +856,14 @@ pub async fn public_ipv6_auto_addr_end_to_end() { wait_for_condition( || async { addr_exists_in_ns( - PublicIpv6Lab::CLIENT_NS, + lab.network.namespace(PublicIpv6Lab::CLIENT_NS), PublicIpv6Lab::CLIENT_TUN, &leased.to_string(), ) && route_exists_in_ns( - PublicIpv6Lab::CLIENT_NS, + lab.network.namespace(PublicIpv6Lab::CLIENT_NS), &format!("default dev {}", PublicIpv6Lab::CLIENT_TUN), ) && route_exists_in_ns( - PublicIpv6Lab::PROVIDER_NS, + lab.network.namespace(PublicIpv6Lab::PROVIDER_NS), &format!("{} dev {}", leased.address(), PublicIpv6Lab::PROVIDER_TUN), ) }, @@ -891,7 +872,14 @@ pub async fn public_ipv6_auto_addr_end_to_end() { .await; wait_for_condition( - || async { ping6_test(PublicIpv6Lab::CLIENT_NS, PublicIpv6Lab::SERVER_IP, None).await }, + || async { + ping6_test( + lab.network.namespace(PublicIpv6Lab::CLIENT_NS), + PublicIpv6Lab::SERVER_IP, + None, + ) + .await + }, Duration::from_secs(10), ) .await; @@ -899,7 +887,7 @@ pub async fn public_ipv6_auto_addr_end_to_end() { wait_for_condition( || async { ping6_test( - PublicIpv6Lab::SERVER_NS, + lab.network.namespace(PublicIpv6Lab::SERVER_NS), leased.address().to_string().as_str(), None, ) @@ -916,7 +904,7 @@ pub async fn public_ipv6_auto_addr_end_to_end() { #[serial_test::serial] pub async fn public_ipv6_auto_addr_on_link_ndp_proxy_end_to_end() { let client_id = uuid::Uuid::parse_str("44444444-4444-4444-4444-444444444444").unwrap(); - let (_lab, _process_runtime, provider, client) = + let (lab, _process_runtime, provider, client) = init_public_ipv6_two_node_with_topology(client_id, PublicIpv6LabTopology::OnLinkPrefix) .await; @@ -939,11 +927,11 @@ pub async fn public_ipv6_auto_addr_on_link_ndp_proxy_end_to_end() { wait_for_condition( || async { addr_exists_in_ns( - PublicIpv6Lab::CLIENT_NS, + lab.network.namespace(PublicIpv6Lab::CLIENT_NS), PublicIpv6Lab::CLIENT_TUN, &leased.to_string(), ) && route_exists_in_ns( - PublicIpv6Lab::PROVIDER_NS, + lab.network.namespace(PublicIpv6Lab::PROVIDER_NS), &format!("{} dev {}", leased.address(), PublicIpv6Lab::PROVIDER_TUN), ) }, @@ -953,7 +941,11 @@ pub async fn public_ipv6_auto_addr_on_link_ndp_proxy_end_to_end() { wait_for_condition( || async { - ndp_proxy_exists_in_ns(PublicIpv6Lab::PROVIDER_NS, "pubwan0", leased.address()) + ndp_proxy_exists_in_ns( + lab.network.namespace(PublicIpv6Lab::PROVIDER_NS), + "pubwan0", + leased.address(), + ) }, Duration::from_secs(20), ) @@ -962,7 +954,7 @@ pub async fn public_ipv6_auto_addr_on_link_ndp_proxy_end_to_end() { wait_for_condition( || async { ping6_test( - PublicIpv6Lab::SERVER_NS, + lab.network.namespace(PublicIpv6Lab::SERVER_NS), leased.address().to_string().as_str(), None, ) @@ -979,14 +971,14 @@ pub async fn public_ipv6_auto_addr_on_link_ndp_proxy_end_to_end() { #[serial_test::serial] pub async fn public_ipv6_auto_addr_reconnect_reuses_same_address() { let client_id = uuid::Uuid::parse_str("33333333-3333-3333-3333-333333333333").unwrap(); - let (_lab, process_runtime, provider, client) = init_public_ipv6_two_node(client_id).await; + let (lab, process_runtime, provider, client) = init_public_ipv6_two_node(client_id).await; let first = wait_for_public_ipv6_addr(&client).await; drop_insts(vec![client]).await; let client_cfg = get_public_ipv6_config( "client_public_ipv6_reconnect", - PublicIpv6Lab::CLIENT_NS, + lab.network.namespace(PublicIpv6Lab::CLIENT_NS), "10.144.144.2", PublicIpv6Lab::CLIENT_TUN, client_id, @@ -1009,7 +1001,14 @@ pub async fn public_ipv6_auto_addr_reconnect_reuses_same_address() { assert_eq!(first, second); wait_for_condition( - || async { ping6_test(PublicIpv6Lab::CLIENT_NS, PublicIpv6Lab::SERVER_IP, None).await }, + || async { + ping6_test( + lab.network.namespace(PublicIpv6Lab::CLIENT_NS), + PublicIpv6Lab::SERVER_IP, + None, + ) + .await + }, Duration::from_secs(10), ) .await; @@ -1039,7 +1038,9 @@ pub async fn basic_three_node_test( )] encrypt_algorithm_pair: [&str; 2], ) { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, proto, |cfg| { let mut flags = cfg.get_flags(); @@ -1069,20 +1070,20 @@ pub async fn basic_three_node_test( // Test IPv4 connectivity wait_for_condition( - || async { ping_test("net_c", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_c"), "10.144.144.1", None).await }, Duration::from_secs(5000), ) .await; // Test IPv6 connectivity wait_for_condition( - || async { ping6_test("net_c", "fd00::1", None).await }, + || async { ping6_test(network.namespace("net_c"), "fd00::1", None).await }, Duration::from_secs(5), ) .await; wait_for_condition( - || async { ping6_test("net_a", "fd00::3", None).await }, + || async { ping6_test(network.namespace("net_a"), "fd00::3", None).await }, Duration::from_secs(5), ) .await; @@ -1093,16 +1094,17 @@ pub async fn basic_three_node_test( #[tokio::test] #[serial_test::serial] pub async fn ping_own_virtual_ip_should_work() { - let insts = init_three_node("udp").await; + let network = prepare_linux_namespaces(); + let insts = init_three_node(&network, "udp").await; wait_for_condition( - || async { ping_test("net_a", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.1", None).await }, Duration::from_secs(5), ) .await; wait_for_condition( - || async { ping6_test("net_a", "fd00::1", None).await }, + || async { ping6_test(network.namespace("net_a"), "fd00::1", None).await }, Duration::from_secs(5), ) .await; @@ -1113,9 +1115,11 @@ pub async fn ping_own_virtual_ip_should_work() { #[tokio::test] #[serial_test::serial] pub async fn subnet_proxy_loop_prevention_test() { + let network = prepare_linux_namespaces(); // 测试场景:inst1 和 inst2 都代理了 10.1.2.0/24 网段, // inst1 发起对 10.1.2.5 的 ping,不应该出现环路 let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { @@ -1155,7 +1159,7 @@ pub async fn subnet_proxy_loop_prevention_test() { // 这应该失败,并且不会产生环路 let now = std::time::Instant::now(); while now.elapsed().as_secs() < 10 { - ping_test("net_a", "10.1.2.5", None).await; + ping_test(network.namespace("net_a"), "10.1.2.5", None).await; tokio::time::sleep(Duration::from_secs(1)).await; } @@ -1174,7 +1178,12 @@ pub async fn subnet_proxy_loop_prevention_test() { drop_insts(insts).await; } -async fn subnet_proxy_test_udp(listen_ip: &str, target_ip: &str, timeout: Duration) { +async fn subnet_proxy_test_udp( + network: &TestNetwork, + listen_ip: &str, + target_ip: &str, + timeout: Duration, +) { use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; use rand::Rng; @@ -1186,16 +1195,16 @@ async fn subnet_proxy_test_udp(listen_ip: &str, target_ip: &str, timeout: Durati rand::thread_rng().fill(&mut buf[..]); let ns_name = if target_ip == "10.144.144.3" { - "net_c" + network.namespace("net_c") } else { - "net_d" + network.namespace("net_d") }; let result = _tunnel_pingpong_netns_with_timeout( udp_listener, udp_connector, NetNS::new(Some(ns_name.into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, timeout, ) @@ -1213,7 +1222,7 @@ async fn subnet_proxy_test_udp(listen_ip: &str, target_ip: &str, timeout: Durati udp_listener, udp_connector, NetNS::new(Some(ns_name.into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, timeout, ) @@ -1221,7 +1230,12 @@ async fn subnet_proxy_test_udp(listen_ip: &str, target_ip: &str, timeout: Durati assert!(result.is_ok(), "{}", result.unwrap_err()); } -async fn subnet_proxy_test_tcp(listen_ip: &str, connect_ip: &str, timeout: Duration) { +async fn subnet_proxy_test_tcp( + network: &TestNetwork, + listen_ip: &str, + connect_ip: &str, + timeout: Duration, +) { use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; use rand::Rng; @@ -1232,16 +1246,16 @@ async fn subnet_proxy_test_tcp(listen_ip: &str, connect_ip: &str, timeout: Durat rand::thread_rng().fill(&mut buf[..]); let ns_name = if connect_ip == "10.144.144.3" { - "net_c" + network.namespace("net_c") } else { - "net_d" + network.namespace("net_d") }; let result = _tunnel_pingpong_netns_with_timeout( tcp_listener, tcp_connector, NetNS::new(Some(ns_name.into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, timeout, ) @@ -1249,15 +1263,15 @@ async fn subnet_proxy_test_tcp(listen_ip: &str, connect_ip: &str, timeout: Durat assert!(result.is_ok(), "{}", result.unwrap_err()); } -async fn subnet_proxy_test_icmp(target_ip: &str, timeout: Duration) { +async fn subnet_proxy_test_icmp(network: &TestNetwork, target_ip: &str, timeout: Duration) { wait_for_condition( - || async { ping_test("net_a", target_ip, None).await }, + || async { ping_test(network.namespace("net_a"), target_ip, None).await }, timeout, ) .await; wait_for_condition( - || async { ping_test("net_a", target_ip, Some(5 * 1024)).await }, + || async { ping_test(network.namespace("net_a"), target_ip, Some(5 * 1024)).await }, timeout, ) .await; @@ -1265,7 +1279,9 @@ async fn subnet_proxy_test_icmp(target_ip: &str, timeout: Duration) { #[tokio::test] pub async fn quic_proxy() { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst3" { @@ -1294,10 +1310,10 @@ pub async fn quic_proxy() { let target_ip = "10.1.2.4"; - subnet_proxy_test_icmp(target_ip, Duration::from_secs(5)).await; - subnet_proxy_test_icmp("10.144.144.3", Duration::from_secs(5)).await; - subnet_proxy_test_tcp(target_ip, target_ip, Duration::from_secs(5)).await; - subnet_proxy_test_tcp("0.0.0.0", "10.144.144.3", Duration::from_secs(5)).await; + subnet_proxy_test_icmp(&network, target_ip, Duration::from_secs(5)).await; + subnet_proxy_test_icmp(&network, "10.144.144.3", Duration::from_secs(5)).await; + subnet_proxy_test_tcp(&network, target_ip, target_ip, Duration::from_secs(5)).await; + subnet_proxy_test_tcp(&network, "0.0.0.0", "10.144.144.3", Duration::from_secs(5)).await; let metrics = insts[0] .get_core_instance() @@ -1325,7 +1341,9 @@ pub async fn subnet_proxy_three_node_test( #[values(true, false)] dst_enable_kcp_proxy: bool, #[values(true, false)] dst_enable_quic_proxy: bool, ) { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst3" { @@ -1387,14 +1405,14 @@ pub async fn subnet_proxy_three_node_test( .await; for target_ip in ["10.1.3.4", "10.1.2.4", "10.144.144.3"] { - subnet_proxy_test_icmp(target_ip, Duration::from_secs(5)).await; + subnet_proxy_test_icmp(&network, target_ip, Duration::from_secs(5)).await; let listen_ip = if target_ip == "10.144.144.3" { "0.0.0.0" } else { "10.1.2.4" }; - subnet_proxy_test_tcp(listen_ip, target_ip, Duration::from_secs(5)).await; - subnet_proxy_test_udp(listen_ip, target_ip, Duration::from_secs(5)).await; + subnet_proxy_test_tcp(&network, listen_ip, target_ip, Duration::from_secs(5)).await; + subnet_proxy_test_udp(&network, listen_ip, target_ip, Duration::from_secs(5)).await; } if enable_quic_proxy && !disable_quic_input { let metrics = insts[0] @@ -1461,9 +1479,11 @@ pub async fn subnet_proxy_half_close_test( #[values("tcp", "kcp", "quic")] transport: &str, #[values(false, true)] use_smoltcp: bool, ) { + let network = prepare_linux_namespaces(); use tokio::io::{AsyncReadExt, AsyncWriteExt}; let insts = init_three_node_ex( + &network, "udp", |cfg| { let mut flags = cfg.get_flags(); @@ -1495,13 +1515,13 @@ pub async fn subnet_proxy_half_close_test( // The advertised route can precede installation of the host TUN route. wait_for_condition( - || async { ping_test("net_a", "10.1.3.4", None).await }, + || async { ping_test(network.namespace("net_a"), "10.1.3.4", None).await }, Duration::from_secs(5), ) .await; let result = tokio::time::timeout(Duration::from_secs(10), async { - let listener = NetNS::new(Some("net_d".into())).run(|| { + let listener = NetNS::new(Some(network.namespace("net_d").into())).run(|| { let listener = std::net::TcpListener::bind("10.1.2.4:22224").unwrap(); listener.set_nonblocking(true).unwrap(); tokio::net::TcpListener::from_std(listener).unwrap() @@ -1509,8 +1529,8 @@ pub async fn subnet_proxy_half_close_test( for (source_closes_first, request_size) in [(true, 0), (false, 0), (true, 64 * 1024), (false, 64 * 1024)] { - let socket = - NetNS::new(Some("net_a".into())).run(|| tokio::net::TcpSocket::new_v4().unwrap()); + let socket = NetNS::new(Some(network.namespace("net_a").into())) + .run(|| tokio::net::TcpSocket::new_v4().unwrap()); let (client, (server, _)) = tokio::try_join!( socket.connect("10.1.3.4:22224".parse().unwrap()), listener.accept(), @@ -1553,7 +1573,9 @@ pub async fn data_compress( #[values(true, false)] inst1_compress: bool, #[values(true, false)] inst2_compress: bool, ) { + let network = prepare_linux_namespaces(); let _insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" && inst1_compress { @@ -1575,13 +1597,13 @@ pub async fn data_compress( .await; wait_for_condition( - || async { ping_test("net_a", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.3", None).await }, Duration::from_secs(5), ) .await; wait_for_condition( - || async { ping_test("net_a", "10.144.144.3", Some(5 * 1024)).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.3", Some(5 * 1024)).await }, Duration::from_secs(5), ) .await; @@ -1594,12 +1616,19 @@ pub async fn data_compress( #[tokio::test] #[serial_test::serial] pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str) { + let network = Arc::new(prepare_linux_namespaces()); use tokio_util::task::AbortOnDropHandle; let process_runtime = CoreProcessRuntime::new(); - let insts = init_three_node_with_process_runtime(proto, process_runtime.clone()).await; + let insts = + init_three_node_with_process_runtime(&network, proto, process_runtime.clone()).await; let mut inst4 = Instance::new_with_process_runtime( - get_inst_config("inst4", Some("net_d"), "10.144.144.4", "fd00::4/64"), + get_inst_config( + "inst4", + Some(network.namespace("net_d")), + "10.144.144.4", + "fd00::4/64", + ), process_runtime.clone(), ); if proto == "tcp" { @@ -1616,7 +1645,9 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str tracing::info!("inst3 peer id: {:?}", insts[2].peer_id()); tracing::info!("inst4 peer id: {:?}", inst4.peer_id()); + let task_network = network.clone(); let task = tokio::spawn(async move { + let network = task_network; for _ in 1..=2 { // inst4 should be in inst1's route list wait_for_condition( @@ -1632,11 +1663,13 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str ) .await; - set_link_status("net_d", false); + set_link_status(network.namespace("net_d"), false); + let ping_network = network.clone(); let _t = AbortOnDropHandle::new(tokio::spawn(async move { + let network = ping_network; // do some ping in net_a to trigger net_c pingpong loop { - ping_test("net_a", "10.144.144.4", Some(1)).await; + ping_test(network.namespace("net_a"), "10.144.144.4", Some(1)).await; } })); wait_for_condition( @@ -1668,10 +1701,11 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str ) .await; - set_link_status("net_d", true); + set_link_status(network.namespace("net_d"), true); } drop_insts(insts).await; + drop_insts(vec![inst4]).await; }); let (ret,) = tokio::join!(task); @@ -1681,7 +1715,8 @@ pub async fn proxy_three_node_disconnect_test(#[values("tcp", "wg")] proto: &str #[tokio::test] #[serial_test::serial] pub async fn udp_broadcast_test() { - let _insts = init_three_node("tcp").await; + let network = prepare_linux_namespaces(); + let _insts = init_three_node(&network, "tcp").await; let udp_broadcast_responder = |net_ns: NetNS, counter: Arc| async move { let _g = net_ns.guard(); @@ -1700,18 +1735,18 @@ pub async fn udp_broadcast_test() { let mut tasks = JoinSet::new(); let counter = Arc::new(AtomicU32::new(0)); tasks.spawn(udp_broadcast_responder( - NetNS::new(Some("net_b".into())), + NetNS::new(Some(network.namespace("net_b").into())), counter.clone(), )); tasks.spawn(udp_broadcast_responder( - NetNS::new(Some("net_c".into())), + NetNS::new(Some(network.namespace("net_c").into())), counter.clone(), )); tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; // send broadcast - let net_ns = NetNS::new(Some("net_a".into())); + let net_ns = NetNS::new(Some(network.namespace("net_a").into())); let _g = net_ns.guard(); let socket: UdpSocket = UdpSocket::bind("0.0.0.0:0").await.unwrap(); socket.set_broadcast(true).unwrap(); @@ -1731,21 +1766,36 @@ pub async fn udp_broadcast_test() { #[tokio::test] #[serial_test::serial] pub async fn foreign_network_forward_nic_data() { - prepare_linux_namespaces(); + let network = prepare_linux_namespaces(); let process_runtime = CoreProcessRuntime::new(); - let center_node_config = get_inst_config("inst1", Some("net_a"), "10.144.144.1", "fd00::1/64"); + let center_node_config = get_inst_config( + "inst1", + Some(network.namespace("net_a")), + "10.144.144.1", + "fd00::1/64", + ); center_node_config .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); let mut center_inst = Instance::new_with_process_runtime(center_node_config, process_runtime.clone()); let mut inst1 = Instance::new_with_process_runtime( - get_inst_config("inst1", Some("net_b"), "10.144.145.1", "fd00:1::1/64"), + get_inst_config( + "inst1", + Some(network.namespace("net_b")), + "10.144.145.1", + "fd00:1::1/64", + ), process_runtime.clone(), ); let mut inst2 = Instance::new_with_process_runtime( - get_inst_config("inst2", Some("net_c"), "10.144.145.2", "fd00:1::2/64"), + get_inst_config( + "inst2", + Some(network.namespace("net_c")), + "10.144.145.2", + "fd00:1::2/64", + ), process_runtime, ); @@ -1770,7 +1820,7 @@ pub async fn foreign_network_forward_nic_data() { .await; wait_for_condition( - || async { ping_test("net_b", "10.144.145.2", None).await }, + || async { ping_test(network.namespace("net_b"), "10.144.145.2", None).await }, Duration::from_secs(5), ) .await; @@ -1851,7 +1901,9 @@ pub(super) fn run_wireguard_client( #[tokio::test] #[serial_test::serial] pub async fn wireguard_vpn_portal(#[values(true, false)] test_v6: bool) { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "tcp", |config| { let identity = config.get_network_identity(); @@ -1883,9 +1935,9 @@ pub async fn wireguard_vpn_portal(#[values(true, false)] test_v6: bool) { .await; if test_v6 { - ping6_test("net_d", "fd12::3", None).await; + ping6_test(network.namespace("net_d"), "fd12::3", None).await; } else { - ping_test("net_d", "10.1.2.3", None).await; + ping_test(network.namespace("net_d"), "10.1.2.3", None).await; } let dst_socket_addr = if test_v6 { @@ -1894,7 +1946,7 @@ pub async fn wireguard_vpn_portal(#[values(true, false)] test_v6: bool) { "10.1.2.3:22121".parse().unwrap() }; - let net_ns = NetNS::new(Some("net_d".into())); + let net_ns = NetNS::new(Some(network.namespace("net_d").into())); let _g = net_ns.guard(); let portal_config = insts[2] .get_global_ctx() @@ -1932,19 +1984,19 @@ pub async fn wireguard_vpn_portal(#[values(true, false)] test_v6: bool) { // ping other node in network wait_for_condition( - || async { ping_test("net_d", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.1", None).await }, Duration::from_secs(5), ) .await; wait_for_condition( - || async { ping_test("net_d", "10.144.144.2", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.2", None).await }, Duration::from_secs(5), ) .await; // ping portal node wait_for_condition( - || async { ping_test("net_d", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.3", None).await }, Duration::from_secs(5), ) .await; @@ -1956,6 +2008,7 @@ pub async fn wireguard_vpn_portal(#[values(true, false)] test_v6: bool) { #[tokio::test] #[serial_test::serial] pub async fn wireguard_vpn_portal_multi_client() { + let network = Arc::new(prepare_linux_namespaces()); use rand::Rng as _; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, @@ -1963,6 +2016,7 @@ pub async fn wireguard_vpn_portal_multi_client() { }; let insts = init_three_node_ex( + &network, "tcp", |config| { let identity = config.get_network_identity(); @@ -2002,8 +2056,8 @@ pub async fn wireguard_vpn_portal_multi_client() { .unwrap(); for (ns, client_name, virtual_ip) in [ - ("net_d", "client-a", "10.144.144.4"), - ("net_f", "client-b", "10.144.144.5"), + (network.namespace("net_d"), "client-a", "10.144.144.4"), + (network.namespace("net_f"), "client-b", "10.144.144.5"), ] { let net_ns = NetNS::new(Some(ns.into())); let _g = net_ns.guard(); @@ -2021,7 +2075,7 @@ pub async fn wireguard_vpn_portal_multi_client() { } // 两个客户端各自 ping mesh 内节点 - for ns in ["net_d", "net_f"] { + for ns in [network.namespace("net_d"), network.namespace("net_f")] { wait_for_condition( || async { ping_test(ns, "10.144.144.1", None).await }, Duration::from_secs(10), @@ -2037,12 +2091,12 @@ pub async fn wireguard_vpn_portal_multi_client() { // 跨客户端互 ping 对方的虚拟 IP,验证 WireGuard 地址与 attached // peer 地址相同且双向数据包无需地址改写。 wait_for_condition( - || async { ping_test("net_d", "10.144.144.5", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.5", None).await }, Duration::from_secs(10), ) .await; wait_for_condition( - || async { ping_test("net_f", "10.144.144.4", None).await }, + || async { ping_test(network.namespace("net_f"), "10.144.144.4", None).await }, Duration::from_secs(10), ) .await; @@ -2052,8 +2106,10 @@ pub async fn wireguard_vpn_portal_multi_client() { let mut buf = vec![0u8; 1024]; rand::thread_rng().fill(&mut buf[..]); let expected = buf.clone(); + let task_network = network.clone(); let echo_task = tokio::spawn(async move { - let net_ns = NetNS::new(Some("net_a".into())); + let network = task_network; + let net_ns = NetNS::new(Some(network.namespace("net_a").into())); let _g = net_ns.guard(); let socket = TcpListener::bind("0.0.0.0:22222").await.unwrap(); let (mut st, addr) = socket.accept().await.unwrap(); @@ -2064,7 +2120,7 @@ pub async fn wireguard_vpn_portal_multi_client() { st.write_all(&rbuf).await.unwrap(); }); { - let net_ns = NetNS::new(Some("net_d".into())); + let net_ns = NetNS::new(Some(network.namespace("net_d").into())); let _g = net_ns.guard(); let mut stream = TcpStream::connect("10.144.144.1:22222").await.unwrap(); stream.write_all(&buf).await.unwrap(); @@ -2103,7 +2159,9 @@ pub async fn wireguard_vpn_portal_multi_client() { #[tokio::test] #[serial_test::serial] pub async fn wireguard_vpn_portal_client_roaming() { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "tcp", |config| { let identity = config.get_network_identity(); @@ -2135,7 +2193,7 @@ pub async fn wireguard_vpn_portal_client_roaming() { .get_vpn_portal_config() .unwrap(); { - let net_ns = NetNS::new(Some("net_d".into())); + let net_ns = NetNS::new(Some(network.namespace("net_d").into())); let _g = net_ns.guard(); let (server_public, client_private) = test_wireguard_keys(&portal_config, "roaming-client").unwrap(); @@ -2152,7 +2210,7 @@ pub async fn wireguard_vpn_portal_client_roaming() { // 客户端在 net_d(源地址 10.1.2.4)上线 wait_for_condition( - || async { ping_test("net_d", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; @@ -2184,24 +2242,24 @@ pub async fn wireguard_vpn_portal_client_roaming() { vec![ "netns".to_owned(), "exec".to_owned(), - "net_d".to_owned(), + network.namespace("net_d").to_owned(), "ip".to_owned(), "addr".to_owned(), "del".to_owned(), "10.1.2.4/24".to_owned(), "dev".to_owned(), - get_guest_veth_name("net_d").to_owned(), + get_guest_veth_name(network.namespace("net_d")).to_owned(), ], vec![ "netns".to_owned(), "exec".to_owned(), - "net_d".to_owned(), + network.namespace("net_d").to_owned(), "ip".to_owned(), "addr".to_owned(), "add".to_owned(), "10.1.2.9/24".to_owned(), "dev".to_owned(), - get_guest_veth_name("net_d").to_owned(), + get_guest_veth_name(network.namespace("net_d")).to_owned(), ], ] { let ret = std::process::Command::new("ip") @@ -2219,7 +2277,7 @@ pub async fn wireguard_vpn_portal_client_roaming() { // 同一个 peer 上更新 endpoint:peer_id 不变说明是同代漫游,客户端没有掉线重连 wait_for_condition( || async { - ping_test("net_d", "10.144.144.1", None).await; + ping_test(network.namespace("net_d"), "10.144.144.1", None).await; let info = insts[2].get_core_instance().vpn_portal_info().await; info.clients.iter().any(|client| { client.name == "roaming-client" @@ -2237,12 +2295,12 @@ pub async fn wireguard_vpn_portal_client_roaming() { // 漫游后连通性保持 wait_for_condition( - || async { ping_test("net_d", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; wait_for_condition( - || async { ping_test("net_d", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.3", None).await }, Duration::from_secs(10), ) .await; @@ -2254,7 +2312,9 @@ pub async fn wireguard_vpn_portal_client_roaming() { #[tokio::test] #[serial_test::serial] pub async fn wireguard_vpn_portal_dynamic_clients() { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "tcp", |config| { let identity = config.get_network_identity(); @@ -2289,7 +2349,7 @@ pub async fn wireguard_vpn_portal_dynamic_clients() { // 初始客户端上线 { - let net_ns = NetNS::new(Some("net_d".into())); + let net_ns = NetNS::new(Some(network.namespace("net_d").into())); let _g = net_ns.guard(); let (server_public, client_private) = test_wireguard_keys(&portal_config, "client-a").unwrap(); @@ -2304,7 +2364,7 @@ pub async fn wireguard_vpn_portal_dynamic_clients() { .unwrap(); } wait_for_condition( - || async { ping_test("net_d", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; @@ -2377,7 +2437,7 @@ pub async fn wireguard_vpn_portal_dynamic_clients() { // 新客户端立即可以握手上线,原客户端不受影响 { - let net_ns = NetNS::new(Some("net_f".into())); + let net_ns = NetNS::new(Some(network.namespace("net_f").into())); let _g = net_ns.guard(); let (server_public, client_private) = test_wireguard_keys(&portal_config, "client-b").unwrap(); @@ -2392,12 +2452,12 @@ pub async fn wireguard_vpn_portal_dynamic_clients() { .unwrap(); } wait_for_condition( - || async { ping_test("net_f", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_f"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; wait_for_condition( - || async { ping_test("net_d", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_d"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; @@ -2422,12 +2482,12 @@ pub async fn wireguard_vpn_portal_dynamic_clients() { .unwrap(); wait_for_condition( - || async { !ping_test("net_d", "10.144.144.1", None).await }, + || async { !ping_test(network.namespace("net_d"), "10.144.144.1", None).await }, Duration::from_secs(20), ) .await; wait_for_condition( - || async { ping_test("net_f", "10.144.144.1", None).await }, + || async { ping_test(network.namespace("net_f"), "10.144.144.1", None).await }, Duration::from_secs(10), ) .await; @@ -2454,6 +2514,7 @@ pub async fn wireguard_vpn_portal_dynamic_clients() { pub async fn socks5_vpn_portal( #[values("10.144.144.1", "10.144.144.3", "10.1.2.4")] dst_addr: &str, ) { + let network = Arc::new(prepare_linux_namespaces()); use rand::Rng as _; use tokio::{ io::{AsyncReadExt, AsyncWriteExt}, @@ -2462,6 +2523,7 @@ pub async fn socks5_vpn_portal( use tokio_socks::tcp::socks5::Socks5Stream; let _insts = init_three_node_ex( + &network, "tcp", |cfg| { if cfg.get_inst_name() == "inst3" { @@ -2480,13 +2542,15 @@ pub async fn socks5_vpn_portal( let buf_clone = buf.clone(); let dst_addr_clone = dst_addr.to_owned(); + let task_network = network.clone(); let task = tokio::spawn(async move { + let network = task_network; let net_ns = if dst_addr_clone == "10.144.144.1" { - NetNS::new(Some("net_a".into())) + NetNS::new(Some(network.namespace("net_a").into())) } else if dst_addr_clone == "10.144.144.3" { - NetNS::new(Some("net_c".into())) + NetNS::new(Some(network.namespace("net_c").into())) } else { - NetNS::new(Some("net_d".into())) + NetNS::new(Some(network.namespace("net_d").into())) }; let _g = net_ns.guard(); @@ -2508,9 +2572,9 @@ pub async fn socks5_vpn_portal( }); let net_ns = if dst_addr == "10.1.2.4" { - NetNS::new(Some("net_c".into())) + NetNS::new(Some(network.namespace("net_c").into())) } else { - NetNS::new(Some("net_a".into())) + NetNS::new(Some(network.namespace("net_a").into())) }; let _g = net_ns.guard(); @@ -2534,27 +2598,47 @@ pub async fn socks5_vpn_portal( #[tokio::test] #[serial_test::serial] pub async fn foreign_network_functional_cluster() { - prepare_linux_namespaces(); + let network = prepare_linux_namespaces(); let process_runtime = CoreProcessRuntime::new(); - let center_node_config1 = get_inst_config("inst1", Some("net_a"), "10.144.144.1", "fd00::1/64"); + let center_node_config1 = get_inst_config( + "inst1", + Some(network.namespace("net_a")), + "10.144.144.1", + "fd00::1/64", + ); center_node_config1 .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); let mut center_inst1 = Instance::new_with_process_runtime(center_node_config1, process_runtime.clone()); - let center_node_config2 = get_inst_config("inst2", Some("net_b"), "10.144.144.2", "fd00::2/64"); + let center_node_config2 = get_inst_config( + "inst2", + Some(network.namespace("net_b")), + "10.144.144.2", + "fd00::2/64", + ); center_node_config2 .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); let mut center_inst2 = Instance::new_with_process_runtime(center_node_config2, process_runtime.clone()); - let inst1_config = get_inst_config("inst1", Some("net_c"), "10.144.145.1", "fd00:2::1/64"); + let inst1_config = get_inst_config( + "inst1", + Some(network.namespace("net_c")), + "10.144.145.1", + "fd00:2::1/64", + ); inst1_config.set_listeners(vec![]); let mut inst1 = Instance::new_with_process_runtime(inst1_config, process_runtime.clone()); let mut inst2 = Instance::new_with_process_runtime( - get_inst_config("inst2", Some("net_d"), "10.144.145.2", "fd00:2::2/64"), + get_inst_config( + "inst2", + Some(network.namespace("net_d")), + "10.144.145.2", + "fd00:2::2/64", + ), process_runtime, ); @@ -2579,7 +2663,7 @@ pub async fn foreign_network_functional_cluster() { ); wait_for_condition( - || async { ping_test("net_c", "10.144.145.2", None).await }, + || async { ping_test(network.namespace("net_c"), "10.144.145.2", None).await }, Duration::from_secs(5), ) .await; @@ -2588,7 +2672,7 @@ pub async fn foreign_network_functional_cluster() { inst1.add_connector_url(center_inst2.ring_listener_url()); tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; wait_for_condition( - || async { ping_test("net_c", "10.144.145.2", None).await }, + || async { ping_test(network.namespace("net_c"), "10.144.145.2", None).await }, Duration::from_secs(5), ) .await; @@ -2600,10 +2684,15 @@ pub async fn foreign_network_functional_cluster() { #[tokio::test] #[serial_test::serial] pub async fn manual_reconnector(#[values(true, false)] is_foreign: bool) { - prepare_linux_namespaces(); + let network = prepare_linux_namespaces(); let process_runtime = CoreProcessRuntime::new(); - let center_node_config = get_inst_config("inst1", Some("net_a"), "10.144.144.1", "fd00::1/64"); + let center_node_config = get_inst_config( + "inst1", + Some(network.namespace("net_a")), + "10.144.144.1", + "fd00::1/64", + ); if is_foreign { center_node_config .set_network_identity(NetworkIdentity::new("center".to_string(), "".to_string())); @@ -2611,12 +2700,22 @@ pub async fn manual_reconnector(#[values(true, false)] is_foreign: bool) { let mut center_inst = Instance::new_with_process_runtime(center_node_config, process_runtime.clone()); - let inst1_config = get_inst_config("inst1", Some("net_b"), "10.144.145.1", "fd00:1::1/64"); + let inst1_config = get_inst_config( + "inst1", + Some(network.namespace("net_b")), + "10.144.145.1", + "fd00:1::1/64", + ); inst1_config.set_listeners(vec![]); let mut inst1 = Instance::new_with_process_runtime(inst1_config, process_runtime.clone()); let mut inst2 = Instance::new_with_process_runtime( - get_inst_config("inst2", Some("net_c"), "10.144.145.2", "fd00:1::2/64"), + get_inst_config( + "inst2", + Some(network.namespace("net_c")), + "10.144.145.2", + "fd00:1::2/64", + ), process_runtime, ); @@ -2657,7 +2756,7 @@ pub async fn manual_reconnector(#[values(true, false)] is_foreign: bool) { assert!(conns_len > 0); wait_for_condition( - || async { ping_test("net_b", "10.144.145.2", None).await }, + || async { ping_test(network.namespace("net_b"), "10.144.145.2", None).await }, Duration::from_secs(5), ) .await; @@ -2675,9 +2774,10 @@ pub async fn port_forward_test( #[values(true, false)] dst_disable_kcp_input: bool, #[values(true, false)] disable_relay_kcp: bool, ) { - prepare_linux_namespaces(); + let network = prepare_linux_namespaces(); let _insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { @@ -2739,8 +2839,8 @@ pub async fn port_forward_test( _tunnel_pingpong_netns_with_timeout( tcp_listener, tcp_connector, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, Duration::from_secs(1), ) @@ -2756,8 +2856,8 @@ pub async fn port_forward_test( _tunnel_pingpong_netns_with_timeout( tcp_listener, tcp_connector, - NetNS::new(Some("net_d".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_d").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, Duration::from_secs(1), ) @@ -2773,8 +2873,8 @@ pub async fn port_forward_test( _tunnel_pingpong_netns_with_timeout( udp_listener, udp_connector, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, Duration::from_secs(1), ) @@ -2790,8 +2890,8 @@ pub async fn port_forward_test( _tunnel_pingpong_netns_with_timeout( udp_listener, udp_connector, - NetNS::new(Some("net_d".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_d").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, Duration::from_secs(1), ) @@ -2811,6 +2911,7 @@ pub async fn port_forward_with_inbound_default_drop_acl_test( #[case] dhcp: bool, #[case] enable_quic_proxy: bool, ) { + let network = prepare_linux_namespaces(); use crate::proto::acl::*; let acl = Acl { @@ -2827,6 +2928,7 @@ pub async fn port_forward_with_inbound_default_drop_acl_test( }; let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { @@ -2881,7 +2983,10 @@ pub async fn port_forward_with_inbound_default_drop_acl_test( .await; } - for (bind_port, server_ns) in [(23456, "net_c"), (23457, "net_d")] { + for (bind_port, server_ns) in [ + (23456, network.namespace("net_c")), + (23457, network.namespace("net_d")), + ] { let tcp_listener = core_tcp_listener(format!("tcp://0.0.0.0:{bind_port}").parse().unwrap()); let tcp_connector = core_tcp_dialer(format!("tcp://127.0.0.1:{bind_port}").parse().unwrap()); @@ -2893,7 +2998,7 @@ pub async fn port_forward_with_inbound_default_drop_acl_test( tcp_listener, tcp_connector, NetNS::new(Some(server_ns.into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_a").into())), buf, Duration::from_secs(1), ) @@ -2922,7 +3027,9 @@ pub async fn port_forward_with_inbound_default_drop_acl_test( #[serial_test::serial] #[tokio::test] pub async fn relay_bps_limit_test(#[values(100, 200, 400, 800)] bps_limit: u64) { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst2" { @@ -2947,8 +3054,8 @@ pub async fn relay_bps_limit_test(#[values(100, 200, 400, 800)] bps_limit: u64) let bps = _tunnel_bench_netns( tcp_listener, tcp_connector, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), ) .await; @@ -2964,7 +3071,9 @@ pub async fn relay_bps_limit_test(#[values(100, 200, 400, 800)] bps_limit: u64) #[serial_test::serial] #[tokio::test] pub async fn instance_recv_bps_limit_test(#[values(100, 800)] bps_limit: u64) { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "tcp", |cfg| { if cfg.get_inst_name() == "inst2" { @@ -2984,8 +3093,8 @@ pub async fn instance_recv_bps_limit_test(#[values(100, 800)] bps_limit: u64) { let bps = _tunnel_bench_netns( tcp_listener, tcp_connector, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), ) .await; @@ -3098,7 +3207,9 @@ async fn avoid_tunnel_loop_back_to_virtual_network( return; } + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "udp", |cfg| { if matches!(cfg.get_inst_name().as_str(), "inst2" | "inst3") { @@ -3145,9 +3256,11 @@ pub async fn acl_rule_test_inbound( #[values(true, false)] enable_kcp_proxy: bool, #[values(true, false)] enable_quic_proxy: bool, ) { + let network = prepare_linux_namespaces(); use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; use rand::Rng; let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { @@ -3242,8 +3355,8 @@ pub async fn acl_rule_test_inbound( let result = _tunnel_pingpong_netns_with_timeout( listener_8081, connector_8081, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_secs(5), ) @@ -3254,8 +3367,8 @@ pub async fn acl_rule_test_inbound( let result = _tunnel_pingpong_netns_with_timeout( listener_8080, connector_8080, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_millis(500), ) @@ -3267,8 +3380,8 @@ pub async fn acl_rule_test_inbound( let result = _tunnel_pingpong_netns_with_timeout( listener_8082, connector_8082, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_b".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_b").into())), buf.clone(), Duration::from_millis(500), ) @@ -3300,8 +3413,8 @@ pub async fn acl_rule_test_inbound( let result = _tunnel_pingpong_netns_with_timeout( listener_8081, connector_8081, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_secs(5), ) @@ -3312,8 +3425,8 @@ pub async fn acl_rule_test_inbound( let result = _tunnel_pingpong_netns_with_timeout( listener_8080, connector_8080, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_millis(500), ) @@ -3334,9 +3447,10 @@ pub async fn acl_rule_test_inbound( #[tokio::test] #[serial_test::serial] pub async fn acl_inbound_default_drop_blocks_bidirectional_icmp() { + let network = prepare_linux_namespaces(); use crate::proto::acl::*; - let insts = init_three_node("udp").await; + let insts = init_three_node(&network, "udp").await; let mut acl = Acl::default(); let mut acl_v1 = AclV1::default(); acl_v1.chains.push(Chain { @@ -3353,19 +3467,19 @@ pub async fn acl_inbound_default_drop_blocks_bidirectional_icmp() { for payload_size in [None, Some(5 * 1024)] { for _ in 0..2 { - assert!(!ping_test("net_a", "10.144.144.2", payload_size).await); - assert!(!ping_test("net_b", "10.144.144.1", payload_size).await); + assert!(!ping_test(network.namespace("net_a"), "10.144.144.2", payload_size).await); + assert!(!ping_test(network.namespace("net_b"), "10.144.144.1", payload_size).await); } } reload_instance_acl(&insts[1], None).await; for payload_size in [None, Some(5 * 1024)] { wait_for_condition( - || async { ping_test("net_a", "10.144.144.2", payload_size).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.2", payload_size).await }, Duration::from_secs(5), ) .await; - assert!(!ping_test("net_b", "10.144.144.1", payload_size).await); + assert!(!ping_test(network.namespace("net_b"), "10.144.144.1", payload_size).await); } drop_insts(insts).await; @@ -3378,10 +3492,12 @@ pub async fn acl_rule_test_subnet_proxy( #[values(true, false)] enable_kcp_proxy: bool, #[values(true, false)] enable_quic_proxy: bool, ) { + let network = Arc::new(prepare_linux_namespaces()); use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; use rand::Rng; let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { @@ -3411,7 +3527,7 @@ pub async fn acl_rule_test_subnet_proxy( // Test IPv4 connectivity wait_for_condition( - || async { ping_test("net_a", "10.1.2.4", None).await }, + || async { ping_test(network.namespace("net_a"), "10.1.2.4", None).await }, Duration::from_secs(5), ) .await; @@ -3492,8 +3608,8 @@ pub async fn acl_rule_test_subnet_proxy( let result = _tunnel_pingpong_netns_with_timeout( listener_8082, connector_8082, - NetNS::new(Some("net_d".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_d").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_secs(5), ) @@ -3504,8 +3620,8 @@ pub async fn acl_rule_test_subnet_proxy( let result = _tunnel_pingpong_netns_with_timeout( listener_8080, connector_8080, - NetNS::new(Some("net_d".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_d").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_millis(500), ) @@ -3520,8 +3636,8 @@ pub async fn acl_rule_test_subnet_proxy( let result = _tunnel_pingpong_netns_with_timeout( listener_8081, connector_8081, - NetNS::new(Some("net_d".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_d").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_millis(500), ) @@ -3551,8 +3667,8 @@ pub async fn acl_rule_test_subnet_proxy( let result = _tunnel_pingpong_netns_with_timeout( listener_8082, connector_8082, - NetNS::new(Some("net_d".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_d").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_secs(5), ) @@ -3563,8 +3679,8 @@ pub async fn acl_rule_test_subnet_proxy( let result = _tunnel_pingpong_netns_with_timeout( listener_8080, connector_8080, - NetNS::new(Some("net_d".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_d").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), Duration::from_millis(500), ) @@ -3580,10 +3696,14 @@ pub async fn acl_rule_test_subnet_proxy( } // 测试 ICMP 到子网代理(应该被拒绝,因为 Any 协议被拒绝) - tokio::spawn(wait_for_condition( - || async { ping_test("net_a", "10.1.2.4", None).await }, - Duration::from_secs(1), - )) + let ping_network = network.clone(); + tokio::spawn(async move { + wait_for_condition( + || async { ping_test(ping_network.namespace("net_a"), "10.1.2.4", None).await }, + Duration::from_secs(1), + ) + .await + }) .await .unwrap_err(); @@ -3592,7 +3712,7 @@ pub async fn acl_rule_test_subnet_proxy( // 验证移除 ACL 后,ICMP 可以正常工作 wait_for_condition( - || async { ping_test("net_a", "10.1.2.4", None).await }, + || async { ping_test(network.namespace("net_a"), "10.1.2.4", None).await }, Duration::from_secs(5), ) .await; @@ -3646,7 +3766,9 @@ pub async fn p2p_only_test( #[values(true, false)] enable_kcp_proxy: bool, #[values(true, false)] enable_quic_proxy: bool, ) { + let network = Arc::new(prepare_linux_namespaces()); let insts = init_three_node_ex( + &network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { @@ -3675,9 +3797,10 @@ pub async fn p2p_only_test( let target_ip = "10.1.2.4"; for target_ip in ["10.144.144.3", target_ip] { + let ping_network = network.clone(); assert_panics_ext( - || async { - subnet_proxy_test_icmp(target_ip, Duration::from_millis(100)).await; + move || async move { + subnet_proxy_test_icmp(&ping_network, target_ip, Duration::from_millis(100)).await; }, !has_p2p_conn, ) @@ -3688,22 +3811,38 @@ pub async fn p2p_only_test( } else { "10.1.2.4" }; + let tcp_network = network.clone(); assert_panics_ext( - || async { - subnet_proxy_test_tcp(listen_ip, target_ip, Duration::from_millis(100)).await; + move || async move { + subnet_proxy_test_tcp( + &tcp_network, + listen_ip, + target_ip, + Duration::from_millis(100), + ) + .await; }, !has_p2p_conn, ) .await; + let udp_network = network.clone(); assert_panics_ext( - || async { - subnet_proxy_test_udp(listen_ip, target_ip, Duration::from_millis(100)).await; + move || async move { + subnet_proxy_test_udp( + &udp_network, + listen_ip, + target_ip, + Duration::from_millis(100), + ) + .await; }, !has_p2p_conn, ) .await; } + + drop_insts(insts).await; } #[rstest::rstest] @@ -3714,6 +3853,7 @@ pub async fn acl_group_base_test( #[values(true, false)] enable_kcp_proxy: bool, #[values(true, false)] enable_quic_proxy: bool, ) { + let network = prepare_linux_namespaces(); use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; use rand::Rng; @@ -3798,6 +3938,7 @@ pub async fn acl_group_base_test( }; let insts = init_three_node_ex( + &network, protocol, move |cfg| { match cfg.get_inst_name().as_str() { @@ -3858,8 +3999,8 @@ pub async fn acl_group_base_test( let result = _tunnel_pingpong_netns_with_timeout( make_listener(8080), make_connector(8080), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), std::time::Duration::from_millis(30000), ) @@ -3878,8 +4019,8 @@ pub async fn acl_group_base_test( let result = _tunnel_pingpong_netns_with_timeout( make_listener(8081), make_connector(8081), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), std::time::Duration::from_millis(30000), ) @@ -3898,8 +4039,8 @@ pub async fn acl_group_base_test( let result = _tunnel_pingpong_netns_with_timeout( make_listener(8080), make_connector(8080), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_b".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_b").into())), buf.clone(), std::time::Duration::from_millis(30000), ) @@ -3918,8 +4059,8 @@ pub async fn acl_group_base_test( let result = _tunnel_pingpong_netns_with_timeout( make_listener(8081), make_connector(8081), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_b".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_b").into())), buf.clone(), std::time::Duration::from_millis(200), ) @@ -3945,7 +4086,8 @@ pub async fn acl_group_base_test( #[tokio::test] #[serial_test::serial] pub async fn lazy_p2p_builds_direct_connection_on_demand() { - let insts = init_lazy_p2p_three_node_ex("udp", |cfg| { + let network = prepare_linux_namespaces(); + let insts = init_lazy_p2p_three_node_ex(&network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { let mut flags = cfg.get_flags(); flags.lazy_p2p = true; @@ -3967,7 +4109,7 @@ pub async fn lazy_p2p_builds_direct_connection_on_demand() { wait_route_cost(&insts[0], inst3_peer_id, 2, Duration::from_secs(5)).await; assert!( - ping_test("net_a", "10.144.144.3", None).await, + ping_test(network.namespace("net_a"), "10.144.144.3", None).await, "initial relay traffic should still succeed" ); @@ -3990,7 +4132,8 @@ pub async fn lazy_p2p_builds_direct_connection_on_demand() { #[tokio::test] #[serial_test::serial] pub async fn need_p2p_overrides_lazy_p2p() { - let insts = init_lazy_p2p_three_node_ex("udp", |cfg| { + let network = prepare_linux_namespaces(); + let insts = init_lazy_p2p_three_node_ex(&network, "udp", |cfg| { let mut flags = cfg.get_flags(); if cfg.get_inst_name() == "inst1" { flags.lazy_p2p = true; @@ -4024,7 +4167,8 @@ pub async fn need_p2p_overrides_lazy_p2p() { #[tokio::test] #[serial_test::serial] pub async fn disable_p2p_still_connects_to_need_p2p_peers() { - let insts = init_lazy_p2p_three_node_ex("udp", |cfg| { + let network = prepare_linux_namespaces(); + let insts = init_lazy_p2p_three_node_ex(&network, "udp", |cfg| { let mut flags = cfg.get_flags(); if cfg.get_inst_name() == "inst1" { flags.disable_p2p = true; @@ -4058,7 +4202,8 @@ pub async fn disable_p2p_still_connects_to_need_p2p_peers() { #[tokio::test] #[serial_test::serial] pub async fn ordinary_nodes_do_not_proactively_connect_to_disable_p2p_peers() { - let insts = init_lazy_p2p_three_node_ex("udp", |cfg| { + let network = prepare_linux_namespaces(); + let insts = init_lazy_p2p_three_node_ex(&network, "udp", |cfg| { if cfg.get_inst_name() == "inst3" { let mut flags = cfg.get_flags(); flags.disable_p2p = true; @@ -4071,7 +4216,7 @@ pub async fn ordinary_nodes_do_not_proactively_connect_to_disable_p2p_peers() { let inst3_peer_id = insts[2].peer_id(); wait_route_cost(&insts[0], inst3_peer_id, 2, Duration::from_secs(5)).await; assert!( - ping_test("net_a", "10.144.144.3", None).await, + ping_test(network.namespace("net_a"), "10.144.144.3", None).await, "relay traffic to disable-p2p peers should still succeed" ); @@ -4093,7 +4238,8 @@ pub async fn ordinary_nodes_do_not_proactively_connect_to_disable_p2p_peers() { #[tokio::test] #[serial_test::serial] pub async fn lazy_p2p_warms_up_before_p2p_only_send() { - let insts = init_lazy_p2p_three_node_ex("udp", |cfg| { + let network = prepare_linux_namespaces(); + let insts = init_lazy_p2p_three_node_ex(&network, "udp", |cfg| { if cfg.get_inst_name() == "inst1" { let mut flags = cfg.get_flags(); flags.lazy_p2p = true; @@ -4107,7 +4253,7 @@ pub async fn lazy_p2p_warms_up_before_p2p_only_send() { let inst3_peer_id = insts[2].peer_id(); wait_route_cost(&insts[0], inst3_peer_id, 2, Duration::from_secs(5)).await; assert!( - !ping_test("net_a", "10.144.144.3", None).await, + !ping_test(network.namespace("net_a"), "10.144.144.3", None).await, "the first send should still fail under p2p_only before direct connectivity exists" ); @@ -4125,7 +4271,7 @@ pub async fn lazy_p2p_warms_up_before_p2p_only_send() { wait_route_cost(&insts[0], inst3_peer_id, 1, Duration::from_secs(10)).await; wait_for_condition( - || async { ping_test("net_a", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.3", None).await }, Duration::from_secs(6), ) .await; @@ -4141,6 +4287,7 @@ pub async fn acl_group_self_test( #[values(true, false)] enable_kcp_proxy: bool, #[values(true, false)] enable_quic_proxy: bool, ) { + let network = prepare_linux_namespaces(); use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; use rand::Rng; @@ -4196,6 +4343,7 @@ pub async fn acl_group_self_test( }; let insts = init_three_node_ex( + &network, protocol, move |cfg| { match cfg.get_inst_name().as_str() { @@ -4256,8 +4404,8 @@ pub async fn acl_group_self_test( let result = _tunnel_pingpong_netns_with_timeout( make_listener(8080), make_connector(8080), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), std::time::Duration::from_millis(30000), ) @@ -4276,8 +4424,8 @@ pub async fn acl_group_self_test( let result = _tunnel_pingpong_netns_with_timeout( make_listener(8080), make_connector(8080), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_b".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_b").into())), buf.clone(), std::time::Duration::from_millis(200), ) @@ -4307,6 +4455,7 @@ pub async fn whitelist_test( #[values("tcp", "udp")] protocol: &str, #[values(true, false)] test_outbound_allow_list: bool, ) { + let network = prepare_linux_namespaces(); let port = 44553; let acl_configured_inst = if test_outbound_allow_list { "inst1" @@ -4314,6 +4463,7 @@ pub async fn whitelist_test( "inst3" }; let insts = init_three_node_ex( + &network, protocol, move |cfg| { let port = if test_outbound_allow_list { 0 } else { port }; @@ -4365,8 +4515,8 @@ pub async fn whitelist_test( _tunnel_pingpong_netns_with_timeout( make_listener(p, port), make_connector(p, port), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), std::time::Duration::from_millis(100), ) @@ -4375,6 +4525,7 @@ pub async fn whitelist_test( } if test_outbound_allow_list { + drop_insts(insts).await; return; } @@ -4384,8 +4535,8 @@ pub async fn whitelist_test( let r = _tunnel_pingpong_netns_with_timeout( make_listener(p, other_port), make_connector(p, other_port), - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), std::time::Duration::from_millis(100), ) @@ -4405,6 +4556,7 @@ pub async fn whitelist_test( #[tokio::test] #[serial_test::serial] pub async fn config_patch_test() { + let network = prepare_linux_namespaces(); use crate::proto::{ api::config::{ ConfigPatchAction, InstanceConfigPatch, PortForwardPatch, ProxyNetworkPatch, @@ -4414,6 +4566,7 @@ pub async fn config_patch_test() { use crate::tunnel::common::tests::_tunnel_pingpong_netns_with_timeout; let insts = init_three_node_ex( + &network, "udp", |cfg| { cfg.set_ipv6(None); @@ -4556,8 +4709,8 @@ pub async fn config_patch_test() { let result = _tunnel_pingpong_netns_with_timeout( tcp_listener, tcp_connector, - NetNS::new(Some("net_c".into())), - NetNS::new(Some("net_a".into())), + NetNS::new(Some(network.namespace("net_c").into())), + NetNS::new(Some(network.namespace("net_a").into())), buf.clone(), std::time::Duration::from_millis(30000), ) @@ -4571,9 +4724,11 @@ pub async fn config_patch_test() { #[tokio::test] #[serial_test::serial] pub async fn config_patch_disable_relay_data_test() { + let network = prepare_linux_namespaces(); use crate::proto::api::config::InstanceConfigPatch; let insts = init_three_node_ex( + &network, "udp", |cfg| { cfg.set_ipv6(None); @@ -4605,7 +4760,7 @@ pub async fn config_patch_disable_relay_data_test() { ); wait_for_condition( - || async { ping_test("net_a", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.3", None).await }, Duration::from_secs(5), ) .await; @@ -4663,7 +4818,7 @@ pub async fn config_patch_disable_relay_data_test() { }, ); assert!( - !ping_test("net_a", "10.144.144.3", None).await, + !ping_test(network.namespace("net_a"), "10.144.144.3", None).await, "traffic from inst1 to inst3 should be blocked while inst2 relay data is disabled" ); @@ -4712,7 +4867,7 @@ pub async fn config_patch_disable_relay_data_test() { .await; wait_for_condition( - || async { ping_test("net_a", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.3", None).await }, Duration::from_secs(5), ) .await; @@ -4747,7 +4902,9 @@ pub fn generate_secure_mode_config() -> SecureModeConfig { #[tokio::test] #[serial_test::serial] pub async fn relay_peer_e2e_encryption(#[values("tcp", "udp")] proto: &str) { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, proto, |cfg| { cfg.set_secure_mode(Some(generate_secure_mode_config())); @@ -4838,7 +4995,7 @@ pub async fn relay_peer_e2e_encryption(#[values("tcp", "udp")] proto: &str) { println!("Starting ping test from net_a to 10.144.144.3..."); assert!( - ping_test("net_a", "10.144.144.3", None).await, + ping_test(network.namespace("net_a"), "10.144.144.3", None).await, "Ping from net_a to inst3 should succeed" ); @@ -4857,11 +5014,11 @@ pub async fn relay_peer_e2e_encryption(#[values("tcp", "udp")] proto: &str) { // Test bidirectional connectivity assert!( - ping_test("net_a", "10.144.144.3", None).await, + ping_test(network.namespace("net_a"), "10.144.144.3", None).await, "Ping from net_a to inst3 should work" ); assert!( - ping_test("net_c", "10.144.144.1", None).await, + ping_test(network.namespace("net_c"), "10.144.144.1", None).await, "Ping from net_c to inst1 should work" ); @@ -4872,7 +5029,9 @@ pub async fn relay_peer_e2e_encryption(#[values("tcp", "udp")] proto: &str) { #[tokio::test] #[serial_test::serial] pub async fn relay_peer_e2e_encryption_udp() { + let network = prepare_linux_namespaces(); let insts = init_three_node_ex( + &network, "udp", |cfg| { cfg.set_secure_mode(Some(generate_secure_mode_config())); @@ -4898,7 +5057,7 @@ pub async fn relay_peer_e2e_encryption_udp() { .await; wait_for_condition( - || async { ping_test("net_a", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.3", None).await }, Duration::from_secs(6), ) .await; @@ -4953,7 +5112,9 @@ pub async fn relay_peer_e2e_encryption_udp() { #[tokio::test] #[serial_test::serial] pub async fn relay_peer_session_cleanup() { + let network = prepare_linux_namespaces(); let mut insts = init_three_node_ex( + &network, "tcp", |cfg| { cfg.set_secure_mode(Some(generate_secure_mode_config())); @@ -4968,7 +5129,7 @@ pub async fn relay_peer_session_cleanup() { let core_1 = insts[0].get_core_instance(); wait_for_condition( - || async { ping_test("net_a", "10.144.144.3", None).await }, + || async { ping_test(network.namespace("net_a"), "10.144.144.3", None).await }, Duration::from_secs(6), ) .await;