From a53f14aa675bff4d64a40756c0962f77f4080e5c Mon Sep 17 00:00:00 2001 From: V Date: Tue, 9 Apr 2024 19:31:54 +0800 Subject: [PATCH] Feat/add docker test for wg (#361) * feat: add docker test for wg --- .../data/config/wg_config/.donoteditthisfile | 7 ++ .../data/config/wg_config/coredns/Corefile | 5 ++ .../data/config/wg_config/peer1/peer1.conf | 11 +++ .../data/config/wg_config/peer1/peer1.png | Bin 0 -> 1135 bytes .../config/wg_config/peer1/presharedkey-peer1 | 1 + .../config/wg_config/peer1/privatekey-peer1 | 1 + .../config/wg_config/peer1/publickey-peer1 | 1 + .../config/wg_config/server/privatekey-server | 1 + .../config/wg_config/server/publickey-server | 1 + .../data/config/wg_config/templates/peer.conf | 11 +++ .../config/wg_config/templates/server.conf | 6 ++ .../data/config/wg_config/wg_confs/wg0.conf | 13 ++++ .../src/proxy/utils/test_utils/consts.rs | 1 + .../proxy/utils/test_utils/docker_runner.rs | 27 ++++++- clash_lib/src/proxy/utils/test_utils/mod.rs | 70 ++++++++++++------ clash_lib/src/proxy/wg/mod.rs | 67 +++++++++++++++++ 16 files changed, 197 insertions(+), 26 deletions(-) create mode 100644 clash/tests/data/config/wg_config/.donoteditthisfile create mode 100644 clash/tests/data/config/wg_config/coredns/Corefile create mode 100644 clash/tests/data/config/wg_config/peer1/peer1.conf create mode 100644 clash/tests/data/config/wg_config/peer1/peer1.png create mode 100644 clash/tests/data/config/wg_config/peer1/presharedkey-peer1 create mode 100644 clash/tests/data/config/wg_config/peer1/privatekey-peer1 create mode 100644 clash/tests/data/config/wg_config/peer1/publickey-peer1 create mode 100644 clash/tests/data/config/wg_config/server/privatekey-server create mode 100644 clash/tests/data/config/wg_config/server/publickey-server create mode 100644 clash/tests/data/config/wg_config/templates/peer.conf create mode 100644 clash/tests/data/config/wg_config/templates/server.conf create mode 100644 clash/tests/data/config/wg_config/wg_confs/wg0.conf diff --git a/clash/tests/data/config/wg_config/.donoteditthisfile b/clash/tests/data/config/wg_config/.donoteditthisfile new file mode 100644 index 000000000..3cfff22c3 --- /dev/null +++ b/clash/tests/data/config/wg_config/.donoteditthisfile @@ -0,0 +1,7 @@ +ORIG_SERVERURL="36.161.169.198" +ORIG_SERVERPORT="51820" +ORIG_PEERDNS="10.13.13.1" +ORIG_PEERS="1" +ORIG_INTERFACE="10.13.13" +ORIG_ALLOWEDIPS="0.0.0.0/0" +ORIG_PERSISTENTKEEPALIVE_PEERS="" diff --git a/clash/tests/data/config/wg_config/coredns/Corefile b/clash/tests/data/config/wg_config/coredns/Corefile new file mode 100644 index 000000000..12da81870 --- /dev/null +++ b/clash/tests/data/config/wg_config/coredns/Corefile @@ -0,0 +1,5 @@ +. { + loop + health + forward . /etc/resolv.conf +} diff --git a/clash/tests/data/config/wg_config/peer1/peer1.conf b/clash/tests/data/config/wg_config/peer1/peer1.conf new file mode 100644 index 000000000..db0e5832a --- /dev/null +++ b/clash/tests/data/config/wg_config/peer1/peer1.conf @@ -0,0 +1,11 @@ +[Interface] +Address = 10.13.13.2 +PrivateKey = KIlDUePHyYwzjgn18przw/ZwPioJhh2aEyhxb/dtCXI= +ListenPort = 51820 +DNS = 10.13.13.1 + +[Peer] +PublicKey = INBZyvB715sA5zatkiX8Jn3Dh5tZZboZ09x4pkr66ig= +PresharedKey = +JmZErvtDT4ZfQequxWhZSydBV+ItqUcPMHUWY1j2yc= +Endpoint = 127.0.0.1:10002 +AllowedIPs = 0.0.0.0/0 diff --git a/clash/tests/data/config/wg_config/peer1/peer1.png b/clash/tests/data/config/wg_config/peer1/peer1.png new file mode 100644 index 0000000000000000000000000000000000000000..da119881f8d4cb0b8bcf91dab7eb319fcebb1560 GIT binary patch literal 1135 zcmV-#1d#iQP))r)@$b2b{7P+SOt|w4wyF6dB zNLqAiZ`yX!cHtZf%S)OuOJ}ch7fz$BqsYC8$lm8>l>HPU8YK=jjyV$Qdu_(Be+TO4 z$nrI{|NHw+<{WVc_#zjbFYkwUp;zKz)zzOCPv0R;&S6D{TNAe@Qf{t@D--u?pN|56yesowFEt?iY zHOYNi=rzyJaO*+jOU*0r4Q5f?()x}BKhjBVM=L}IV?bcb#t{B)zAm(ygjyTPD!tYE zPMmgX>@|jXx$U5xA zyp1d|b4sbTVc^$eIMB=L2oERwEHC7a&oER(H)79aKYgLnWGU+t=`(e?5pfubEktSq z9&v8_-`?v(#o{20c}((bslp7&Jm5!I&}az~YcZu65o)D(pFlz-Qrc$bmblEiA(H%v z<5&u8f+7g)(U%(6;77;$Y)$rc?D%V#Z{;&B`_QqQH~e#47mK^Atw^6~*@p@$qGjVh z)?tq&PTqT^8(F5XqiMdf02ca1^Il=xsKY1|IlBe{{ZIL>kMkRG}-_F002ovPDHLkV1mhw BE))O& literal 0 HcmV?d00001 diff --git a/clash/tests/data/config/wg_config/peer1/presharedkey-peer1 b/clash/tests/data/config/wg_config/peer1/presharedkey-peer1 new file mode 100644 index 000000000..9ad12fd4f --- /dev/null +++ b/clash/tests/data/config/wg_config/peer1/presharedkey-peer1 @@ -0,0 +1 @@ ++JmZErvtDT4ZfQequxWhZSydBV+ItqUcPMHUWY1j2yc= diff --git a/clash/tests/data/config/wg_config/peer1/privatekey-peer1 b/clash/tests/data/config/wg_config/peer1/privatekey-peer1 new file mode 100644 index 000000000..0809131e2 --- /dev/null +++ b/clash/tests/data/config/wg_config/peer1/privatekey-peer1 @@ -0,0 +1 @@ +KIlDUePHyYwzjgn18przw/ZwPioJhh2aEyhxb/dtCXI= diff --git a/clash/tests/data/config/wg_config/peer1/publickey-peer1 b/clash/tests/data/config/wg_config/peer1/publickey-peer1 new file mode 100644 index 000000000..5dc898594 --- /dev/null +++ b/clash/tests/data/config/wg_config/peer1/publickey-peer1 @@ -0,0 +1 @@ +H7NHC22d44AhrJf7BSzbNJrW1wiTDCRYNfP0rQicM3g= diff --git a/clash/tests/data/config/wg_config/server/privatekey-server b/clash/tests/data/config/wg_config/server/privatekey-server new file mode 100644 index 000000000..cf2a8a0f2 --- /dev/null +++ b/clash/tests/data/config/wg_config/server/privatekey-server @@ -0,0 +1 @@ +CA7cMGAh7BF/kD000ZRN+ZXDe1SGd1Z3kqNjQxnCAmQ= diff --git a/clash/tests/data/config/wg_config/server/publickey-server b/clash/tests/data/config/wg_config/server/publickey-server new file mode 100644 index 000000000..a2936ff96 --- /dev/null +++ b/clash/tests/data/config/wg_config/server/publickey-server @@ -0,0 +1 @@ +INBZyvB715sA5zatkiX8Jn3Dh5tZZboZ09x4pkr66ig= diff --git a/clash/tests/data/config/wg_config/templates/peer.conf b/clash/tests/data/config/wg_config/templates/peer.conf new file mode 100644 index 000000000..196540e87 --- /dev/null +++ b/clash/tests/data/config/wg_config/templates/peer.conf @@ -0,0 +1,11 @@ +[Interface] +Address = ${CLIENT_IP} +PrivateKey = $(cat /config/${PEER_ID}/privatekey-${PEER_ID}) +ListenPort = 10002 +DNS = ${PEERDNS} + +[Peer] +PublicKey = $(cat /config/server/publickey-server) +PresharedKey = $(cat /config/${PEER_ID}/presharedkey-${PEER_ID}) +Endpoint = ${SERVERURL}:${SERVERPORT} +AllowedIPs = ${ALLOWEDIPS} diff --git a/clash/tests/data/config/wg_config/templates/server.conf b/clash/tests/data/config/wg_config/templates/server.conf new file mode 100644 index 000000000..8d1a3fda2 --- /dev/null +++ b/clash/tests/data/config/wg_config/templates/server.conf @@ -0,0 +1,6 @@ +[Interface] +Address = ${INTERFACE}.1 +ListenPort = 10002 +PrivateKey = $(cat /config/server/privatekey-server) +PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE +PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE diff --git a/clash/tests/data/config/wg_config/wg_confs/wg0.conf b/clash/tests/data/config/wg_config/wg_confs/wg0.conf new file mode 100644 index 000000000..4ab6eb1e3 --- /dev/null +++ b/clash/tests/data/config/wg_config/wg_confs/wg0.conf @@ -0,0 +1,13 @@ +[Interface] +Address = 10.13.13.1 +ListenPort = 10002 +PrivateKey = CA7cMGAh7BF/kD000ZRN+ZXDe1SGd1Z3kqNjQxnCAmQ= +PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE +PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE + +[Peer] +# peer1 +PublicKey = H7NHC22d44AhrJf7BSzbNJrW1wiTDCRYNfP0rQicM3g= +PresharedKey = +JmZErvtDT4ZfQequxWhZSydBV+ItqUcPMHUWY1j2yc= +AllowedIPs = 10.13.13.2/32 + diff --git a/clash_lib/src/proxy/utils/test_utils/consts.rs b/clash_lib/src/proxy/utils/test_utils/consts.rs index 58ef48123..8d81f660a 100644 --- a/clash_lib/src/proxy/utils/test_utils/consts.rs +++ b/clash_lib/src/proxy/utils/test_utils/consts.rs @@ -2,6 +2,7 @@ pub const LOCAL_ADDR: &str = "127.0.0.1"; pub const EXAMPLE_REQ: &[u8] = b"GET / HTTP/1.1\r\nHost: example.com\r\nAccept: */*\r\n\r\n"; pub const EXAMLE_RESP_200: &[u8] = b"HTTP/1.1 200"; +pub const IMAGE_WG: &str = "lscr.io/linuxserver/wireguard:latest"; pub const IMAGE_SS_RUST: &str = "ghcr.io/shadowsocks/ssserver-rust:latest"; pub const IMAGE_SHADOW_TLS: &str = "ghcr.io/ihciah/shadow-tls:latest"; pub const IMAGE_TROJAN_GO: &str = "p4gefau1t/trojan-go:latest"; diff --git a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs index 6a0a4bb3a..fc2a782e6 100644 --- a/clash_lib/src/proxy/utils/test_utils/docker_runner.rs +++ b/clash_lib/src/proxy/utils/test_utils/docker_runner.rs @@ -188,9 +188,9 @@ impl DockerTestRunnerBuilder { pub fn port(mut self, port: u16) -> Self { self._server_port = port; self.exposed_ports = vec![format!("{}/tcp", port), format!("{}/udp", port)]; - let mounts = self.host_config.mounts.take(); - self.host_config = get_host_config(port); - self.host_config.mounts = mounts; + let new_host_config = get_host_config(port); + self.host_config.network_mode = new_host_config.network_mode; + self.host_config.port_bindings = new_host_config.port_bindings; self } @@ -227,6 +227,27 @@ impl DockerTestRunnerBuilder { self } + pub fn sysctls(mut self, sysctls: &[(&str, &str)]) -> Self { + self.host_config.sysctls = Some( + sysctls + .into_iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect::>(), + ); + + self + } + + pub fn cap_add(mut self, caps: &[&str]) -> Self { + self.host_config.cap_add = Some(caps.iter().map(|x| x.to_string()).collect()); + self + } + + pub fn net_mode(mut self, mode: &str) -> Self { + self.host_config.network_mode = Some(mode.to_string()); + self + } + pub async fn build(self) -> anyhow::Result { tracing::trace!("building docker test runner: {:?}", &self); let exposed = self diff --git a/clash_lib/src/proxy/utils/test_utils/mod.rs b/clash_lib/src/proxy/utils/test_utils/mod.rs index 5dc103ddb..da04bcdac 100644 --- a/clash_lib/src/proxy/utils/test_utils/mod.rs +++ b/clash_lib/src/proxy/utils/test_utils/mod.rs @@ -176,37 +176,61 @@ pub async fn latency_test( Ok(end_time.duration_since(start_time)) } -pub async fn run_default_test_suites_and_cleanup( +#[derive(Clone, Copy)] +pub enum Suite { + PingPong, + Latency, +} + +pub const DEFAULT_TEST_SUITES: &[Suite] = &[Suite::PingPong, Suite::Latency]; + +pub async fn run_test_suites_and_cleanup( handler: Arc, docker_test_runner: impl RunAndCleanup, + suites: &[Suite], ) -> anyhow::Result<()> { + let suites = suites.to_owned(); docker_test_runner .run_and_cleanup(async move { - let rv = ping_pong_test(handler.clone(), 10001).await; - if rv.is_err() { - tracing::error!("ping_pong_test failed: {:?}", rv); - return rv; - } else { - tracing::info!("ping_pong_test success"); - } - - let rv = latency_test( - handler, - LatencyTestOption { - dst: SocksAddr::Domain("example.com".to_owned(), 80), - req: consts::EXAMPLE_REQ, - expected_resp: consts::EXAMLE_RESP_200, - read_exact: true, - }, - ) - .await; - if let Err(e) = rv { - return Err(e); - } else { - tracing::info!("latency test success: {}", rv.unwrap().as_millis()); + for suite in suites { + match suite { + Suite::PingPong => { + let rv = ping_pong_test(handler.clone(), 10001).await; + if rv.is_err() { + tracing::error!("ping_pong_test failed: {:?}", rv); + return rv; + } else { + tracing::info!("ping_pong_test success"); + } + } + Suite::Latency => { + let rv = latency_test( + handler.clone(), + LatencyTestOption { + dst: SocksAddr::Domain("example.com".to_owned(), 80), + req: consts::EXAMPLE_REQ, + expected_resp: consts::EXAMLE_RESP_200, + read_exact: true, + }, + ) + .await; + if rv.is_err() { + return Err(rv.unwrap_err()); + } else { + tracing::info!("latency test success: {}", rv.unwrap().as_millis()); + } + } + } } Ok(()) }) .await } + +pub async fn run_default_test_suites_and_cleanup( + handler: Arc, + docker_test_runner: impl RunAndCleanup, +) -> anyhow::Result<()> { + run_test_suites_and_cleanup(handler, docker_test_runner, DEFAULT_TEST_SUITES).await +} diff --git a/clash_lib/src/proxy/wg/mod.rs b/clash_lib/src/proxy/wg/mod.rs index d070dc64e..9cc63a0aa 100644 --- a/clash_lib/src/proxy/wg/mod.rs +++ b/clash_lib/src/proxy/wg/mod.rs @@ -290,3 +290,70 @@ impl OutboundHandler for Handler { Ok(Box::new(chained)) } } + +#[cfg(all(test, not(ci)))] +mod tests { + + use crate::proxy::utils::test_utils::docker_runner::DockerTestRunnerBuilder; + use crate::proxy::utils::test_utils::{config_helper::test_config_base_dir, Suite}; + + use super::super::utils::test_utils::{consts::*, docker_runner::DockerTestRunner}; + use crate::proxy::utils::test_utils::run_test_suites_and_cleanup; + + use super::*; + + // see: https://github.com/linuxserver/docker-wireguard?tab=readme-ov-file#usage + // we shouldn't run the wireguard server with host mode, or + // the sysctl of `net.ipv4.conf.all.src_valid_mark` will fail + async fn get_runner() -> anyhow::Result { + let test_config_dir = test_config_base_dir(); + let wg_config = test_config_dir.join("wg_config"); + // the following configs is in accordance with the config in `wg_config` dir + DockerTestRunnerBuilder::new() + .image(IMAGE_WG) + .env(&[ + "PUID=1000", + "PGID=1000", + "TZ=Etc/UTC", + "SERVERPORT=10002", + "PEERS=1", + "PEERDNS=auto", + "INTERNAL_SUBNET=10.13.13.0", + "ALLOWEDIPS=0.0.0.0/0", + ]) + .mounts(&[(wg_config.to_str().unwrap(), "/config")]) + .sysctls(&[("net.ipv4.conf.all.src_valid_mark", "1")]) + .cap_add(&["NET_ADMIN"]) + .net_mode("bridge") // the default network mode for testing is `host` + .build() + .await + } + + #[tokio::test] + #[serial_test::serial] + async fn test_wg() -> anyhow::Result<()> { + let opts = HandlerOpts { + name: "wg".to_owned(), + common_opts: CommonOption::default(), + server: "127.0.0.1".to_owned(), + port: 10002, + ip: Ipv4Addr::new(10, 13, 13, 2), + ipv6: None, + private_key: "KIlDUePHyYwzjgn18przw/ZwPioJhh2aEyhxb/dtCXI=".to_owned(), + public_key: "INBZyvB715sA5zatkiX8Jn3Dh5tZZboZ09x4pkr66ig=".to_owned(), + preshared_key: Some("+JmZErvtDT4ZfQequxWhZSydBV+ItqUcPMHUWY1j2yc=".to_owned()), + remote_dns_resolve: false, + dns: None, + mtu: Some(1000), + udp: true, + allowed_ips: Some(vec!["0.0.0.0/0".to_owned()]), + reserved_bits: None, + }; + let handler = Handler::new(opts); + + // cannot run the ping pong test, since the wireguard server is running on bridge network mode + // and the `net.ipv4.conf.all.src_valid_mark` is not supported in the host network mode + // the latency test should be enough + run_test_suites_and_cleanup(handler, get_runner().await?, &[Suite::Latency]).await + } +}