From b3314f5abc98f6517f9f8c1dbf1a838085d351e6 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Mon, 26 Feb 2024 00:50:23 +0100 Subject: [PATCH 001/196] Properly close streams --- src/lib.rs | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ce816db..c37be76 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,7 +202,7 @@ async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc>, ) -> crate::Result<()> { @@ -211,21 +211,25 @@ async fn handle_tcp_session( let session_info = proxy_handler.lock().await.get_session_info(); log::info!("Beginning {}", session_info); - let _ = handle_proxy_session(&mut server, proxy_handler).await?; + if let Err(e) = handle_proxy_session(&mut server, proxy_handler).await { + tcp_stack.shutdown().await?; + return Err(e); + } let (mut t_rx, mut t_tx) = tokio::io::split(tcp_stack); let (mut s_rx, mut s_tx) = tokio::io::split(server); - let result = tokio::join! { - tokio::io::copy(&mut t_rx, &mut s_tx), - tokio::io::copy(&mut s_rx, &mut t_tx), - }; - let result = match result { - (Ok(t), Ok(s)) => Ok((t, s)), - (Err(e), _) | (_, Err(e)) => Err(e), - }; - - log::info!("Ending {} with {:?}", session_info, result); + for _ in 0..2 { + tokio::select! { + _ = tokio::io::copy(&mut t_rx, &mut s_tx) => { + s_tx.shutdown().await?; + }, + _ = tokio::io::copy(&mut s_rx, &mut t_tx) => { + t_tx.shutdown().await?; + }, + } + } + log::info!("Ending {}", session_info); Ok(()) } From 01a0d9164d47ab20363ce82a2ca1b9966479e204 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Mon, 26 Feb 2024 01:24:22 +0100 Subject: [PATCH 002/196] Fix banner bug with HTTP proxies --- src/http.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/http.rs b/src/http.rs index e225940..73cf9b6 100644 --- a/src/http.rs +++ b/src/http.rs @@ -172,6 +172,8 @@ impl HttpConnection { return Ok(()); } + let header_size = self.counter; + self.counter = 0; self.crlf_state = 0; @@ -192,7 +194,9 @@ impl HttpConnection { if status_code == 200 { // Connection successful self.state = HttpState::Established; - self.server_inbuf.clear(); + // The server may have sent a banner already (SMTP, SSH, etc.). + // Therefore, server_inbuf must retain this data. + self.server_inbuf.drain(0..header_size); return self.state_change().await; } From 4ab6f1a9bc6df8572fa221f5beddaf56666c5d44 Mon Sep 17 00:00:00 2001 From: Ebrahim Tahernejad Date: Thu, 29 Feb 2024 07:08:44 +0330 Subject: [PATCH 003/196] XCFramework build for apple (#93) --- README.md | 6 ++++ apple/tun2proxy/Tun2proxyWrapper.m | 4 +-- build-apple.sh | 44 ++++++++++++++++++++++++++++++ cbindgen.toml | 7 +++-- src/{ios.rs => apple.rs} | 12 ++++++-- src/desktop_api.rs | 11 ++++++-- src/lib.rs | 5 +++- src/mobile_api.rs | 11 +++++--- 8 files changed, 85 insertions(+), 15 deletions(-) create mode 100755 build-apple.sh rename src/{ios.rs => apple.rs} (67%) diff --git a/README.md b/README.md index 15ab769..952ed3a 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ Clone the repository and `cd` into the project folder. Then run the following: cargo build --release ``` +### Building Framework for Apple Devices +To build an XCFramework for macOS and iOS, run the following: +``` +./build-apple.sh +``` + ## Installation ### Install from binary diff --git a/apple/tun2proxy/Tun2proxyWrapper.m b/apple/tun2proxy/Tun2proxyWrapper.m index dd8c75d..d6a088e 100644 --- a/apple/tun2proxy/Tun2proxyWrapper.m +++ b/apple/tun2proxy/Tun2proxyWrapper.m @@ -19,11 +19,11 @@ verbose:(bool)verbose { ArgDns dns_strategy = dns_over_tcp ? OverTcp : Direct; ArgVerbosity v = verbose ? Trace : Info; - tun2proxy_run_with_fd(proxy_url.UTF8String, tun_fd, tun_mtu, dns_strategy, v); + tun2proxy_with_fd_run(proxy_url.UTF8String, tun_fd, tun_mtu, dns_strategy, v); } + (void)shutdown { - tun2proxy_stop(); + tun2proxy_with_fd_stop(); } @end diff --git a/build-apple.sh b/build-apple.sh new file mode 100755 index 0000000..7470b9a --- /dev/null +++ b/build-apple.sh @@ -0,0 +1,44 @@ +#! /bin/sh + +echo "Setting up the rust environment..." +rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios x86_64-apple-darwin aarch64-apple-darwin +cargo install cbindgen + +echo "Building..." +cargo build --release --target x86_64-apple-darwin +cargo build --release --target aarch64-apple-darwin +cargo build --release --target aarch64-apple-ios +cargo build --release --target x86_64-apple-ios +cargo build --release --target aarch64-apple-ios-sim + +echo "Generating includes..." +mkdir -p target/include/ +cbindgen --config cbindgen.toml -l C -o target/include/tun2proxy.h +cat > target/include/module.modulemap < c_int { +pub unsafe extern "C" fn tun2proxy_with_fd_stop() -> c_int { crate::mobile_api::mobile_stop() } diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 61dab33..5d8bb69 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -13,8 +13,15 @@ static TUN_QUIT: std::sync::Mutex> = /// # Safety /// /// Run the tun2proxy component with some arguments. +/// Parameters: +/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" +/// - tun: the tun device name, e.g. "utun5" +/// - bypass: the bypass ip, e.g. "123.45.67.89" +/// - dns_strategy: the dns strategy, see ArgDns enum +/// - root_privilege: whether to run with root privilege +/// - verbosity: the verbosity level, see ArgVerbosity enum #[no_mangle] -pub unsafe extern "C" fn tun2proxy_run_with_name( +pub unsafe extern "C" fn tun2proxy_with_name_run( proxy_url: *const c_char, tun: *const c_char, bypass: *const c_char, @@ -142,7 +149,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can /// /// Shutdown the tun2proxy component. #[no_mangle] -pub unsafe extern "C" fn tun2proxy_stop() -> c_int { +pub unsafe extern "C" fn tun2proxy_with_name_stop() -> c_int { if let Ok(lock) = TUN_QUIT.lock() { if let Some(shutdown_token) = lock.as_ref() { shutdown_token.cancel(); diff --git a/src/lib.rs b/src/lib.rs index c37be76..9020cb3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,11 @@ pub use desktop_api::desktop_run_async; #[cfg(any(target_os = "ios", target_os = "android"))] pub use mobile_api::{desktop_run_async, mobile_run, mobile_stop}; +#[cfg(target_os = "macos")] +pub use mobile_api::{mobile_run, mobile_stop}; + mod android; +mod apple; mod args; mod desktop_api; mod directions; @@ -37,7 +41,6 @@ mod dns; mod dump_logger; mod error; mod http; -mod ios; mod mobile_api; mod proxy_handler; mod session_info; diff --git a/src/mobile_api.rs b/src/mobile_api.rs index bf2573c..6d04390 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -1,4 +1,4 @@ -#![cfg(any(target_os = "ios", target_os = "android"))] +#![cfg(any(target_os = "ios", target_os = "android", target_os = "macos"))] use crate::Args; use std::os::raw::c_int; @@ -7,6 +7,7 @@ static TUN_QUIT: std::sync::Mutex> = /// Dummy function to make the build pass. #[doc(hidden)] +#[cfg(not(target_os = "macos"))] pub async fn desktop_run_async(_: Args, _: tokio_util::sync::CancellationToken) -> std::io::Result<()> { Ok(()) } @@ -28,11 +29,13 @@ pub fn mobile_run(args: Args, tun_mtu: u16) -> c_int { #[cfg(unix)] if let Some(fd) = args.tun_fd { config.raw_fd(fd); - } else { - config.name(&args.tun); + } else if let Some(ref tun) = args.tun { + config.tun_name(tun); } #[cfg(windows)] - config.name(&args.tun); + if let Some(ref tun) = args.tun { + config.tun_name(tun); + } let device = tun2::create_as_async(&config).map_err(std::io::Error::from)?; let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); From 7e7aadb04b52c61412b917d8ed695f8f7801d97e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 3 Mar 2024 19:49:10 +0800 Subject: [PATCH 004/196] tun shutdown issues. (#97) --- Cargo.toml | 4 ++-- src/bin/main.rs | 4 ++-- src/lib.rs | 28 +++++++++++++++++----------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 950c1a1..f82ae9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ crate-type = ["staticlib", "cdylib", "lib"] [dependencies] async-recursion = "1.0" async-trait = "0.1" -base64 = { version = "0.21" } +base64 = { version = "0.22" } chrono = "0.4" clap = { version = "4.5", features = ["derive", "wrap_help", "color"] } ctrlc2 = { version = "3.5", features = ["tokio", "termination"] } @@ -32,7 +32,7 @@ tokio = { version = "1.36", features = ["full"] } tokio-util = "0.7" tproxy-config = { version = "2.0", features = ["log"] } trust-dns-proto = "0.23" -tun2 = { version = "1.1", features = ["async"] } +tun2 = { version = "1.2", features = ["async"] } udp-stream = { version = "0.0", default-features = false } unicase = "2.7" url = "2.5" diff --git a/src/bin/main.rs b/src/bin/main.rs index 0293235..7be56ab 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -5,8 +5,8 @@ async fn main() -> Result<(), BoxError> { dotenvy::dotenv().ok(); let args = Args::parse_args(); - // let default = format!("{}={:?}", module_path!(), args.verbosity); - let default = format!("{:?}", args.verbosity); + // let default = format!("{}={:?},trust_dns_proto=warn", module_path!(), args.verbosity); + let default = format!("{:?},trust_dns_proto=warn", args.verbosity); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); let shutdown_token = tokio_util::sync::CancellationToken::new(); diff --git a/src/lib.rs b/src/lib.rs index 9020cb3..61cfdc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -222,17 +222,23 @@ async fn handle_tcp_session( let (mut t_rx, mut t_tx) = tokio::io::split(tcp_stack); let (mut s_rx, mut s_tx) = tokio::io::split(server); - for _ in 0..2 { - tokio::select! { - _ = tokio::io::copy(&mut t_rx, &mut s_tx) => { - s_tx.shutdown().await?; - }, - _ = tokio::io::copy(&mut s_rx, &mut t_tx) => { - t_tx.shutdown().await?; - }, - } - } - log::info!("Ending {}", session_info); + let res = tokio::join!( + async move { + let r = tokio::io::copy(&mut t_rx, &mut s_tx).await; + if let Err(err) = s_tx.shutdown().await { + log::trace!("{} s_tx shutdown error {}", session_info, err); + } + r + }, + async move { + let r = tokio::io::copy(&mut s_rx, &mut t_tx).await; + if let Err(err) = t_tx.shutdown().await { + log::trace!("{} t_tx shutdown error {}", session_info, err); + } + r + }, + ); + log::info!("Ending {} with {:?}", session_info, res); Ok(()) } From fb7b6862e557c772374312fbab14a8832eb1eedd Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:33:47 +0800 Subject: [PATCH 005/196] tst (#99) --- .github/workflows/publish-exe.yml | 3 +++ src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 5e9e500..d51159b 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -69,6 +69,9 @@ jobs: powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib + ./build-apple.sh + mv ./target/Tun2Proxy.xcframework . + zip -r mypubdir4/tun2proxy-${{ matrix.target }}.zip ./Tun2Proxy.xcframework/ elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so fi diff --git a/src/lib.rs b/src/lib.rs index 61cfdc3..02a0b63 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,7 +171,7 @@ where Ok(proxy_handler) => { tokio::spawn(async move { if let Err(err) = handle_udp_associate_session(udp, server_addr, proxy_handler, ipv6_enabled).await { - log::error!("{} error \"{}\"", info, err); + log::trace!("{} reason \"{}\"", info, err); } log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); }); From d3e77e6c174934401ce3007dc7d1b79d4ee0afa5 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 6 Mar 2024 12:22:41 +0800 Subject: [PATCH 006/196] Bump version 0.2.12 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f82ae9f..af524f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.11" +version = "0.2.12" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 989406d00caeb1b3666d278ca6103d85f092f871 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:08:28 +0800 Subject: [PATCH 007/196] script issues --- .github/workflows/publish-exe.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index d51159b..b7ad246 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -69,9 +69,11 @@ jobs: powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib - ./build-apple.sh - mv ./target/Tun2Proxy.xcframework . - zip -r mypubdir4/tun2proxy-${{ matrix.target }}.zip ./Tun2Proxy.xcframework/ + if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then + ./build-apple.sh + mv ./target/Tun2Proxy.xcframework . + zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./Tun2Proxy.xcframework/ + fi elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so fi From 7cb251c190b141877ba4af4b63df887b8c2e74b5 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 6 Mar 2024 18:01:02 +0800 Subject: [PATCH 008/196] refine TUN_QUIT --- src/desktop_api.rs | 9 ++------- src/mobile_api.rs | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 5d8bb69..0e85803 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -74,11 +74,6 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( }, }; - // release shutdown token before exit. - if let Ok(mut lock) = TUN_QUIT.lock() { - let _ = lock.take(); - } - exit_code } @@ -150,8 +145,8 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can /// Shutdown the tun2proxy component. #[no_mangle] pub unsafe extern "C" fn tun2proxy_with_name_stop() -> c_int { - if let Ok(lock) = TUN_QUIT.lock() { - if let Some(shutdown_token) = lock.as_ref() { + if let Ok(mut lock) = TUN_QUIT.lock() { + if let Some(shutdown_token) = lock.take() { shutdown_token.cancel(); return 0; } diff --git a/src/mobile_api.rs b/src/mobile_api.rs index 6d04390..8e67cef 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -15,12 +15,16 @@ pub async fn desktop_run_async(_: Args, _: tokio_util::sync::CancellationToken) pub fn mobile_run(args: Args, tun_mtu: u16) -> c_int { let shutdown_token = tokio_util::sync::CancellationToken::new(); { - let mut lock = TUN_QUIT.lock().unwrap(); - if lock.is_some() { - log::error!("tun2proxy already started"); - return -1; + if let Ok(mut lock) = TUN_QUIT.lock() { + if lock.is_some() { + log::error!("tun2proxy already started"); + return -1; + } + *lock = Some(shutdown_token.clone()); + } else { + log::error!("failed to lock tun2proxy quit token"); + return -2; } - *lock = Some(shutdown_token.clone()); } let block = async move { @@ -57,16 +61,12 @@ pub fn mobile_run(args: Args, tun_mtu: u16) -> c_int { }, }; - // release shutdown token before exit. - let mut lock = TUN_QUIT.lock().unwrap(); - let _ = lock.take(); - exit_code } pub fn mobile_stop() -> c_int { - if let Ok(lock) = TUN_QUIT.lock() { - if let Some(shutdown_token) = lock.as_ref() { + if let Ok(mut lock) = TUN_QUIT.lock() { + if let Some(shutdown_token) = lock.take() { shutdown_token.cancel(); return 0; } From bd96807bf84fcdeff2a1238436bde77b3fe8e6d1 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 11 Mar 2024 08:57:18 +0800 Subject: [PATCH 009/196] minor changes --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 02a0b63..4b7e1ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -171,7 +171,7 @@ where Ok(proxy_handler) => { tokio::spawn(async move { if let Err(err) = handle_udp_associate_session(udp, server_addr, proxy_handler, ipv6_enabled).await { - log::trace!("{} reason \"{}\"", info, err); + log::info!("Ending {} with \"{}\"", info, err); } log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); }); From 98419870317a694d5e7704281572c06e9906d367 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 13 Mar 2024 13:07:32 +0800 Subject: [PATCH 010/196] fix #101 --- build.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/build.rs b/build.rs index 8cfb150..a89c129 100644 --- a/build.rs +++ b/build.rs @@ -49,17 +49,16 @@ fn get_cargo_target_dir() -> Result Result> { - let dll_path = if cfg!(target_arch = "x86") { - "wintun/bin/x86/wintun.dll" - } else if cfg!(target_arch = "x86_64") { - "wintun/bin/amd64/wintun.dll" - } else if cfg!(target_arch = "arm") { - "wintun/bin/arm/wintun.dll" - } else if cfg!(target_arch = "aarch64") { - "wintun/bin/arm64/wintun.dll" - } else { - return Err("Unsupported architecture".into()); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH")?; + + let dll_path = match target_arch.as_str() { + "x86" => "wintun/bin/x86/wintun.dll", + "x86_64" => "wintun/bin/amd64/wintun.dll", + "arm" => "wintun/bin/arm/wintun.dll", + "aarch64" => "wintun/bin/arm64/wintun.dll", + _ => return Err("Unsupported architecture".into()), }; + Ok(dll_path.into()) } From 3a156f58372157530ee7cf416b4cd1dc16fed2fe Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:17:27 +0800 Subject: [PATCH 011/196] Bump version 0.2.13 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index af524f7..4e3c045 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.12" +version = "0.2.13" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From c9272609b844374ec3d7160d11b0b9bf928a6334 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 14 Mar 2024 20:28:33 +0800 Subject: [PATCH 012/196] building script --- .github/workflows/publish-exe.yml | 3 +-- .gitignore | 1 + build-apple.sh | 26 ++++++++++++++------------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index b7ad246..8125396 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -71,8 +71,7 @@ jobs: zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then ./build-apple.sh - mv ./target/Tun2Proxy.xcframework . - zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./Tun2Proxy.xcframework/ + zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/ fi elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so diff --git a/.gitignore b/.gitignore index 2ac3387..59800e0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +tun2proxy.xcframework/ .env project.xcworkspace/ xcuserdata/ diff --git a/build-apple.sh b/build-apple.sh index 7470b9a..3745079 100755 --- a/build-apple.sh +++ b/build-apple.sh @@ -15,7 +15,7 @@ echo "Generating includes..." mkdir -p target/include/ cbindgen --config cbindgen.toml -l C -o target/include/tun2proxy.h cat > target/include/module.modulemap < Date: Fri, 15 Mar 2024 16:52:55 +0800 Subject: [PATCH 013/196] update deps --- Cargo.toml | 2 +- src/desktop_api.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4e3c045..cdd585f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ socks5-impl = { version = "0.5" } thiserror = "1.0" tokio = { version = "1.36", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "2.0", features = ["log"] } +tproxy-config = { version = "3.0", features = ["log"] } trust-dns-proto = "0.23" tun2 = { version = "1.2", features = ["async"] } udp-stream = { version = "0.0", default-features = false } diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 0e85803..f83a081 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -117,7 +117,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can tproxy_args = tproxy_args.tun_name(&tun_name); } - let mut restore: Option = None; + let mut restore: Option = None; #[cfg(target_os = "linux")] { From c430d76534c1eb34823879ec5bd38ee92d9ac9f4 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 18 Mar 2024 13:12:30 +0800 Subject: [PATCH 014/196] tcp timeout option --- src/args.rs | 5 +++++ src/lib.rs | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index 165bd58..1264fc0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -41,6 +41,10 @@ pub struct Args { #[arg(short, long, value_name = "IP")] pub bypass: Vec, + /// TCP timeout in seconds + #[arg(long, value_name = "seconds", default_value = "600")] + pub tcp_timeout: u64, + /// Verbosity level #[arg(short, long, value_name = "level", value_enum, default_value = "info")] pub verbosity: ArgVerbosity, @@ -57,6 +61,7 @@ impl Default for Args { dns: ArgDns::default(), dns_addr: "8.8.8.8".parse().unwrap(), bypass: vec![], + tcp_timeout: 600, verbosity: ArgVerbosity::Info, } } diff --git a/src/lib.rs b/src/lib.rs index 4b7e1ac..5936440 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,6 @@ mod virtual_dns; const DNS_PORT: u16 = 53; const MAX_SESSIONS: u64 = 200; -const TCP_TIMEOUT_SEC: u64 = 600; // 10 minutes const UDP_TIMEOUT_SEC: u64 = 10; // 10 seconds static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); @@ -87,7 +86,7 @@ where let mut ipstack_config = ipstack::IpStackConfig::default(); ipstack_config.mtu(mtu); - ipstack_config.tcp_timeout(std::time::Duration::from_secs(TCP_TIMEOUT_SEC)); + ipstack_config.tcp_timeout(std::time::Duration::from_secs(args.tcp_timeout)); ipstack_config.udp_timeout(std::time::Duration::from_secs(UDP_TIMEOUT_SEC)); let mut ip_stack = ipstack::IpStack::new(ipstack_config, device); From 715a85920c4b064b7ac20cfbb8d94e0cb305d183 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:31:04 +0800 Subject: [PATCH 015/196] update deps --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cdd585f..518a553 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" crate-type = ["staticlib", "cdylib", "lib"] [dependencies] -async-recursion = "1.0" +async-recursion = "1.1" async-trait = "0.1" base64 = { version = "0.22" } chrono = "0.4" @@ -24,7 +24,7 @@ dotenvy = "0.15" env_logger = "0.11" hashlink = "0.9" httparse = "1.8" -ipstack = { version = "0.0", features = ["log"] } +ipstack = { version = "0.0" } log = { version = "0.4", features = ["std"] } socks5-impl = { version = "0.5" } thiserror = "1.0" From eab795e61cb5c1fe3a23c200aad665ce18ebc11e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 24 Mar 2024 17:20:51 +0800 Subject: [PATCH 016/196] build-apple-debug.sh --- build-apple-debug.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100755 build-apple-debug.sh diff --git a/build-apple-debug.sh b/build-apple-debug.sh new file mode 100755 index 0000000..32c1a97 --- /dev/null +++ b/build-apple-debug.sh @@ -0,0 +1,27 @@ +#! /bin/sh + +echo "Setting up the rust environment..." +rustup target add aarch64-apple-ios +cargo install cbindgen + +echo "Building target aarch64-apple-ios..." +cargo build --target aarch64-apple-ios + +echo "Generating includes..." +mkdir -p target/include/ +rm -rf target/include/* +cbindgen --config cbindgen.toml -l C -o target/include/tun2proxy.h +cat > target/include/module.modulemap < Date: Thu, 28 Mar 2024 17:03:36 +0800 Subject: [PATCH 017/196] Bump version 0.2.14 --- Cargo.toml | 2 +- src/android.rs | 2 +- src/apple.rs | 4 +++- src/mobile_api.rs | 8 +++++++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 518a553..1a00cf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.13" +version = "0.2.14" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" diff --git a/src/android.rs b/src/android.rs index f89bb00..0f62d1c 100644 --- a/src/android.rs +++ b/src/android.rs @@ -39,7 +39,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( let mut args = Args::default(); args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns).verbosity(verbosity); - crate::mobile_api::mobile_run(args, tun_mtu) + crate::mobile_api::mobile_run(args, tun_mtu, false) } /// # Safety diff --git a/src/apple.rs b/src/apple.rs index eba0676..66e0d66 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -12,6 +12,7 @@ use std::os::raw::{c_char, c_int, c_ushort}; /// Parameters: /// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" /// - tun_fd: the tun file descriptor +/// - packet_information: whether exists packet information in tun_fd /// - tun_mtu: the tun mtu /// - dns_strategy: the dns strategy, see ArgDns enum /// - verbosity: the verbosity level, see ArgVerbosity enum @@ -19,6 +20,7 @@ use std::os::raw::{c_char, c_int, c_ushort}; pub unsafe extern "C" fn tun2proxy_with_fd_run( proxy_url: *const c_char, tun_fd: c_int, + packet_information: bool, tun_mtu: c_ushort, dns_strategy: ArgDns, verbosity: ArgVerbosity, @@ -32,7 +34,7 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( let mut args = Args::default(); args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns_strategy).verbosity(verbosity); - crate::mobile_api::mobile_run(args, tun_mtu) + crate::mobile_api::mobile_run(args, tun_mtu, packet_information) } /// # Safety diff --git a/src/mobile_api.rs b/src/mobile_api.rs index 8e67cef..645a06e 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -12,7 +12,7 @@ pub async fn desktop_run_async(_: Args, _: tokio_util::sync::CancellationToken) Ok(()) } -pub fn mobile_run(args: Args, tun_mtu: u16) -> c_int { +pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int { let shutdown_token = tokio_util::sync::CancellationToken::new(); { if let Ok(mut lock) = TUN_QUIT.lock() { @@ -41,6 +41,12 @@ pub fn mobile_run(args: Args, tun_mtu: u16) -> c_int { config.tun_name(tun); } + #[cfg(unix)] + config.platform_config(|config| { + #[allow(deprecated)] + config.packet_information(_packet_information); + }); + let device = tun2::create_as_async(&config).map_err(std::io::Error::from)?; let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); From ce0c02b3bf40e62ed29c00f74759cc2c9a202d1b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 30 Mar 2024 12:30:01 +0800 Subject: [PATCH 018/196] Bump version 0.2.15 --- .github/workflows/publish-exe.yml | 2 ++ Cargo.toml | 2 +- ...bug.sh => build-aarch64-apple-ios-debug.sh | 2 +- build-aarch64-apple-ios.sh | 27 +++++++++++++++++++ build-apple.sh | 13 ++++++++- cbindgen.toml | 7 +++++ src/apple.rs | 6 +++-- src/desktop_api.rs | 4 ++- src/mobile_api.rs | 3 +-- 9 files changed, 58 insertions(+), 8 deletions(-) rename build-apple-debug.sh => build-aarch64-apple-ios-debug.sh (93%) create mode 100755 build-aarch64-apple-ios.sh diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 8125396..6bcb516 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -70,6 +70,8 @@ jobs: elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then + ./build-aarch64-apple-ios.sh + zip -r mypubdir4/tun2proxy-aarch64-apple-ios-xcframework.zip ./tun2proxy.xcframework/ ./build-apple.sh zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/ fi diff --git a/Cargo.toml b/Cargo.toml index 1a00cf9..ca23b27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.14" +version = "0.2.15" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" diff --git a/build-apple-debug.sh b/build-aarch64-apple-ios-debug.sh similarity index 93% rename from build-apple-debug.sh rename to build-aarch64-apple-ios-debug.sh index 32c1a97..538865b 100755 --- a/build-apple-debug.sh +++ b/build-aarch64-apple-ios-debug.sh @@ -11,7 +11,7 @@ echo "Generating includes..." mkdir -p target/include/ rm -rf target/include/* cbindgen --config cbindgen.toml -l C -o target/include/tun2proxy.h -cat > target/include/module.modulemap < target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < target/include/module.modulemap < target/include/tun2proxy.modulemap < c_int { log::set_max_level(verbosity.into()); - log::set_boxed_logger(Box::::default()).unwrap(); + if let Err(err) = log::set_boxed_logger(Box::::default()) { + log::error!("failed to set logger: {:?}", err); + } let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); let proxy = ArgProxy::from_url(proxy_url).unwrap(); diff --git a/src/desktop_api.rs b/src/desktop_api.rs index f83a081..3d5723e 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -42,7 +42,9 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( } log::set_max_level(verbosity.into()); - log::set_boxed_logger(Box::::default()).unwrap(); + if let Err(err) = log::set_boxed_logger(Box::::default()) { + log::error!("set logger error: {}", err); + } let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); let proxy = ArgProxy::from_url(proxy_url).unwrap(); diff --git a/src/mobile_api.rs b/src/mobile_api.rs index 645a06e..2750733 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -41,9 +41,8 @@ pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int config.tun_name(tun); } - #[cfg(unix)] + #[cfg(any(target_os = "ios", target_os = "macos"))] config.platform_config(|config| { - #[allow(deprecated)] config.packet_information(_packet_information); }); From b7e59b130e674547f9256b313922cc6f12a0b365 Mon Sep 17 00:00:00 2001 From: "Remy D. Farley" Date: Wed, 3 Apr 2024 20:40:51 +0000 Subject: [PATCH 019/196] ci: don't abort checks immediately if error is encountered --- .github/workflows/rust.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d66b58a..e7609d7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,6 +9,7 @@ env: jobs: build_n_test: strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -16,11 +17,23 @@ jobs: steps: - uses: actions/checkout@v3 + - name: rustfmt + if: ${{ !cancelled() }} run: cargo fmt --all -- --check + - name: check + if: ${{ !cancelled() }} run: cargo check --verbose + - name: clippy + if: ${{ !cancelled() }} run: cargo clippy --all-targets --all-features -- -D warnings + - name: Build + if: ${{ !cancelled() }} run: cargo build --verbose --tests --all-features + + - name: Abort on error + if: ${{ failure() }} + run: echo "Some of jobs failed" && false \ No newline at end of file From 74e5220d088ca676c6fa985d42634ed59f312454 Mon Sep 17 00:00:00 2001 From: "Remy D. Farley" Date: Wed, 3 Apr 2024 20:40:51 +0000 Subject: [PATCH 020/196] ci: don't abort checks immediately if error is encountered --- .github/workflows/rust.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index d66b58a..e7609d7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -9,6 +9,7 @@ env: jobs: build_n_test: strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] @@ -16,11 +17,23 @@ jobs: steps: - uses: actions/checkout@v3 + - name: rustfmt + if: ${{ !cancelled() }} run: cargo fmt --all -- --check + - name: check + if: ${{ !cancelled() }} run: cargo check --verbose + - name: clippy + if: ${{ !cancelled() }} run: cargo clippy --all-targets --all-features -- -D warnings + - name: Build + if: ${{ !cancelled() }} run: cargo build --verbose --tests --all-features + + - name: Abort on error + if: ${{ failure() }} + run: echo "Some of jobs failed" && false \ No newline at end of file From 361cf95f4eba3bf2e1f676cb92372dd61c046e10 Mon Sep 17 00:00:00 2001 From: "Remy D. Farley" Date: Wed, 3 Apr 2024 14:13:18 +0000 Subject: [PATCH 021/196] add udp timeout option --- src/args.rs | 5 +++++ src/lib.rs | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index 1264fc0..309f7d0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -45,6 +45,10 @@ pub struct Args { #[arg(long, value_name = "seconds", default_value = "600")] pub tcp_timeout: u64, + /// UDP timeout in seconds + #[arg(long, value_name = "seconds", default_value = "10")] + pub udp_timeout: u64, + /// Verbosity level #[arg(short, long, value_name = "level", value_enum, default_value = "info")] pub verbosity: ArgVerbosity, @@ -62,6 +66,7 @@ impl Default for Args { dns_addr: "8.8.8.8".parse().unwrap(), bypass: vec![], tcp_timeout: 600, + udp_timeout: 10, verbosity: ArgVerbosity::Info, } } diff --git a/src/lib.rs b/src/lib.rs index 5936440..6a8f699 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,6 @@ mod virtual_dns; const DNS_PORT: u16 = 53; const MAX_SESSIONS: u64 = 200; -const UDP_TIMEOUT_SEC: u64 = 10; // 10 seconds static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); use std::sync::atomic::Ordering::Relaxed; @@ -87,7 +86,7 @@ where let mut ipstack_config = ipstack::IpStackConfig::default(); ipstack_config.mtu(mtu); ipstack_config.tcp_timeout(std::time::Duration::from_secs(args.tcp_timeout)); - ipstack_config.udp_timeout(std::time::Duration::from_secs(UDP_TIMEOUT_SEC)); + ipstack_config.udp_timeout(std::time::Duration::from_secs(args.udp_timeout)); let mut ip_stack = ipstack::IpStack::new(ipstack_config, device); From 5e99c9f87405b99bfd558445dc6768d5edb6afd5 Mon Sep 17 00:00:00 2001 From: "Remy D. Farley" Date: Wed, 3 Apr 2024 14:20:05 +0000 Subject: [PATCH 022/196] add no-proxy mode --- src/args.rs | 10 +++++ src/lib.rs | 3 ++ src/no_proxy.rs | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) create mode 100644 src/no_proxy.rs diff --git a/src/args.rs b/src/args.rs index 309f7d0..e7df38c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -249,6 +249,14 @@ impl std::fmt::Display for ArgProxy { impl ArgProxy { pub fn from_url(s: &str) -> Result { + if s == "none" { + return Ok(ArgProxy { + proxy_type: ProxyType::None, + addr: "0.0.0.0:0".parse().unwrap(), + credentials: None, + }); + } + let e = format!("`{s}` is not a valid proxy URL"); let url = url::Url::parse(s).map_err(|_| Error::from(&e))?; let e = format!("`{s}` does not contain a host"); @@ -299,6 +307,7 @@ pub enum ProxyType { Socks4, #[default] Socks5, + None, } impl std::fmt::Display for ProxyType { @@ -307,6 +316,7 @@ impl std::fmt::Display for ProxyType { ProxyType::Socks4 => write!(f, "socks4"), ProxyType::Socks5 => write!(f, "socks5"), ProxyType::Http => write!(f, "http"), + ProxyType::None => write!(f, "none"), } } } diff --git a/src/lib.rs b/src/lib.rs index 6a8f699..e2701fe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ use crate::{ directions::{IncomingDataEvent, IncomingDirection, OutgoingDirection}, http::HttpManager, + no_proxy::NoProxyManager, session_info::{IpProtocol, SessionInfo}, virtual_dns::VirtualDns, }; @@ -42,6 +43,7 @@ mod dump_logger; mod error; mod http; mod mobile_api; +mod no_proxy; mod proxy_handler; mod session_info; mod socks; @@ -81,6 +83,7 @@ where ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc, ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)) as Arc, ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)) as Arc, + ProxyType::None => Arc::new(NoProxyManager::new(server_addr)) as Arc, }; let mut ipstack_config = ipstack::IpStackConfig::default(); diff --git a/src/no_proxy.rs b/src/no_proxy.rs new file mode 100644 index 0000000..99c4dbf --- /dev/null +++ b/src/no_proxy.rs @@ -0,0 +1,109 @@ +use crate::{ + directions::{IncomingDataEvent, IncomingDirection, OutgoingDataEvent, OutgoingDirection}, + proxy_handler::{ProxyHandler, ProxyHandlerManager}, + session_info::SessionInfo, +}; +use std::{collections::VecDeque, net::SocketAddr, sync::Arc}; +use tokio::sync::Mutex; + +struct NoProxyHandler { + info: SessionInfo, + domain_name: Option, + client_outbuf: VecDeque, + server_outbuf: VecDeque, + udp_associate: bool, +} + +#[async_trait::async_trait] +impl ProxyHandler for NoProxyHandler { + fn get_session_info(&self) -> SessionInfo { + self.info + } + + fn get_domain_name(&self) -> Option { + self.domain_name.clone() + } + + async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()> { + let IncomingDataEvent { direction, buffer } = event; + match direction { + IncomingDirection::FromServer => { + self.client_outbuf.extend(buffer.iter()); + } + IncomingDirection::FromClient => { + self.server_outbuf.extend(buffer.iter()); + } + } + Ok(()) + } + + fn consume_data(&mut self, dir: OutgoingDirection, size: usize) { + let buffer = match dir { + OutgoingDirection::ToServer => &mut self.server_outbuf, + OutgoingDirection::ToClient => &mut self.client_outbuf, + }; + buffer.drain(0..size); + } + + fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent { + let buffer = match dir { + OutgoingDirection::ToServer => &mut self.server_outbuf, + OutgoingDirection::ToClient => &mut self.client_outbuf, + }; + OutgoingDataEvent { + direction: dir, + buffer: buffer.make_contiguous(), + } + } + + fn connection_established(&self) -> bool { + true + } + + fn data_len(&self, dir: OutgoingDirection) -> usize { + match dir { + OutgoingDirection::ToServer => self.server_outbuf.len(), + OutgoingDirection::ToClient => self.client_outbuf.len(), + } + } + + fn reset_connection(&self) -> bool { + false + } + + fn get_udp_associate(&self) -> Option { + self.udp_associate.then_some(self.info.dst) + } +} + +pub(crate) struct NoProxyManager { + server: SocketAddr, +} + +#[async_trait::async_trait] +impl ProxyHandlerManager for NoProxyManager { + async fn new_proxy_handler( + &self, + info: SessionInfo, + domain_name: Option, + udp_associate: bool, + ) -> std::io::Result>> { + Ok(Arc::new(Mutex::new(NoProxyHandler { + info, + domain_name, + client_outbuf: VecDeque::default(), + server_outbuf: VecDeque::default(), + udp_associate, + }))) + } + + fn get_server_addr(&self) -> SocketAddr { + self.server + } +} + +impl NoProxyManager { + pub(crate) fn new(server: SocketAddr) -> Self { + Self { server } + } +} From 050f8c0e65ce3f01e6acc8deeea99bf4cfb20fde Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:55:50 +0800 Subject: [PATCH 023/196] minor changes --- src/apple.rs | 2 +- src/desktop_api.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apple.rs b/src/apple.rs index 12890ba..87fc81b 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -27,7 +27,7 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( ) -> c_int { log::set_max_level(verbosity.into()); if let Err(err) = log::set_boxed_logger(Box::::default()) { - log::error!("failed to set logger: {:?}", err); + log::warn!("failed to set logger: {:?}", err); } let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 3d5723e..1914fec 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -43,7 +43,7 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( log::set_max_level(verbosity.into()); if let Err(err) = log::set_boxed_logger(Box::::default()) { - log::error!("set logger error: {}", err); + log::warn!("set logger error: {}", err); } let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); From d351b5031cef734d163e914079459c6112d4f400 Mon Sep 17 00:00:00 2001 From: "Remy D. Farley" Date: Wed, 3 Apr 2024 14:26:46 +0000 Subject: [PATCH 024/196] add support for unprivileged namespaces --- Cargo.toml | 5 + src/args.rs | 26 ++++- src/bin/main.rs | 58 +++++++++- src/desktop_api.rs | 58 ++++++++++ src/error.rs | 8 ++ src/http.rs | 9 +- src/lib.rs | 256 ++++++++++++++++++++++++++++++++++------- src/no_proxy.rs | 4 + src/proxy_handler.rs | 1 + src/socket_transfer.rs | 230 ++++++++++++++++++++++++++++++++++++ src/socks.rs | 8 ++ 11 files changed, 615 insertions(+), 48 deletions(-) create mode 100644 src/socket_transfer.rs diff --git a/Cargo.toml b/Cargo.toml index ca23b27..65e1f74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,11 @@ udp-stream = { version = "0.0", default-features = false } unicase = "2.7" url = "2.5" +[target.'cfg(target_os="linux")'.dependencies] +serde = { version = "1", features = [ "derive" ] } +bincode = "1" +nix = { version = "0", default-features = false, features = ["fs", "socket", "uio"] } + [target.'cfg(target_os="android")'.dependencies] android_logger = "0.13" jni = { version = "0.21", default-features = false } diff --git a/src/args.rs b/src/args.rs index e7df38c..bd9d413 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,6 +1,9 @@ use crate::{Error, Result}; use socks5_impl::protocol::UserKey; -use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; +use std::{ + ffi::OsString, + net::{IpAddr, SocketAddr, ToSocketAddrs}, +}; #[derive(Debug, Clone, clap::Parser)] #[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] @@ -20,13 +23,29 @@ pub struct Args { #[arg(long, value_name = "fd", conflicts_with = "tun")] pub tun_fd: Option, + /// Create a tun interface in a newly created unprivileged namespace + /// while maintaining proxy connectivity via the global network namespace. + #[arg(long)] + pub unshare: bool, + + /// File descriptor for UNIX datagram socket meant to transfer + /// network sockets from global namespace to the new one. + /// See `unshare(1)`, `namespaces(7)`, `sendmsg(2)`, `unix(7)`. + #[arg(long)] + pub socket_transfer_fd: Option, + + /// Specify a command to run with root-like capabilities in the new namespace. + /// This could be useful to start additional daemons, e.g. `openvpn` instance. + #[arg(requires = "unshare")] + pub admin_command: Vec, + /// IPv6 enabled #[arg(short = '6', long)] pub ipv6_enabled: bool, #[arg(short, long)] /// Routing and system setup, which decides whether to setup the routing and system configuration. - /// This option is only available on Linux and requires root privileges. + /// This option is only available on Linux and requires root-like privileges. See `capabilities(7)`. pub setup: bool, /// DNS handling strategy @@ -60,6 +79,9 @@ impl Default for Args { proxy: ArgProxy::default(), tun: None, tun_fd: None, + unshare: false, + socket_transfer_fd: None, + admin_command: Vec::new(), ipv6_enabled: false, setup: false, dns: ArgDns::default(), diff --git a/src/bin/main.rs b/src/bin/main.rs index 7be56ab..2fdad5e 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -13,8 +13,17 @@ async fn main() -> Result<(), BoxError> { let join_handle = tokio::spawn({ let shutdown_token = shutdown_token.clone(); async move { - if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { - log::error!("main loop error: {}", err); + if args.unshare && args.socket_transfer_fd.is_none() { + #[cfg(target_os = "linux")] + if let Err(err) = namespace_proxy_main(args, shutdown_token).await { + log::error!("namespace proxy error: {}", err); + } + #[cfg(not(target_os = "linux"))] + log::error!("Your platform doesn't support unprivileged namespaces"); + } else { + if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { + log::error!("main loop error: {}", err); + } } } }); @@ -31,3 +40,48 @@ async fn main() -> Result<(), BoxError> { Ok(()) } + +#[cfg(target_os = "linux")] +async fn namespace_proxy_main( + _args: Args, + _shutdown_token: tokio_util::sync::CancellationToken, +) -> Result { + use std::os::fd::AsRawFd; + + let (socket, remote_fd) = tun2proxy::socket_transfer::create_transfer_socket_pair().await?; + + let child = tokio::process::Command::new("unshare") + .args("--user --map-current-user --net --mount --keep-caps --kill-child --fork".split(' ')) + .arg(std::env::current_exe()?) + .arg("--socket-transfer-fd") + .arg(remote_fd.as_raw_fd().to_string()) + .args(std::env::args().skip(1)) + .kill_on_drop(true) + .spawn(); + + let mut child = match child { + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + log::error!("`unshare(1)` executable wasn't located in PATH."); + log::error!("Consider installing linux utils package: `apt install util-linux`"); + log::error!("Or similar for your distribution."); + return Err(err.into()); + } + child => child?, + }; + + log::info!("The tun proxy is running in unprivileged mode. See `namespaces(7)`."); + log::info!(""); + log::info!("If you need to run a process that relies on root-like capabilities (e.g. `openvpn`)"); + log::info!("Use `tun2proxy --unshare --setup [...] -- openvpn --config [...]`"); + log::info!(""); + log::info!("To run a new process in the created namespace (e.g. a flatpak app)"); + log::info!( + "Use `nsenter --preserve-credentials --user --net --mount --target {} /bin/sh`", + child.id().unwrap_or(0) + ); + log::info!(""); + + tokio::spawn(async move { tun2proxy::socket_transfer::process_socket_requests(&socket).await }); + + Ok(child.wait().await?) +} diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 3d5723e..71f67dc 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -131,11 +131,69 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can restore = Some(tproxy_config::tproxy_setup(&tproxy_args)?); } + #[cfg(target_os = "linux")] + { + let run_ip_util = |args: String| { + tokio::process::Command::new("ip") + .args(args.split(' ')) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .spawn() + .ok(); + }; + + if setup && !args.ipv6_enabled { + // Remove ipv6 connectivity if not explicitly required + // TODO: remove this when upstream will get updated + run_ip_util(format!("-6 route delete ::/1 dev {}", tproxy_args.tun_name)); + run_ip_util(format!("-6 route delete 80::/1 dev {}", tproxy_args.tun_name)); + } + + if setup && args.unshare { + // New namespace doesn't have any other routing device by default + // So our `tun` device should act as such to make space for other proxies. + run_ip_util(format!("route delete 0.0.0.0/1 dev {}", tproxy_args.tun_name)); + run_ip_util(format!("route delete 128.0.0.0/1 dev {}", tproxy_args.tun_name)); + + run_ip_util(format!("route add 0.0.0.0/0 dev {}", tproxy_args.tun_name)); + + if args.ipv6_enabled { + run_ip_util(format!("-6 route delete ::/1 dev {}", tproxy_args.tun_name)); + run_ip_util(format!("-6 route delete 80::/1 dev {}", tproxy_args.tun_name)); + + run_ip_util(format!("-6 route add ::/0 dev {}", tproxy_args.tun_name)); + } + } + } + + let mut admin_command_args = args.admin_command.iter(); + if let Some(command) = admin_command_args.next() { + let child = tokio::process::Command::new(command) + .args(admin_command_args) + .kill_on_drop(true) + .spawn(); + + match child { + Err(err) => { + log::warn!("Failed to start admin process: {err}"); + } + Ok(mut child) => { + tokio::spawn(async move { + if let Err(err) = child.wait().await { + log::warn!("Admin process terminated: {err}"); + } + }); + } + }; + } + let join_handle = tokio::spawn(crate::run(device, MTU, args, shutdown_token)); join_handle.await.map_err(std::io::Error::from)??; #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] if setup { + // TODO: This probably should be handled by a destructor + // since otherwise removal is not guaranteed if anything above returns early. tproxy_config::tproxy_remove(restore)?; } diff --git a/src/error.rs b/src/error.rs index 96b9732..2afd19b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,10 @@ pub enum Error { #[error(transparent)] Io(#[from] std::io::Error), + #[cfg(target_os = "linux")] + #[error("nix::errno::Errno {0:?}")] + NixErrno(#[from] nix::errno::Errno), + #[error("TryFromIntError {0:?}")] TryFromInt(#[from] std::num::TryFromIntError), @@ -39,6 +43,10 @@ pub enum Error { #[error("std::num::ParseIntError {0:?}")] IntParseError(#[from] std::num::ParseIntError), + + #[cfg(target_os = "linux")] + #[error("bincode::Error {0:?}")] + BincodeError(#[from] bincode::Error), } impl From<&str> for Error { diff --git a/src/http.rs b/src/http.rs index 73cf9b6..7aa2569 100644 --- a/src/http.rs +++ b/src/http.rs @@ -38,6 +38,7 @@ enum HttpState { pub(crate) type DigestState = digest_auth::WwwAuthenticateHeader; pub struct HttpConnection { + server_addr: SocketAddr, state: HttpState, client_inbuf: VecDeque, server_inbuf: VecDeque, @@ -61,12 +62,14 @@ static CONTENT_LENGTH: &str = "Content-Length"; impl HttpConnection { async fn new( + server_addr: SocketAddr, info: SessionInfo, domain_name: Option, credentials: Option, digest_state: Arc>>, ) -> Result { let mut res = Self { + server_addr, state: HttpState::ExpectResponseHeaders, client_inbuf: VecDeque::default(), server_inbuf: VecDeque::default(), @@ -330,6 +333,10 @@ impl HttpConnection { #[async_trait::async_trait] impl ProxyHandler for HttpConnection { + fn get_server_addr(&self) -> SocketAddr { + self.server_addr + } + fn get_session_info(&self) -> SessionInfo { self.info } @@ -413,7 +420,7 @@ impl ProxyHandlerManager for HttpManager { return Err(Error::from("Protocol not supported by HTTP proxy").into()); } Ok(Arc::new(Mutex::new( - HttpConnection::new(info, domain_name, self.credentials.clone(), self.digest_state.clone()).await?, + HttpConnection::new(self.server, info, domain_name, self.credentials.clone(), self.digest_state.clone()).await?, ))) } diff --git a/src/lib.rs b/src/lib.rs index e2701fe..183f37d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,11 +9,16 @@ use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; use proxy_handler::{ProxyHandler, ProxyHandlerManager}; use socks::SocksProxyManager; pub use socks5_impl::protocol::UserKey; -use std::{collections::VecDeque, net::SocketAddr, sync::Arc}; +use std::{ + collections::VecDeque, + io::ErrorKind, + net::{IpAddr, SocketAddr}, + sync::Arc, +}; use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, - net::TcpStream, - sync::Mutex, + net::{TcpSocket, TcpStream, UdpSocket}, + sync::{mpsc::Receiver, Mutex}, }; pub use tokio_util::sync::CancellationToken; use tproxy_config::is_private_ip; @@ -46,6 +51,7 @@ mod mobile_api; mod no_proxy; mod proxy_handler; mod session_info; +pub mod socket_transfer; mod socks; mod virtual_dns; @@ -56,6 +62,81 @@ const MAX_SESSIONS: u64 = 200; static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); use std::sync::atomic::Ordering::Relaxed; +#[allow(unused)] +#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))] +pub enum SocketProtocol { + Tcp, + Udp, +} + +#[allow(unused)] +#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))] +pub enum SocketDomain { + IpV4, + IpV6, +} + +impl From for SocketDomain { + fn from(value: IpAddr) -> Self { + match value { + IpAddr::V4(_) => Self::IpV4, + IpAddr::V6(_) => Self::IpV6, + } + } +} + +struct SocketQueue { + tcp_v4: Mutex>, + tcp_v6: Mutex>, + udp_v4: Mutex>, + udp_v6: Mutex>, +} + +impl SocketQueue { + async fn recv_tcp(&self, domain: SocketDomain) -> Result { + match domain { + SocketDomain::IpV4 => &self.tcp_v4, + SocketDomain::IpV6 => &self.tcp_v6, + } + .lock() + .await + .recv() + .await + .ok_or(ErrorKind::Other.into()) + } + async fn recv_udp(&self, domain: SocketDomain) -> Result { + match domain { + SocketDomain::IpV4 => &self.udp_v4, + SocketDomain::IpV6 => &self.udp_v6, + } + .lock() + .await + .recv() + .await + .ok_or(ErrorKind::Other.into()) + } +} + +async fn create_tcp_stream(socket_queue: &Option>, peer: SocketAddr) -> std::io::Result { + match &socket_queue { + None => TcpStream::connect(peer).await, + Some(queue) => queue.recv_tcp(peer.ip().into()).await?.connect(peer).await, + } +} + +async fn create_udp_stream(socket_queue: &Option>, peer: SocketAddr) -> std::io::Result { + match &socket_queue { + None => UdpStream::connect(peer).await, + Some(queue) => { + let socket = queue.recv_udp(peer.ip().into()).await?; + socket.connect(peer).await?; + UdpStream::from_tokio(socket).await + } + } +} + /// Run the proxy server /// # Arguments /// * `device` - The network device to use @@ -78,6 +159,56 @@ where None }; + #[cfg(target_os = "linux")] + let socket_queue = match args.socket_transfer_fd { + None => None, + Some(fd) => { + use crate::socket_transfer::{reconstruct_socket, reconstruct_transfer_socket, request_sockets}; + use tokio::sync::mpsc::channel; + + let fd = reconstruct_socket(fd)?; + let socket = reconstruct_transfer_socket(fd)?; + let socket = Arc::new(Mutex::new(socket)); + + macro_rules! create_socket_queue { + ($domain:ident) => {{ + const SOCKETS_PER_REQUEST: usize = 64; + + let socket = socket.clone(); + let (tx, rx) = channel(SOCKETS_PER_REQUEST); + tokio::spawn(async move { + loop { + let sockets = + match request_sockets(socket.lock().await, SocketDomain::$domain, SOCKETS_PER_REQUEST as u32).await { + Ok(sockets) => sockets, + Err(err) => { + log::warn!("Socket allocation request failed: {err}"); + continue; + } + }; + for s in sockets { + if let Err(_) = tx.send(s).await { + return; + } + } + } + }); + Mutex::new(rx) + }}; + } + + Some(Arc::new(SocketQueue { + tcp_v4: create_socket_queue!(IpV4), + tcp_v6: create_socket_queue!(IpV6), + udp_v4: create_socket_queue!(IpV4), + udp_v6: create_socket_queue!(IpV6), + })) + } + }; + + #[cfg(not(target_os = "linux"))] + let socket_queue = None; + use socks5_impl::protocol::Version::{V4, V5}; let mgr = match args.proxy.proxy_type { ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc, @@ -120,8 +251,9 @@ where None }; let proxy_handler = mgr.new_proxy_handler(info, domain_name, false).await?; + let socket_queue = socket_queue.clone(); tokio::spawn(async move { - if let Err(err) = handle_tcp_session(tcp, server_addr, proxy_handler).await { + if let Err(err) = handle_tcp_session(tcp, proxy_handler, socket_queue).await { log::error!("{} error \"{}\"", info, err); } log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); @@ -140,8 +272,9 @@ where } if args.dns == ArgDns::OverTcp { let proxy_handler = mgr.new_proxy_handler(info, None, false).await?; + let socket_queue = socket_queue.clone(); tokio::spawn(async move { - if let Err(err) = handle_dns_over_tcp_session(udp, server_addr, proxy_handler, ipv6_enabled).await { + if let Err(err) = handle_dns_over_tcp_session(udp, proxy_handler, socket_queue, ipv6_enabled).await { log::error!("{} error \"{}\"", info, err); } log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); @@ -170,8 +303,11 @@ where }; match mgr.new_proxy_handler(info, domain_name, true).await { Ok(proxy_handler) => { + let socket_queue = socket_queue.clone(); tokio::spawn(async move { - if let Err(err) = handle_udp_associate_session(udp, server_addr, proxy_handler, ipv6_enabled).await { + if let Err(err) = + handle_udp_associate_session(udp, args.proxy.proxy_type, proxy_handler, socket_queue, ipv6_enabled).await + { log::info!("Ending {} with \"{}\"", info, err); } log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); @@ -207,12 +343,17 @@ async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc>, + socket_queue: Option>, ) -> crate::Result<()> { - let mut server = TcpStream::connect(server_addr).await?; + let (session_info, server_addr) = { + let handler = proxy_handler.lock().await; + + (handler.get_session_info(), handler.get_server_addr()) + }; + + let mut server = create_tcp_stream(&socket_queue, server_addr).await?; - let session_info = proxy_handler.lock().await.get_session_info(); log::info!("Beginning {}", session_info); if let Err(e) = handle_proxy_session(&mut server, proxy_handler).await { @@ -246,20 +387,36 @@ async fn handle_tcp_session( async fn handle_udp_associate_session( mut udp_stack: IpStackUdpStream, - server_addr: SocketAddr, + proxy_type: ProxyType, proxy_handler: Arc>, + socket_queue: Option>, ipv6_enabled: bool, ) -> crate::Result<()> { use socks5_impl::protocol::{Address, StreamOperation, UdpHeader}; - let mut server = TcpStream::connect(server_addr).await?; - let session_info = proxy_handler.lock().await.get_session_info(); - let domain_name = proxy_handler.lock().await.get_domain_name(); + + let (session_info, server_addr, domain_name, udp_addr) = { + let handler = proxy_handler.lock().await; + ( + handler.get_session_info(), + handler.get_server_addr(), + handler.get_domain_name(), + handler.get_udp_associate(), + ) + }; + log::info!("Beginning {}", session_info); - let udp_addr = handle_proxy_session(&mut server, proxy_handler).await?; - let udp_addr = udp_addr.ok_or("udp associate failed")?; + let udp_addr = match udp_addr { + Some(udp_addr) => udp_addr, + None => { + let mut server = create_tcp_stream(&socket_queue, server_addr).await?; - let mut udp_server = UdpStream::connect(udp_addr).await?; + let udp_addr = handle_proxy_session(&mut server, proxy_handler).await?; + udp_addr.ok_or("udp associate failed")? + } + }; + + let mut udp_server = create_udp_stream(&socket_queue, udp_addr).await?; let mut buf1 = [0_u8; 4096]; let mut buf2 = [0_u8; 4096]; @@ -272,18 +429,22 @@ async fn handle_udp_associate_session( } let buf1 = &buf1[..len]; - let s5addr = if let Some(domain_name) = &domain_name { - Address::DomainAddress(domain_name.clone(), session_info.dst.port()) + if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { + let s5addr = if let Some(domain_name) = &domain_name { + Address::DomainAddress(domain_name.clone(), session_info.dst.port()) + } else { + session_info.dst.into() + }; + + // Add SOCKS5 UDP header to the incoming data + let mut s5_udp_data = Vec::::new(); + UdpHeader::new(0, s5addr).write_to_stream(&mut s5_udp_data)?; + s5_udp_data.extend_from_slice(buf1); + + udp_server.write_all(&s5_udp_data).await?; } else { - session_info.dst.into() - }; - - // Add SOCKS5 UDP header to the incoming data - let mut s5_udp_data = Vec::::new(); - UdpHeader::new(0, s5addr).write_to_stream(&mut s5_udp_data)?; - s5_udp_data.extend_from_slice(buf1); - - udp_server.write_all(&s5_udp_data).await?; + udp_server.write_all(buf1).await?; + } } len = udp_server.read(&mut buf2) => { let len = len?; @@ -292,21 +453,25 @@ async fn handle_udp_associate_session( } let buf2 = &buf2[..len]; - // Remove SOCKS5 UDP header from the server data - let header = UdpHeader::retrieve_from_stream(&mut &buf2[..])?; - let data = &buf2[header.len()..]; + if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { + // Remove SOCKS5 UDP header from the server data + let header = UdpHeader::retrieve_from_stream(&mut &buf2[..])?; + let data = &buf2[header.len()..]; - let buf = if session_info.dst.port() == DNS_PORT { - let mut message = dns::parse_data_to_dns_message(data, false)?; - if !ipv6_enabled { - dns::remove_ipv6_entries(&mut message); - } - message.to_vec()? + let buf = if session_info.dst.port() == DNS_PORT { + let mut message = dns::parse_data_to_dns_message(data, false)?; + if !ipv6_enabled { + dns::remove_ipv6_entries(&mut message); + } + message.to_vec()? + } else { + data.to_vec() + }; + + udp_stack.write_all(&buf).await?; } else { - data.to_vec() - }; - - udp_stack.write_all(&buf).await?; + udp_stack.write_all(buf2).await?; + } } } } @@ -318,13 +483,18 @@ async fn handle_udp_associate_session( async fn handle_dns_over_tcp_session( mut udp_stack: IpStackUdpStream, - server_addr: SocketAddr, proxy_handler: Arc>, + socket_queue: Option>, ipv6_enabled: bool, ) -> crate::Result<()> { - let mut server = TcpStream::connect(server_addr).await?; + let (session_info, server_addr) = { + let handler = proxy_handler.lock().await; + + (handler.get_session_info(), handler.get_server_addr()) + }; + + let mut server = create_tcp_stream(&socket_queue, server_addr).await?; - let session_info = proxy_handler.lock().await.get_session_info(); log::info!("Beginning {}", session_info); let _ = handle_proxy_session(&mut server, proxy_handler).await?; diff --git a/src/no_proxy.rs b/src/no_proxy.rs index 99c4dbf..83edadf 100644 --- a/src/no_proxy.rs +++ b/src/no_proxy.rs @@ -16,6 +16,10 @@ struct NoProxyHandler { #[async_trait::async_trait] impl ProxyHandler for NoProxyHandler { + fn get_server_addr(&self) -> SocketAddr { + self.info.dst + } + fn get_session_info(&self) -> SessionInfo { self.info } diff --git a/src/proxy_handler.rs b/src/proxy_handler.rs index 5621347..94406a6 100644 --- a/src/proxy_handler.rs +++ b/src/proxy_handler.rs @@ -7,6 +7,7 @@ use tokio::sync::Mutex; #[async_trait::async_trait] pub(crate) trait ProxyHandler: Send + Sync { + fn get_server_addr(&self) -> SocketAddr; fn get_session_info(&self) -> SessionInfo; fn get_domain_name(&self) -> Option; async fn push_data(&mut self, event: IncomingDataEvent<'_>) -> std::io::Result<()>; diff --git a/src/socket_transfer.rs b/src/socket_transfer.rs new file mode 100644 index 0000000..194c6f7 --- /dev/null +++ b/src/socket_transfer.rs @@ -0,0 +1,230 @@ +#![cfg(target_os = "linux")] + +use crate::{error, SocketDomain, SocketProtocol}; +use nix::{ + errno::Errno, + fcntl::{self, FdFlag}, + sys::socket::{cmsg_space, getsockopt, recvmsg, sendmsg, sockopt, ControlMessage, ControlMessageOwned, MsgFlags, SockType}, +}; +use serde::{Deserialize, Serialize}; +use std::{ + io::{ErrorKind, IoSlice, IoSliceMut, Result}, + ops::DerefMut, + os::fd::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, +}; +use tokio::net::{TcpSocket, UdpSocket, UnixDatagram}; + +const REQUEST_BUFFER_SIZE: usize = 64; + +#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +struct Request { + protocol: SocketProtocol, + domain: SocketDomain, + number: u32, +} + +#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +enum Response { + Ok, +} + +/// Reconstruct socket from raw `fd` +pub fn reconstruct_socket(fd: RawFd) -> Result { + // Check if `fd` is valid + let fd_flags = fcntl::fcntl(fd, fcntl::F_GETFD)?; + + // `fd` is confirmed to be valid so it should be closed + let socket = unsafe { OwnedFd::from_raw_fd(fd) }; + + // Insert CLOEXEC flag to the `fd` to prevent further propagation across `execve(2)` calls + let mut fd_flags = FdFlag::from_bits(fd_flags).ok_or(ErrorKind::Unsupported)?; + if !fd_flags.contains(FdFlag::FD_CLOEXEC) { + fd_flags.insert(FdFlag::FD_CLOEXEC); + fcntl::fcntl(fd, fcntl::F_SETFD(fd_flags))?; + } + + Ok(socket) +} + +/// Reconstruct transfer socket from `fd` +/// +/// Panics if called outside of tokio runtime +pub fn reconstruct_transfer_socket(fd: OwnedFd) -> Result { + // Check if socket of type DATAGRAM + let sock_type = getsockopt(&fd, sockopt::SockType)?; + if !matches!(sock_type, SockType::Datagram) { + return Err(ErrorKind::InvalidInput.into()); + } + + let std_socket: std::os::unix::net::UnixDatagram = fd.into(); + std_socket.set_nonblocking(true)?; + + // Fails if tokio context is absent + Ok(UnixDatagram::from_std(std_socket).unwrap()) +} + +/// Create pair of interconnected sockets one of which is set to stay open across `execve(2)` calls. +pub async fn create_transfer_socket_pair() -> std::io::Result<(UnixDatagram, OwnedFd)> { + let (local, remote) = tokio::net::UnixDatagram::pair()?; + + let remote_fd: OwnedFd = remote.into_std().unwrap().into(); + + // Get `remote_fd` flags + let fd_flags = fcntl::fcntl(remote_fd.as_raw_fd(), fcntl::F_GETFD)?; + + // Remove CLOEXEC flag from the `remote_fd` to allow propagating across `execve(2)` + let mut fd_flags = FdFlag::from_bits(fd_flags).ok_or(ErrorKind::Unsupported)?; + fd_flags.remove(FdFlag::FD_CLOEXEC); + fcntl::fcntl(remote_fd.as_raw_fd(), fcntl::F_SETFD(fd_flags))?; + + Ok((local, remote_fd)) +} + +pub trait TransferableSocket: Sized { + fn from_fd(fd: OwnedFd) -> Result; + fn domain() -> SocketProtocol; +} + +impl TransferableSocket for TcpSocket { + fn from_fd(fd: OwnedFd) -> Result { + // Check if socket is of type STREAM + let sock_type = getsockopt(&fd, sockopt::SockType)?; + if !matches!(sock_type, SockType::Stream) { + return Err(ErrorKind::InvalidInput.into()); + } + + let std_stream: std::net::TcpStream = fd.into(); + std_stream.set_nonblocking(true)?; + + Ok(TcpSocket::from_std_stream(std_stream)) + } + + fn domain() -> SocketProtocol { + SocketProtocol::Tcp + } +} + +impl TransferableSocket for UdpSocket { + /// Panics if called outside of tokio runtime + fn from_fd(fd: OwnedFd) -> Result { + // Check if socket is of type DATAGRAM + let sock_type = getsockopt(&fd, sockopt::SockType)?; + if !matches!(sock_type, SockType::Datagram) { + return Err(ErrorKind::InvalidInput.into()); + } + + let std_socket: std::net::UdpSocket = fd.into(); + std_socket.set_nonblocking(true)?; + + Ok(UdpSocket::try_from(std_socket).unwrap()) + } + + fn domain() -> SocketProtocol { + SocketProtocol::Udp + } +} + +/// Send [`Request`] to `socket` and return received [`TransferableSocket`]s +/// +/// Panics if called outside of tokio runtime +pub async fn request_sockets(mut socket: S, domain: SocketDomain, number: u32) -> error::Result> +where + S: DerefMut, + T: TransferableSocket, +{ + // Borrow socket as mut to prevent multiple simultaneous requests + let socket = socket.deref_mut(); + + // Send request + let request = bincode::serialize(&Request { + protocol: T::domain(), + domain, + number, + })?; + + socket.send(&request[..]).await?; + + // Receive response + loop { + socket.readable().await?; + + let mut buf = [0_u8; REQUEST_BUFFER_SIZE]; + let mut iov = [IoSliceMut::new(&mut buf[..])]; + let mut cmsg = Vec::with_capacity(cmsg_space::() * number as usize); + + let msg = recvmsg::<()>(socket.as_fd().as_raw_fd(), &mut iov, Some(&mut cmsg), MsgFlags::empty()); + + let msg = match msg { + Err(Errno::EAGAIN) => continue, + msg => msg?, + }; + + // Parse response + let response = &msg.iovs().next().unwrap()[..msg.bytes]; + let response: Response = bincode::deserialize(response)?; + if !matches!(response, Response::Ok) { + return Err("Request for new sockets failed".into()); + } + + // Process received file descriptors + let mut sockets = Vec::::with_capacity(number as usize); + for cmsg in msg.cmsgs() { + if let ControlMessageOwned::ScmRights(fds) = cmsg { + for fd in fds { + if fd < 0 { + return Err("Received socket is invalid".into()); + } + + let owned_fd = reconstruct_socket(fd)?; + sockets.push(T::from_fd(owned_fd)?); + } + } + } + + return Ok(sockets); + } +} + +/// Process [`Request`]s received from `socket` +/// +/// Panics if called outside of tokio runtime +pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()> { + loop { + let mut buf = [0_u8; REQUEST_BUFFER_SIZE]; + + let len = socket.recv(&mut buf[..]).await?; + + let request: Request = bincode::deserialize(&buf[..len])?; + + let response = Response::Ok; + let buf = bincode::serialize(&response)?; + + let mut owned_fd_buf: Vec = Vec::with_capacity(request.number as usize); + for _ in 0..request.number { + let fd = match request.protocol { + SocketProtocol::Tcp => match request.domain { + SocketDomain::IpV4 => tokio::net::TcpSocket::new_v4(), + SocketDomain::IpV6 => tokio::net::TcpSocket::new_v6(), + } + .map(|s| unsafe { OwnedFd::from_raw_fd(s.into_raw_fd()) }), + SocketProtocol::Udp => match request.domain { + SocketDomain::IpV4 => tokio::net::UdpSocket::bind("0.0.0.0:0").await, + SocketDomain::IpV6 => tokio::net::UdpSocket::bind("[::]:0").await, + } + .map(|s| s.into_std().unwrap().into()), + }; + match fd { + Err(err) => log::warn!("Failed to allocate socket: {err}"), + Ok(fd) => owned_fd_buf.push(fd), + }; + } + + socket.writable().await?; + + let raw_fd_buf: Vec = owned_fd_buf.iter().map(|fd| fd.as_raw_fd()).collect(); + let cmsg = ControlMessage::ScmRights(&raw_fd_buf[..]); + let iov = [IoSlice::new(&buf[..])]; + + sendmsg::<()>(socket.as_raw_fd(), &iov, &[cmsg], MsgFlags::empty(), None)?; + } +} diff --git a/src/socks.rs b/src/socks.rs index 90a6bcd..a7c0b0b 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -20,6 +20,7 @@ enum SocksState { } struct SocksProxyImpl { + server_addr: SocketAddr, info: SessionInfo, domain_name: Option, state: SocksState, @@ -35,6 +36,7 @@ struct SocksProxyImpl { impl SocksProxyImpl { fn new( + server_addr: SocketAddr, info: SessionInfo, domain_name: Option, credentials: Option, @@ -42,6 +44,7 @@ impl SocksProxyImpl { command: protocol::Command, ) -> Result { let mut result = Self { + server_addr, info, domain_name, state: SocksState::ClientHello, @@ -260,6 +263,10 @@ impl SocksProxyImpl { #[async_trait::async_trait] impl ProxyHandler for SocksProxyImpl { + fn get_server_addr(&self) -> SocketAddr { + self.server_addr + } + fn get_session_info(&self) -> SessionInfo { self.info } @@ -339,6 +346,7 @@ impl ProxyHandlerManager for SocksProxyManager { let command = if udp_associate { UdpAssociate } else { Connect }; let credentials = self.credentials.clone(); Ok(Arc::new(Mutex::new(SocksProxyImpl::new( + self.server, info, domain_name, credentials, From a08b3338c3e037e0ffa4f8f3741b9a4ae7ce72a1 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Wed, 3 Apr 2024 22:39:05 +0200 Subject: [PATCH 025/196] Apply clippy suggestion --- src/bin/main.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 2fdad5e..1723895 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -20,10 +20,8 @@ async fn main() -> Result<(), BoxError> { } #[cfg(not(target_os = "linux"))] log::error!("Your platform doesn't support unprivileged namespaces"); - } else { - if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { - log::error!("main loop error: {}", err); - } + } else if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { + log::error!("main loop error: {}", err); } } }); From 181497e709b8ec49e90b82d316ef82ec55268fc1 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 6 Apr 2024 16:22:26 +0800 Subject: [PATCH 026/196] remove useless get_server_addr --- src/http.rs | 4 ---- src/lib.rs | 2 +- src/no_proxy.rs | 12 +++--------- src/proxy_handler.rs | 1 - src/socks.rs | 4 ---- 5 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/http.rs b/src/http.rs index 7aa2569..79c403a 100644 --- a/src/http.rs +++ b/src/http.rs @@ -423,10 +423,6 @@ impl ProxyHandlerManager for HttpManager { HttpConnection::new(self.server, info, domain_name, self.credentials.clone(), self.digest_state.clone()).await?, ))) } - - fn get_server_addr(&self) -> SocketAddr { - self.server - } } impl HttpManager { diff --git a/src/lib.rs b/src/lib.rs index 183f37d..b3bf11c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -214,7 +214,7 @@ where ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc, ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)) as Arc, ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)) as Arc, - ProxyType::None => Arc::new(NoProxyManager::new(server_addr)) as Arc, + ProxyType::None => Arc::new(NoProxyManager::new()) as Arc, }; let mut ipstack_config = ipstack::IpStackConfig::default(); diff --git a/src/no_proxy.rs b/src/no_proxy.rs index 83edadf..d3c00e0 100644 --- a/src/no_proxy.rs +++ b/src/no_proxy.rs @@ -80,9 +80,7 @@ impl ProxyHandler for NoProxyHandler { } } -pub(crate) struct NoProxyManager { - server: SocketAddr, -} +pub(crate) struct NoProxyManager; #[async_trait::async_trait] impl ProxyHandlerManager for NoProxyManager { @@ -100,14 +98,10 @@ impl ProxyHandlerManager for NoProxyManager { udp_associate, }))) } - - fn get_server_addr(&self) -> SocketAddr { - self.server - } } impl NoProxyManager { - pub(crate) fn new(server: SocketAddr) -> Self { - Self { server } + pub(crate) fn new() -> Self { + Self } } diff --git a/src/proxy_handler.rs b/src/proxy_handler.rs index 94406a6..fb9d9f4 100644 --- a/src/proxy_handler.rs +++ b/src/proxy_handler.rs @@ -27,5 +27,4 @@ pub(crate) trait ProxyHandlerManager: Send + Sync { domain_name: Option, udp_associate: bool, ) -> std::io::Result>>; - fn get_server_addr(&self) -> SocketAddr; } diff --git a/src/socks.rs b/src/socks.rs index a7c0b0b..3800c6a 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -354,10 +354,6 @@ impl ProxyHandlerManager for SocksProxyManager { command, )?))) } - - fn get_server_addr(&self) -> SocketAddr { - self.server - } } impl SocksProxyManager { From 56be614334e49ac75ecf15a551969f6430ab6f22 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 6 Apr 2024 23:21:50 +0800 Subject: [PATCH 027/196] Args class --- src/args.rs | 29 ++++++++++++++++++++++++----- src/desktop_api.rs | 22 +++++++++++----------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/args.rs b/src/args.rs index bd9d413..b0766f6 100644 --- a/src/args.rs +++ b/src/args.rs @@ -16,7 +16,7 @@ pub struct Args { /// Name of the tun interface, such as tun0, utun4, etc. /// If this option is not provided, the OS will generate a random one. - #[arg(short, long, value_name = "name", conflicts_with = "tun_fd")] + #[arg(short, long, value_name = "name", conflicts_with = "tun_fd", value_parser = validate_tun)] pub tun: Option, /// File descriptor of the tun interface @@ -31,7 +31,7 @@ pub struct Args { /// File descriptor for UNIX datagram socket meant to transfer /// network sockets from global namespace to the new one. /// See `unshare(1)`, `namespaces(7)`, `sendmsg(2)`, `unix(7)`. - #[arg(long)] + #[arg(long, value_name = "fd")] pub socket_transfer_fd: Option, /// Specify a command to run with root-like capabilities in the new namespace. @@ -43,9 +43,9 @@ pub struct Args { #[arg(short = '6', long)] pub ipv6_enabled: bool, - #[arg(short, long)] /// Routing and system setup, which decides whether to setup the routing and system configuration. /// This option is only available on Linux and requires root-like privileges. See `capabilities(7)`. + #[arg(short, long, default_value = if cfg!(target_os = "linux") { "false" } else { "true" })] pub setup: bool, /// DNS handling strategy @@ -73,8 +73,20 @@ pub struct Args { pub verbosity: ArgVerbosity, } +fn validate_tun(p: &str) -> Result { + #[cfg(target_os = "macos")] + if p.len() <= 4 || &p[..4] != "utun" { + return Err(Error::from("Invalid tun interface name, please use utunX")); + } + Ok(p.to_string()) +} + impl Default for Args { fn default() -> Self { + #[cfg(target_os = "linux")] + let setup = false; + #[cfg(not(target_os = "linux"))] + let setup = true; Args { proxy: ArgProxy::default(), tun: None, @@ -83,7 +95,7 @@ impl Default for Args { socket_transfer_fd: None, admin_command: Vec::new(), ipv6_enabled: false, - setup: false, + setup, dns: ArgDns::default(), dns_addr: "8.8.8.8".parse().unwrap(), bypass: vec![], @@ -95,9 +107,16 @@ impl Default for Args { } impl Args { + #[allow(clippy::let_and_return)] pub fn parse_args() -> Self { use clap::Parser; - Self::parse() + let args = Self::parse(); + #[cfg(target_os = "linux")] + if !args.setup && args.tun.is_none() { + eprintln!("Missing required argument, '--tun' must present when '--setup' is not used."); + std::process::exit(-1); + } + args } pub fn proxy(&mut self, proxy: ArgProxy) -> &mut Self { diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 71f67dc..72da377 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -83,25 +83,25 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::CancellationToken) -> std::io::Result<()> { let bypass_ips = args.bypass.clone(); - let mut config = tun2::Configuration::default(); - config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); - config.destination(TUN_GATEWAY); + let mut tun_config = tun2::Configuration::default(); + tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); + tun_config.destination(TUN_GATEWAY); if let Some(tun_fd) = args.tun_fd { - config.raw_fd(tun_fd); + tun_config.raw_fd(tun_fd); } else if let Some(ref tun) = args.tun { - config.tun_name(tun); + tun_config.tun_name(tun); } #[cfg(target_os = "linux")] - config.platform_config(|config| { + tun_config.platform_config(|cfg| { #[allow(deprecated)] - config.packet_information(true); - config.ensure_root_privileges(args.setup); + cfg.packet_information(true); + cfg.ensure_root_privileges(args.setup); }); #[cfg(target_os = "windows")] - config.platform_config(|config| { - config.device_guid(Some(12324323423423434234_u128)); + tun_config.platform_config(|cfg| { + cfg.device_guid(Some(12324323423423434234_u128)); }); #[allow(unused_variables)] @@ -113,7 +113,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can #[allow(unused_mut, unused_assignments, unused_variables)] let mut setup = true; - let device = tun2::create_as_async(&config)?; + let device = tun2::create_as_async(&tun_config)?; if let Ok(tun_name) = device.as_ref().tun_name() { tproxy_args = tproxy_args.tun_name(&tun_name); From f9f5401ba4bb23e06d0b8675cc8d69252e1db9b4 Mon Sep 17 00:00:00 2001 From: "Remy D. Farley" Date: Sun, 7 Apr 2024 10:28:11 +0000 Subject: [PATCH 028/196] fix socks5 udp connectivity --- src/lib.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b3bf11c..c378a3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -305,9 +305,8 @@ where Ok(proxy_handler) => { let socket_queue = socket_queue.clone(); tokio::spawn(async move { - if let Err(err) = - handle_udp_associate_session(udp, args.proxy.proxy_type, proxy_handler, socket_queue, ipv6_enabled).await - { + let ty = args.proxy.proxy_type; + if let Err(err) = handle_udp_associate_session(udp, ty, proxy_handler, socket_queue, ipv6_enabled).await { log::info!("Ending {} with \"{}\"", info, err); } log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); @@ -406,13 +405,12 @@ async fn handle_udp_associate_session( log::info!("Beginning {}", session_info); - let udp_addr = match udp_addr { - Some(udp_addr) => udp_addr, + let (_server, udp_addr) = match udp_addr { + Some(udp_addr) => (None, udp_addr), None => { let mut server = create_tcp_stream(&socket_queue, server_addr).await?; - let udp_addr = handle_proxy_session(&mut server, proxy_handler).await?; - udp_addr.ok_or("udp associate failed")? + (Some(server), udp_addr.ok_or("udp associate failed")?) } }; From af6a8a3cb01024c12bfe9fa9bbc49372f0d331f4 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 7 Apr 2024 19:02:57 +0800 Subject: [PATCH 029/196] minor changes --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c378a3d..71f8298 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -405,6 +405,8 @@ async fn handle_udp_associate_session( log::info!("Beginning {}", session_info); + // `_server` is meaningful here, it must be alive all the time + // to ensure that UDP transmission will not be interrupted accidentally. let (_server, udp_addr) = match udp_addr { Some(udp_addr) => (None, udp_addr), None => { @@ -567,6 +569,9 @@ async fn handle_dns_over_tcp_session( Ok(()) } +/// This function is used to handle the business logic of tun2proxy and SOCKS5 server. +/// When handling UDP proxy, the return value UDP associate IP address is the result of this business logic. +/// However, when handling TCP business logic, the return value Ok(None) is meaningless, just indicating that the operation was successful. async fn handle_proxy_session(server: &mut TcpStream, proxy_handler: Arc>) -> crate::Result> { let mut launched = false; let mut proxy_handler = proxy_handler.lock().await; From e8469f0aee568e8f3d67c296d88cdf674d70a567 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 7 Apr 2024 21:12:20 +0200 Subject: [PATCH 030/196] Restrict namespace arguments to Linux --- src/args.rs | 20 ++++++++++++++------ src/bin/main.rs | 9 +++++---- src/desktop_api.rs | 39 ++++++++++++++++++++------------------- 3 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/args.rs b/src/args.rs index b0766f6..e5d1ca5 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,9 +1,10 @@ use crate::{Error, Result}; use socks5_impl::protocol::UserKey; -use std::{ - ffi::OsString, - net::{IpAddr, SocketAddr, ToSocketAddrs}, -}; + +#[cfg(target_os = "linux")] +use std::ffi::OsString; + +use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; #[derive(Debug, Clone, clap::Parser)] #[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] @@ -25,17 +26,21 @@ pub struct Args { /// Create a tun interface in a newly created unprivileged namespace /// while maintaining proxy connectivity via the global network namespace. + #[cfg(target_os = "linux")] #[arg(long)] pub unshare: bool, /// File descriptor for UNIX datagram socket meant to transfer /// network sockets from global namespace to the new one. /// See `unshare(1)`, `namespaces(7)`, `sendmsg(2)`, `unix(7)`. - #[arg(long, value_name = "fd")] + #[cfg(target_os = "linux")] + #[arg(long, value_name = "fd", hide(true))] pub socket_transfer_fd: Option, - /// Specify a command to run with root-like capabilities in the new namespace. + /// Specify a command to run with root-like capabilities in the new namespace + /// when using `--unshare`. /// This could be useful to start additional daemons, e.g. `openvpn` instance. + #[cfg(target_os = "linux")] #[arg(requires = "unshare")] pub admin_command: Vec, @@ -91,8 +96,11 @@ impl Default for Args { proxy: ArgProxy::default(), tun: None, tun_fd: None, + #[cfg(target_os = "linux")] unshare: false, + #[cfg(target_os = "linux")] socket_transfer_fd: None, + #[cfg(target_os = "linux")] admin_command: Vec::new(), ipv6_enabled: false, setup, diff --git a/src/bin/main.rs b/src/bin/main.rs index 1723895..4984050 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -13,14 +13,15 @@ async fn main() -> Result<(), BoxError> { let join_handle = tokio::spawn({ let shutdown_token = shutdown_token.clone(); async move { + #[cfg(target_os = "linux")] if args.unshare && args.socket_transfer_fd.is_none() { - #[cfg(target_os = "linux")] if let Err(err) = namespace_proxy_main(args, shutdown_token).await { log::error!("namespace proxy error: {}", err); } - #[cfg(not(target_os = "linux"))] - log::error!("Your platform doesn't support unprivileged namespaces"); - } else if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { + return; + } + + if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { log::error!("main loop error: {}", err); } } diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 72da377..e1fc29e 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -149,6 +149,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can run_ip_util(format!("-6 route delete 80::/1 dev {}", tproxy_args.tun_name)); } + #[cfg(target_os = "linux")] if setup && args.unshare { // New namespace doesn't have any other routing device by default // So our `tun` device should act as such to make space for other proxies. @@ -164,27 +165,27 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can run_ip_util(format!("-6 route add ::/0 dev {}", tproxy_args.tun_name)); } } - } - let mut admin_command_args = args.admin_command.iter(); - if let Some(command) = admin_command_args.next() { - let child = tokio::process::Command::new(command) - .args(admin_command_args) - .kill_on_drop(true) - .spawn(); + let mut admin_command_args = args.admin_command.iter(); + if let Some(command) = admin_command_args.next() { + let child = tokio::process::Command::new(command) + .args(admin_command_args) + .kill_on_drop(true) + .spawn(); - match child { - Err(err) => { - log::warn!("Failed to start admin process: {err}"); - } - Ok(mut child) => { - tokio::spawn(async move { - if let Err(err) = child.wait().await { - log::warn!("Admin process terminated: {err}"); - } - }); - } - }; + match child { + Err(err) => { + log::warn!("Failed to start admin process: {err}"); + } + Ok(mut child) => { + tokio::spawn(async move { + if let Err(err) = child.wait().await { + log::warn!("Admin process terminated: {err}"); + } + }); + } + }; + } } let join_handle = tokio::spawn(crate::run(device, MTU, args, shutdown_token)); From 4f5a128972b025c6034414385e001472e558b20e Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 7 Apr 2024 21:21:59 +0200 Subject: [PATCH 031/196] Update README --- README.md | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 952ed3a..ea290a4 100644 --- a/README.md +++ b/README.md @@ -114,22 +114,32 @@ sudo ip link del tun0 ``` Tunnel interface to proxy. -Usage: tun2proxy [OPTIONS] --proxy +Usage: tun2proxy [OPTIONS] --proxy [ADMIN_COMMAND]... + +Arguments: + [ADMIN_COMMAND]... Specify a command to run with root-like capabilities in the new namespace when using `--unshare`. + This could be useful to start additional daemons, e.g. `openvpn` instance Options: - -p, --proxy Proxy URL in the form proto://[username[:password]@]host:port, where proto is one of socks4, - socks5, http. For example: socks5://myname:password@127.0.0.1:1080 - -t, --tun Name of the tun interface [default: tun0] - --tun-fd File descriptor of the tun interface - -6, --ipv6-enabled IPv6 enabled - -s, --setup Routing and system setup, which decides whether to setup the routing and system configuration, - this option requires root privileges - -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct] - --dns-addr DNS resolver address [default: 8.8.8.8] - -b, --bypass IPs used in routing setup which should bypass the tunnel - -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] - -h, --help Print help - -V, --version Print version + -p, --proxy Proxy URL in the form proto://[username[:password]@]host:port, where proto is one of + socks4, socks5, http. For example: socks5://myname:password@127.0.0.1:1080 + -t, --tun Name of the tun interface, such as tun0, utun4, etc. If this option is not provided, the + OS will generate a random one + --tun-fd File descriptor of the tun interface + --unshare Create a tun interface in a newly created unprivileged namespace while maintaining proxy + connectivity via the global network namespace + -6, --ipv6-enabled IPv6 enabled + -s, --setup Routing and system setup, which decides whether to setup the routing and system + configuration. This option is only available on Linux and requires root-like privileges. + See `capabilities(7)` + -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct] + --dns-addr DNS resolver address [default: 8.8.8.8] + -b, --bypass IPs used in routing setup which should bypass the tunnel + --tcp-timeout TCP timeout in seconds [default: 600] + --udp-timeout UDP timeout in seconds [default: 10] + -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] + -h, --help Print help + -V, --version Print version ``` Currently, tun2proxy supports HTTP, SOCKS4/SOCKS4a and SOCKS5. A proxy is supplied to the `--proxy` argument in the URL format. For example, an HTTP proxy at `1.2.3.4:3128` with a username of `john.doe` and a password of `secret` is From 40368dd232ee2a8c0640db76e5bc39bdce8474ec Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 7 Apr 2024 21:44:50 +0200 Subject: [PATCH 032/196] Increase security and portability through the use of /proc/self/exe --- src/bin/main.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 4984050..9360b54 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -45,13 +45,17 @@ async fn namespace_proxy_main( _args: Args, _shutdown_token: tokio_util::sync::CancellationToken, ) -> Result { + use nix::fcntl::{open, OFlag}; + use nix::sys::stat::Mode; use std::os::fd::AsRawFd; let (socket, remote_fd) = tun2proxy::socket_transfer::create_transfer_socket_pair().await?; + let fd = open("/proc/self/exe", OFlag::O_PATH, Mode::empty())?; + let child = tokio::process::Command::new("unshare") .args("--user --map-current-user --net --mount --keep-caps --kill-child --fork".split(' ')) - .arg(std::env::current_exe()?) + .arg(format!("/proc/self/fd/{}", fd)) .arg("--socket-transfer-fd") .arg(remote_fd.as_raw_fd().to_string()) .args(std::env::args().skip(1)) From ebbe939f857531655d36d70bb77059f7616182f3 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 7 Apr 2024 23:08:32 +0200 Subject: [PATCH 033/196] Use destructor to restore network config --- Cargo.toml | 2 +- src/desktop_api.rs | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 65e1f74..7e2588f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ socks5-impl = { version = "0.5" } thiserror = "1.0" tokio = { version = "1.36", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "3.0", features = ["log"] } +tproxy-config = { version = ">=3.0.2", features = ["log"] } trust-dns-proto = "0.23" tun2 = { version = "1.2", features = ["async"] } udp-stream = { version = "0.0", default-features = false } diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 3364bb8..44f16c4 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -119,7 +119,9 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can tproxy_args = tproxy_args.tun_name(&tun_name); } - let mut restore: Option = None; + // TproxyState implements the Drop trait to restore network configuration, + // so we we need to assign it to a variable, even if it is not used. + let mut _restore: Option = None; #[cfg(target_os = "linux")] { @@ -128,7 +130,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] if setup { - restore = Some(tproxy_config::tproxy_setup(&tproxy_args)?); + _restore = Some(tproxy_config::tproxy_setup(&tproxy_args)?); } #[cfg(target_os = "linux")] @@ -191,13 +193,6 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can let join_handle = tokio::spawn(crate::run(device, MTU, args, shutdown_token)); join_handle.await.map_err(std::io::Error::from)??; - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - if setup { - // TODO: This probably should be handled by a destructor - // since otherwise removal is not guaranteed if anything above returns early. - tproxy_config::tproxy_remove(restore)?; - } - Ok::<(), std::io::Error>(()) } From 18044a8056d580c0a2da130cc37894786f7b4221 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Apr 2024 13:45:42 +0800 Subject: [PATCH 034/196] change Apple building description --- Cargo.toml | 8 +- apple/readme.md | 17 +- apple/tun2proxy.xcodeproj/project.pbxproj | 398 ---------------------- apple/tun2proxy/Tun2proxyWrapper.h | 22 -- apple/tun2proxy/Tun2proxyWrapper.m | 29 -- apple/tun2proxy/tun2proxy.h | 18 - 6 files changed, 15 insertions(+), 477 deletions(-) delete mode 100644 apple/tun2proxy.xcodeproj/project.pbxproj delete mode 100644 apple/tun2proxy/Tun2proxyWrapper.h delete mode 100644 apple/tun2proxy/Tun2proxyWrapper.m delete mode 100644 apple/tun2proxy/tun2proxy.h diff --git a/Cargo.toml b/Cargo.toml index 7e2588f..45c4a1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,9 +38,13 @@ unicase = "2.7" url = "2.5" [target.'cfg(target_os="linux")'.dependencies] -serde = { version = "1", features = [ "derive" ] } +serde = { version = "1", features = ["derive"] } bincode = "1" -nix = { version = "0", default-features = false, features = ["fs", "socket", "uio"] } +nix = { version = "0", default-features = false, features = [ + "fs", + "socket", + "uio", +] } [target.'cfg(target_os="android")'.dependencies] android_logger = "0.13" diff --git a/apple/readme.md b/apple/readme.md index 0185db4..afff997 100644 --- a/apple/readme.md +++ b/apple/readme.md @@ -1,4 +1,4 @@ -Build iOS framework +Build iOS xcframework ---------------- # Install Rust build tools @@ -8,14 +8,15 @@ Build iOS framework - Install iOS target support: `rustup target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios` - Install cbindgen tool: `cargo install cbindgen` -# Building iOS framework +# Building iOS xcframework -Due to an unknown reason at present, compiling Rust code inside Xcode fails, so you have to manually compile it. Please run the following command in zsh (or bash): +Run the following command in zsh (or bash): ```bash cd tun2proxy - -cargo build --release --target aarch64-apple-ios -cargo build --release --target x86_64-apple-ios -lipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a -cbindgen --config cbindgen.toml -l C -o target/tun2proxy-ffi.h +./build-apple.sh ``` + +The script `build-apple.sh` will build the iOS/macOS xcframework and output it to `./tun2proxy.xcframework` + +To save the build time, you can use the `build-aarch64-apple-ios-debug.sh` or `build-aarch64-apple-ios.sh` script +to build the `aarch64-apple-ios` target only. diff --git a/apple/tun2proxy.xcodeproj/project.pbxproj b/apple/tun2proxy.xcodeproj/project.pbxproj deleted file mode 100644 index 1d4c250..0000000 --- a/apple/tun2proxy.xcodeproj/project.pbxproj +++ /dev/null @@ -1,398 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 55; - objects = { - -/* Begin PBXBuildFile section */ - B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = B648A35829F43D110045B334 /* Tun2proxyWrapper.m */; }; - B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; }; - B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B692ACC829F7EA4C006BF04D /* libtun2proxy.a */; }; - B6DE654429F4255A00468184 /* tun2proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = B6DE654329F4255A00468184 /* tun2proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; -/* End PBXBuildFile section */ - -/* Begin PBXFileReference section */ - B648A35829F43D110045B334 /* Tun2proxyWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tun2proxyWrapper.m; sourceTree = ""; }; - B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tun2proxyWrapper.h; sourceTree = ""; }; - B692ACC829F7EA4C006BF04D /* libtun2proxy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtun2proxy.a; path = ../target/libtun2proxy.a; sourceTree = ""; }; - B6DE654029F4255A00468184 /* tun2proxy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = tun2proxy.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - B6DE654329F4255A00468184 /* tun2proxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = tun2proxy.h; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - B6DE653D29F4255A00468184 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - B692ACC929F7EA4C006BF04D /* libtun2proxy.a in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - B692ACC729F7EA4C006BF04D /* Frameworks */ = { - isa = PBXGroup; - children = ( - B692ACC829F7EA4C006BF04D /* libtun2proxy.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - B6DE653629F4255A00468184 = { - isa = PBXGroup; - children = ( - B6DE654229F4255A00468184 /* tun2proxy */, - B6DE654129F4255A00468184 /* Products */, - B692ACC729F7EA4C006BF04D /* Frameworks */, - ); - sourceTree = ""; - }; - B6DE654129F4255A00468184 /* Products */ = { - isa = PBXGroup; - children = ( - B6DE654029F4255A00468184 /* tun2proxy.framework */, - ); - name = Products; - sourceTree = ""; - }; - B6DE654229F4255A00468184 /* tun2proxy */ = { - isa = PBXGroup; - children = ( - B6DE654329F4255A00468184 /* tun2proxy.h */, - B648A35829F43D110045B334 /* Tun2proxyWrapper.m */, - B648A35A29F43DDB0045B334 /* Tun2proxyWrapper.h */, - ); - path = tun2proxy; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - B6DE653B29F4255A00468184 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - B648A35B29F43DDB0045B334 /* Tun2proxyWrapper.h in Headers */, - B6DE654429F4255A00468184 /* tun2proxy.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - B6DE653F29F4255A00468184 /* tun2proxy */ = { - isa = PBXNativeTarget; - buildConfigurationList = B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */; - buildPhases = ( - B692ACB329F7E203006BF04D /* Run Script */, - B6DE653B29F4255A00468184 /* Headers */, - B6DE653C29F4255A00468184 /* Sources */, - B6DE653D29F4255A00468184 /* Frameworks */, - B6DE653E29F4255A00468184 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = tun2proxy; - productName = tun2proxy; - productReference = B6DE654029F4255A00468184 /* tun2proxy.framework */; - productType = "com.apple.product-type.framework"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - B6DE653729F4255A00468184 /* Project object */ = { - isa = PBXProject; - attributes = { - BuildIndependentTargetsInParallel = 1; - LastUpgradeCheck = 1430; - TargetAttributes = { - B6DE653F29F4255A00468184 = { - CreatedOnToolsVersion = 13.2.1; - }; - }; - }; - buildConfigurationList = B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */; - compatibilityVersion = "Xcode 13.0"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = B6DE653629F4255A00468184; - productRefGroup = B6DE654129F4255A00468184 /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - B6DE653F29F4255A00468184 /* tun2proxy */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - B6DE653E29F4255A00468184 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - B692ACB329F7E203006BF04D /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputFileListPaths = ( - ); - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/bash; - shellScript = "set -e\nPATH=\"$PATH:${HOME}/.cargo/bin\"\nRUST_PROJ=${PROJECT_DIR}/..\ncd \"${RUST_PROJ}\"\ncargo build --release --target aarch64-apple-ios\ncargo build --release --target x86_64-apple-ios\nlipo -create target/aarch64-apple-ios/release/libtun2proxy.a target/x86_64-apple-ios/release/libtun2proxy.a -output target/libtun2proxy.a\ncbindgen --config cbindgen.toml -l C -o target/tun2proxy-ffi.h\n"; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - B6DE653C29F4255A00468184 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - B648A35929F43D110045B334 /* Tun2proxyWrapper.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin XCBuildConfiguration section */ - B6DE654529F4255A00468184 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Debug; - }; - B6DE654629F4255A00468184 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; - MTL_ENABLE_DEBUG_INFO = NO; - MTL_FAST_MATH = YES; - SDKROOT = iphoneos; - VALIDATE_PRODUCT = YES; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = Release; - }; - B6DE654829F4255A00468184 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_BITCODE = NO; - ENABLE_MODULE_VERIFIER = YES; - GENERATE_INFOPLIST_FILE = YES; - HEADER_SEARCH_PATHS = ""; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - ../target, - "$(PROJECT_DIR)/../target", - ); - MARKETING_VERSION = 1.0; - MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; - PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - USER_HEADER_SEARCH_PATHS = ../target; - }; - name = Debug; - }; - B6DE654929F4255A00468184 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = ""; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = ""; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_BITCODE = NO; - ENABLE_MODULE_VERIFIER = YES; - GENERATE_INFOPLIST_FILE = YES; - HEADER_SEARCH_PATHS = ""; - INFOPLIST_KEY_NSHumanReadableCopyright = ""; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = ( - "$(inherited)", - "@executable_path/Frameworks", - "@loader_path/Frameworks", - ); - LIBRARY_SEARCH_PATHS = ( - ../target, - "$(PROJECT_DIR)/../target", - ); - MARKETING_VERSION = 1.0; - MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; - MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++17"; - PRODUCT_BUNDLE_IDENTIFIER = com.ssrlive.tun2proxy; - PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; - SKIP_INSTALL = YES; - SWIFT_EMIT_LOC_STRINGS = YES; - TARGETED_DEVICE_FAMILY = "1,2"; - USER_HEADER_SEARCH_PATHS = ../target; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - B6DE653A29F4255A00468184 /* Build configuration list for PBXProject "tun2proxy" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B6DE654529F4255A00468184 /* Debug */, - B6DE654629F4255A00468184 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - B6DE654729F4255A00468184 /* Build configuration list for PBXNativeTarget "tun2proxy" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B6DE654829F4255A00468184 /* Debug */, - B6DE654929F4255A00468184 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = B6DE653729F4255A00468184 /* Project object */; -} diff --git a/apple/tun2proxy/Tun2proxyWrapper.h b/apple/tun2proxy/Tun2proxyWrapper.h deleted file mode 100644 index 517192e..0000000 --- a/apple/tun2proxy/Tun2proxyWrapper.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// Tun2proxyWrapper.h -// tun2proxy -// -// Created by ssrlive on 2023/4/23. -// - -#ifndef Tun2proxyWrapper_h -#define Tun2proxyWrapper_h - -@interface Tun2proxyWrapper : NSObject - -+ (void)startWithConfig:(NSString *)proxy_url - tun_fd:(int)tun_fd - tun_mtu:(uint16_t)tun_mtu - dns_over_tcp:(bool)dns_over_tcp - verbose:(bool)verbose; -+ (void) shutdown; - -@end - -#endif /* Tun2proxyWrapper_h */ diff --git a/apple/tun2proxy/Tun2proxyWrapper.m b/apple/tun2proxy/Tun2proxyWrapper.m deleted file mode 100644 index d6a088e..0000000 --- a/apple/tun2proxy/Tun2proxyWrapper.m +++ /dev/null @@ -1,29 +0,0 @@ -// -// Tun2proxyWrapper.m -// tun2proxy -// -// Created by ssrlive on 2023/4/23. -// - -#import - -#import "Tun2proxyWrapper.h" -#include "tun2proxy-ffi.h" - -@implementation Tun2proxyWrapper - -+ (void)startWithConfig:(NSString *)proxy_url - tun_fd:(int)tun_fd - tun_mtu:(uint16_t)tun_mtu - dns_over_tcp:(bool)dns_over_tcp - verbose:(bool)verbose { - ArgDns dns_strategy = dns_over_tcp ? OverTcp : Direct; - ArgVerbosity v = verbose ? Trace : Info; - tun2proxy_with_fd_run(proxy_url.UTF8String, tun_fd, tun_mtu, dns_strategy, v); -} - -+ (void)shutdown { - tun2proxy_with_fd_stop(); -} - -@end diff --git a/apple/tun2proxy/tun2proxy.h b/apple/tun2proxy/tun2proxy.h deleted file mode 100644 index d62e035..0000000 --- a/apple/tun2proxy/tun2proxy.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// tun2proxy.h -// tun2proxy -// -// Created by tun2proxy on 2023/4/22. -// - -#import - -//! Project version number for tun2proxy. -FOUNDATION_EXPORT double tun2proxyVersionNumber; - -//! Project version string for tun2proxy. -FOUNDATION_EXPORT const unsigned char tun2proxyVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - -#import From c1d93942cc611430b38e48e71d604e92cf62417f Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:24:41 +0800 Subject: [PATCH 035/196] async-recursion removed --- Cargo.toml | 2 +- src/http.rs | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 45c4a1b..b2c5b6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,12 +8,12 @@ homepage = "https://github.com/blechschmidt/tun2proxy" authors = ["B. Blechschmidt", "ssrlive"] description = "Tunnel interface to proxy" readme = "README.md" +rust-version = "1.77" [lib] crate-type = ["staticlib", "cdylib", "lib"] [dependencies] -async-recursion = "1.1" async-trait = "0.1" base64 = { version = "0.22" } chrono = "0.4" diff --git a/src/http.rs b/src/http.rs index 79c403a..00a14b4 100644 --- a/src/http.rs +++ b/src/http.rs @@ -152,7 +152,6 @@ impl HttpConnection { Ok(()) } - #[async_recursion::async_recursion] async fn state_change(&mut self) -> Result<()> { match self.state { HttpState::ExpectResponseHeaders => { @@ -200,7 +199,7 @@ impl HttpConnection { // The server may have sent a banner already (SMTP, SSH, etc.). // Therefore, server_inbuf must retain this data. self.server_inbuf.drain(0..header_size); - return self.state_change().await; + return Box::pin(self.state_change()).await; } if status_code != 407 { @@ -295,7 +294,7 @@ impl HttpConnection { self.state = HttpState::ExpectResponse; self.skip = content_length + len; - return self.state_change().await; + return Box::pin(self.state_change()).await; } HttpState::ExpectResponse => { if self.skip > 0 { @@ -312,7 +311,7 @@ impl HttpConnection { self.send_tunnel_request().await?; self.state = HttpState::ExpectResponseHeaders; - return self.state_change().await; + return Box::pin(self.state_change()).await; } } HttpState::Established => { @@ -323,7 +322,7 @@ impl HttpConnection { } HttpState::Reset => { self.state = HttpState::ExpectResponseHeaders; - return self.state_change().await; + return Box::pin(self.state_change()).await; } _ => {} } From e582d6cbecf543b9cbdd1f207394eae814b188ab Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:28:56 +0800 Subject: [PATCH 036/196] Fix #107 --- Cargo.toml | 1 + src/args.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b2c5b6e..60c8dd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ tun2 = { version = "1.2", features = ["async"] } udp-stream = { version = "0.0", default-features = false } unicase = "2.7" url = "2.5" +url-escape = "0.1" [target.'cfg(target_os="linux")'.dependencies] serde = { version = "1", features = ["derive"] } diff --git a/src/args.rs b/src/args.rs index e5d1ca5..63c1713 100644 --- a/src/args.rs +++ b/src/args.rs @@ -326,8 +326,8 @@ impl ArgProxy { let credentials = if url.username() == "" && url.password().is_none() { None } else { - let username = String::from(url.username()); - let password = String::from(url.password().unwrap_or("")); + let username = url_escape::decode(url.username()); + let password = url_escape::decode(url.password().unwrap_or("")); Some(UserKey::new(username, password)) }; From 84c03426f2c49b09f942d5cba920842708db45e0 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Apr 2024 15:39:20 +0800 Subject: [PATCH 037/196] Bump version 0.2.16 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 60c8dd2..d13fa00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.15" +version = "0.2.16" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 92011edd43375bd4d0ecbeeb754a38d4d4724ddd Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:37:56 +0800 Subject: [PATCH 038/196] use percent-encoding instead of url-escape --- Cargo.toml | 2 +- src/args.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d13fa00..71d6b70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ hashlink = "0.9" httparse = "1.8" ipstack = { version = "0.0" } log = { version = "0.4", features = ["std"] } +percent-encoding = "2" socks5-impl = { version = "0.5" } thiserror = "1.0" tokio = { version = "1.36", features = ["full"] } @@ -36,7 +37,6 @@ tun2 = { version = "1.2", features = ["async"] } udp-stream = { version = "0.0", default-features = false } unicase = "2.7" url = "2.5" -url-escape = "0.1" [target.'cfg(target_os="linux")'.dependencies] serde = { version = "1", features = ["derive"] } diff --git a/src/args.rs b/src/args.rs index 63c1713..962dbcc 100644 --- a/src/args.rs +++ b/src/args.rs @@ -326,8 +326,9 @@ impl ArgProxy { let credentials = if url.username() == "" && url.password().is_none() { None } else { - let username = url_escape::decode(url.username()); - let password = url_escape::decode(url.password().unwrap_or("")); + use percent_encoding::percent_decode; + let username = percent_decode(url.username().as_bytes()).decode_utf8().unwrap(); + let password = percent_decode(url.password().unwrap_or("").as_bytes()).decode_utf8().unwrap(); Some(UserKey::new(username, password)) }; From ba1615fcd18695ff4d4bfca8bcc4c085dfb07424 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Apr 2024 19:23:13 +0800 Subject: [PATCH 039/196] minor changes --- src/args.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/args.rs b/src/args.rs index 962dbcc..24c0e41 100644 --- a/src/args.rs +++ b/src/args.rs @@ -10,7 +10,8 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; #[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] pub struct Args { /// Proxy URL in the form proto://[username[:password]@]host:port, - /// where proto is one of socks4, socks5, http. For example: + /// where proto is one of socks4, socks5, http. + /// Username and password are encoded in percent encoding. For example: /// socks5://myname:password@127.0.0.1:1080 #[arg(short, long, value_parser = ArgProxy::from_url, value_name = "URL")] pub proxy: ArgProxy, @@ -327,8 +328,8 @@ impl ArgProxy { None } else { use percent_encoding::percent_decode; - let username = percent_decode(url.username().as_bytes()).decode_utf8().unwrap(); - let password = percent_decode(url.password().unwrap_or("").as_bytes()).decode_utf8().unwrap(); + let username = percent_decode(url.username().as_bytes()).decode_utf8()?; + let password = percent_decode(url.password().unwrap_or("").as_bytes()).decode_utf8()?; Some(UserKey::new(username, password)) }; From 18f4689d21febed0a9478280fe15796c89c6a59a Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Apr 2024 20:49:44 +0800 Subject: [PATCH 040/196] refine code --- src/args.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/args.rs b/src/args.rs index 24c0e41..d0cc99f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -12,7 +12,7 @@ pub struct Args { /// Proxy URL in the form proto://[username[:password]@]host:port, /// where proto is one of socks4, socks5, http. /// Username and password are encoded in percent encoding. For example: - /// socks5://myname:password@127.0.0.1:1080 + /// socks5://myname:pass%40word@127.0.0.1:1080 #[arg(short, long, value_parser = ArgProxy::from_url, value_name = "URL")] pub proxy: ArgProxy, @@ -333,15 +333,12 @@ impl ArgProxy { Some(UserKey::new(username, password)) }; - let scheme = url.scheme(); - let proxy_type = match url.scheme().to_ascii_lowercase().as_str() { - "socks4" => Some(ProxyType::Socks4), - "socks5" => Some(ProxyType::Socks5), - "http" => Some(ProxyType::Http), - _ => None, - } - .ok_or(Error::from(&format!("`{scheme}` is an invalid proxy type")))?; + "socks4" => Ok(ProxyType::Socks4), + "socks5" => Ok(ProxyType::Socks5), + "http" => Ok(ProxyType::Http), + scheme => Err(Error::from(&format!("`{scheme}` is an invalid proxy type"))), + }?; Ok(ArgProxy { proxy_type, From 58364580f50abece5750e7e2ced7f66b7d97837b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 9 Apr 2024 12:57:18 +0800 Subject: [PATCH 041/196] TryFrom for ProxyType --- src/args.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/args.rs b/src/args.rs index d0cc99f..46c00e8 100644 --- a/src/args.rs +++ b/src/args.rs @@ -333,12 +333,7 @@ impl ArgProxy { Some(UserKey::new(username, password)) }; - let proxy_type = match url.scheme().to_ascii_lowercase().as_str() { - "socks4" => Ok(ProxyType::Socks4), - "socks5" => Ok(ProxyType::Socks5), - "http" => Ok(ProxyType::Http), - scheme => Err(Error::from(&format!("`{scheme}` is an invalid proxy type"))), - }?; + let proxy_type = url.scheme().to_ascii_lowercase().as_str().try_into()?; Ok(ArgProxy { proxy_type, @@ -358,6 +353,19 @@ pub enum ProxyType { None, } +impl TryFrom<&str> for ProxyType { + type Error = Error; + fn try_from(value: &str) -> Result { + match value { + "http" => Ok(ProxyType::Http), + "socks4" => Ok(ProxyType::Socks4), + "socks5" => Ok(ProxyType::Socks5), + "none" => Ok(ProxyType::None), + scheme => Err(Error::from(&format!("`{scheme}` is an invalid proxy type"))), + } + } +} + impl std::fmt::Display for ProxyType { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { From 7bee2e096817c700ef0bd67c3235b770864a6ef9 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 9 Apr 2024 14:24:22 +0800 Subject: [PATCH 042/196] TryFrom for ArgProxy --- src/android.rs | 2 +- src/apple.rs | 2 +- src/args.rs | 7 ++++--- src/desktop_api.rs | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/android.rs b/src/android.rs index 0f62d1c..52d6af3 100644 --- a/src/android.rs +++ b/src/android.rs @@ -35,7 +35,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( .with_filter(filter), ); let proxy_url = get_java_string(&mut env, &proxy_url).unwrap(); - let proxy = ArgProxy::from_url(proxy_url).unwrap(); + let proxy = ArgProxy::try_from(proxy_url).unwrap(); let mut args = Args::default(); args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns).verbosity(verbosity); diff --git a/src/apple.rs b/src/apple.rs index 87fc81b..0a5d1c3 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -31,7 +31,7 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( } let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); - let proxy = ArgProxy::from_url(proxy_url).unwrap(); + let proxy = ArgProxy::try_from(proxy_url).unwrap(); let mut args = Args::default(); args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns_strategy).verbosity(verbosity); diff --git a/src/args.rs b/src/args.rs index 46c00e8..b37521f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -13,7 +13,7 @@ pub struct Args { /// where proto is one of socks4, socks5, http. /// Username and password are encoded in percent encoding. For example: /// socks5://myname:pass%40word@127.0.0.1:1080 - #[arg(short, long, value_parser = ArgProxy::from_url, value_name = "URL")] + #[arg(short, long, value_parser = |s: &str| ArgProxy::try_from(s), value_name = "URL")] pub proxy: ArgProxy, /// Name of the tun interface, such as tun0, utun4, etc. @@ -297,8 +297,9 @@ impl std::fmt::Display for ArgProxy { } } -impl ArgProxy { - pub fn from_url(s: &str) -> Result { +impl TryFrom<&str> for ArgProxy { + type Error = Error; + fn try_from(s: &str) -> Result { if s == "none" { return Ok(ArgProxy { proxy_type: ProxyType::None, diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 44f16c4..15288ef 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -47,7 +47,7 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( } let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); - let proxy = ArgProxy::from_url(proxy_url).unwrap(); + let proxy = ArgProxy::try_from(proxy_url).unwrap(); let tun = std::ffi::CStr::from_ptr(tun).to_str().unwrap().to_string(); let mut args = Args::default(); From 2df59ae5962398e1342354c0036d56abfe964f00 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:26:34 +0800 Subject: [PATCH 043/196] UDP read time out case --- src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 71f8298..59bc4b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -329,7 +329,14 @@ where async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc>) -> crate::Result<()> { let mut buf = [0_u8; 4096]; loop { - let len = udp.read(&mut buf).await?; + let len = match udp.read(&mut buf).await { + Err(e) => { + // indicate UDP read fails not an error. + log::debug!("Virtual DNS session error: {}", e); + break; + } + Ok(len) => len, + }; if len == 0 { break; } From 09994d43cc8ceea173e3bd95370fef39af168fbc Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Tue, 9 Apr 2024 23:11:48 +0200 Subject: [PATCH 044/196] Fix routing issues described in #104 --- Cargo.toml | 2 +- src/desktop_api.rs | 36 ++---------------------------------- 2 files changed, 3 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 71d6b70..2c100ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ socks5-impl = { version = "0.5" } thiserror = "1.0" tokio = { version = "1.36", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = ">=3.0.2", features = ["log"] } +tproxy-config = { version = "4.0", features = ["log"] } trust-dns-proto = "0.23" tun2 = { version = "1.2", features = ["async"] } udp-stream = { version = "0.0", default-features = false } diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 15288ef..d49a9b0 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -108,7 +108,8 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can let mut tproxy_args = TproxyArgs::new() .tun_dns(args.dns_addr) .proxy_addr(args.proxy.addr) - .bypass_ips(&bypass_ips); + .bypass_ips(&bypass_ips) + .ipv6_default_route(args.ipv6_enabled); #[allow(unused_mut, unused_assignments, unused_variables)] let mut setup = true; @@ -135,39 +136,6 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can #[cfg(target_os = "linux")] { - let run_ip_util = |args: String| { - tokio::process::Command::new("ip") - .args(args.split(' ')) - .stdout(std::process::Stdio::null()) - .stderr(std::process::Stdio::null()) - .spawn() - .ok(); - }; - - if setup && !args.ipv6_enabled { - // Remove ipv6 connectivity if not explicitly required - // TODO: remove this when upstream will get updated - run_ip_util(format!("-6 route delete ::/1 dev {}", tproxy_args.tun_name)); - run_ip_util(format!("-6 route delete 80::/1 dev {}", tproxy_args.tun_name)); - } - - #[cfg(target_os = "linux")] - if setup && args.unshare { - // New namespace doesn't have any other routing device by default - // So our `tun` device should act as such to make space for other proxies. - run_ip_util(format!("route delete 0.0.0.0/1 dev {}", tproxy_args.tun_name)); - run_ip_util(format!("route delete 128.0.0.0/1 dev {}", tproxy_args.tun_name)); - - run_ip_util(format!("route add 0.0.0.0/0 dev {}", tproxy_args.tun_name)); - - if args.ipv6_enabled { - run_ip_util(format!("-6 route delete ::/1 dev {}", tproxy_args.tun_name)); - run_ip_util(format!("-6 route delete 80::/1 dev {}", tproxy_args.tun_name)); - - run_ip_util(format!("-6 route add ::/0 dev {}", tproxy_args.tun_name)); - } - } - let mut admin_command_args = args.admin_command.iter(); if let Some(command) = admin_command_args.next() { let child = tokio::process::Command::new(command) From d5d847fa92e6703732be724dc55f651ae63eba3b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 14 Apr 2024 19:18:18 +0800 Subject: [PATCH 045/196] Bump version 0.2.17 --- Cargo.toml | 8 ++++---- src/lib.rs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2c100ec..a9b7b2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.16" +version = "0.2.17" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" @@ -29,11 +29,11 @@ log = { version = "0.4", features = ["std"] } percent-encoding = "2" socks5-impl = { version = "0.5" } thiserror = "1.0" -tokio = { version = "1.36", features = ["full"] } +tokio = { version = "1", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "4.0", features = ["log"] } +tproxy-config = { version = "4", features = ["log"] } trust-dns-proto = "0.23" -tun2 = { version = "1.2", features = ["async"] } +tun2 = { version = "1.3", features = ["async"] } udp-stream = { version = "0.0", default-features = false } unicase = "2.7" url = "2.5" diff --git a/src/lib.rs b/src/lib.rs index 59bc4b9..9ee8e5f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,7 @@ pub async fn run(device: D, mtu: u16, args: Args, shutdown_token: Cancellatio where D: AsyncRead + AsyncWrite + Unpin + Send + 'static, { + log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); let server_addr = args.proxy.addr; From f418ca4fe7dd9ec11b48a3a44ff81b346b5d35bd Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Thu, 18 Apr 2024 21:45:09 +0200 Subject: [PATCH 046/196] Fix over-tcp DNS for HTTP proxies --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 9ee8e5f..850d123 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -272,6 +272,7 @@ where info.dst.set_ip(dns_addr); } if args.dns == ArgDns::OverTcp { + info.protocol = IpProtocol::Tcp; let proxy_handler = mgr.new_proxy_handler(info, None, false).await?; let socket_queue = socket_queue.clone(); tokio::spawn(async move { From 8aa2a66942cf7aa66094be65eb3de98e7ef58320 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Thu, 18 Apr 2024 22:20:05 +0200 Subject: [PATCH 047/196] Add functional tests --- tests/tests.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tests/tests.py diff --git a/tests/tests.py b/tests/tests.py new file mode 100644 index 0000000..076411a --- /dev/null +++ b/tests/tests.py @@ -0,0 +1,68 @@ +import glob +import logging +import os +import unittest +import subprocess +import requests +import dotenv +import time +import itertools + +dotenv.load_dotenv() + + +def get_ip(version=None): + """provider = 'https://%swtfismyip.com/text' + prefix = { + None: '', + 4: 'ipv4.', + 6: 'ipv6.' + }[version]""" + provider = 'https://%sipify.org' + prefix = { + None: 'api64.', + 4: 'api4.', + 6: 'api6.' + }[version] + result = requests.Session().get(provider % prefix).text.strip() + return result + + +def get_tool_path(): + default = glob.glob(os.path.join(os.path.dirname(__file__), '..', 'target', '*', 'tun2proxy')) + default = default[0] if len(default) > 0 else 'tun2proxy' + return os.environ.get('TOOL_PATH', default) + + +def start_process(*args): + pass + + +class Tun2ProxyTest(unittest.TestCase): + @staticmethod + def _test(ip_version, dns, proxy_var): + ip_noproxy = get_ip(ip_version) + print(ip_noproxy) + additional = ['-6'] if ip_version == 6 else [] + p = subprocess.Popen([get_tool_path(), "--proxy", os.getenv(proxy_var), '--setup', '-v', 'trace', '--dns', dns, *additional]) + try: + time.sleep(1) + ip_withproxy = get_ip(ip_version) + print(ip_withproxy) + + assert ip_noproxy != ip_withproxy + except Exception as e: + raise e + finally: + p.terminate() + p.wait() + + @classmethod + def add_tests(cls): + for ip_version, dns, proxy_var in itertools.product([None, 4, 6], ['virtual', 'over-tcp'], ['SOCKS5_PROXY', 'HTTP_PROXY']): + setattr(cls, 'test_ipv%s_dns%s_proxy%s' % (ip_version, dns, proxy_var), lambda self: cls._test(ip_version, dns, proxy_var)) + + +if __name__ == '__main__': + Tun2ProxyTest.add_tests() + unittest.main() From 03f98a0741e2c81b2f64d837766e4aa73c1f75fa Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Thu, 18 Apr 2024 22:39:07 +0200 Subject: [PATCH 048/196] Update tproxy-config dependency --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a9b7b2d..940c2c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ socks5-impl = { version = "0.5" } thiserror = "1.0" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "4", features = ["log"] } +tproxy-config = { version = "4.0.2", features = ["log"] } trust-dns-proto = "0.23" tun2 = { version = "1.3", features = ["async"] } udp-stream = { version = "0.0", default-features = false } From c36c4ecf1b3e79bd7e93091c8ae1394507cd9e6a Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Thu, 18 Apr 2024 22:47:16 +0200 Subject: [PATCH 049/196] Add CI workflow for functional tests --- .github/workflows/tests.yml | 33 +++++++++++++++++++++++++++++++++ tests/requirements.txt | 2 ++ tests/tests.py | 32 ++++++++++++++++---------------- 3 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 tests/requirements.txt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..9e6f1ab --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,33 @@ +on: + pull_request_review: + types: [submitted] + push: + workflow_dispatch: + pull_request_target: + types: [labeled] + +name: Integration Tests + +jobs: + proxy_tests: + name: Proxy Tests + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'safe to test') + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Populate .env + env: + DOTENV: ${{ secrets.DOTENV }} + run: echo "$DOTENV" > .env + - name: Run tests + run: >- + pwd; + ls -la; + sudo python -m pip install -r tests/requirements.txt; + cargo build --release; + sudo python tests/tests.py \ No newline at end of file diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..d44fe44 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,2 @@ +requests +python-dotenv \ No newline at end of file diff --git a/tests/tests.py b/tests/tests.py index 076411a..0ec0891 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,12 +1,12 @@ import glob -import logging -import os -import unittest -import subprocess -import requests -import dotenv -import time import itertools +import os +import subprocess +import time +import unittest + +import dotenv +import requests dotenv.load_dotenv() @@ -34,21 +34,16 @@ def get_tool_path(): return os.environ.get('TOOL_PATH', default) -def start_process(*args): - pass - - class Tun2ProxyTest(unittest.TestCase): @staticmethod def _test(ip_version, dns, proxy_var): ip_noproxy = get_ip(ip_version) - print(ip_noproxy) additional = ['-6'] if ip_version == 6 else [] - p = subprocess.Popen([get_tool_path(), "--proxy", os.getenv(proxy_var), '--setup', '-v', 'trace', '--dns', dns, *additional]) + p = subprocess.Popen( + [get_tool_path(), "--proxy", os.getenv(proxy_var), '--setup', '-v', 'trace', '--dns', dns, *additional]) try: time.sleep(1) ip_withproxy = get_ip(ip_version) - print(ip_withproxy) assert ip_noproxy != ip_withproxy except Exception as e: @@ -59,8 +54,13 @@ class Tun2ProxyTest(unittest.TestCase): @classmethod def add_tests(cls): - for ip_version, dns, proxy_var in itertools.product([None, 4, 6], ['virtual', 'over-tcp'], ['SOCKS5_PROXY', 'HTTP_PROXY']): - setattr(cls, 'test_ipv%s_dns%s_proxy%s' % (ip_version, dns, proxy_var), lambda self: cls._test(ip_version, dns, proxy_var)) + ip_options = [None, 4] + if bool(int(os.environ.get('IPV6', 1))): + ip_options.append(6) + for ip_version, dns, proxy_var in itertools.product(ip_options, ['virtual', 'over-tcp'], + ['SOCKS5_PROXY', 'HTTP_PROXY']): + setattr(cls, 'test_ipv%s_dns%s_proxy%s' % (ip_version, dns, proxy_var), + lambda self: cls._test(ip_version, dns, proxy_var)) if __name__ == '__main__': From 8438eddc952ce1ec59c927cee8c37954eca4dfbc Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 19 Apr 2024 22:17:36 +0800 Subject: [PATCH 050/196] The bypass value is IP/CIDR now --- Cargo.toml | 2 +- README.md | 5 +++-- src/args.rs | 11 +++++++---- src/desktop_api.rs | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 940c2c4..e8ea745 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ socks5-impl = { version = "0.5" } thiserror = "1.0" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "4.0.2", features = ["log"] } +tproxy-config = { version = "5.0.0", features = ["log"] } trust-dns-proto = "0.23" tun2 = { version = "1.3", features = ["async"] } udp-stream = { version = "0.0", default-features = false } diff --git a/README.md b/README.md index ea290a4..543f7c6 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Apart from SOCKS5, SOCKS4 and HTTP are supported. Note that if your proxy is a non-global IP address (e.g. because the proxy is provided by some tunneling tool running locally), you will additionally need to provide the public IP address of the server through which the traffic is -actually tunneled. In such a case, the tool will tell you to specify the address through `--bypass ` if you +actually tunneled. In such a case, the tool will tell you to specify the address through `--bypass ` if you wish to make use of the automated setup feature. ## Manual Setup @@ -134,7 +134,8 @@ Options: See `capabilities(7)` -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct] --dns-addr DNS resolver address [default: 8.8.8.8] - -b, --bypass IPs used in routing setup which should bypass the tunnel + -b, --bypass IPs used in routing setup which should bypass the tunnel, in the form of IP or IP/CIDR. + Multiple IPs can be specified, e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8 --tcp-timeout TCP timeout in seconds [default: 600] --udp-timeout UDP timeout in seconds [default: 10] -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] diff --git a/src/args.rs b/src/args.rs index b37521f..7578e10 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,5 +1,6 @@ use crate::{Error, Result}; use socks5_impl::protocol::UserKey; +use tproxy_config::IpCidr; #[cfg(target_os = "linux")] use std::ffi::OsString; @@ -62,9 +63,11 @@ pub struct Args { #[arg(long, value_name = "IP", default_value = "8.8.8.8")] pub dns_addr: IpAddr, - /// IPs used in routing setup which should bypass the tunnel - #[arg(short, long, value_name = "IP")] - pub bypass: Vec, + /// IPs used in routing setup which should bypass the tunnel, + /// in the form of IP or IP/CIDR. Multiple IPs can be specified, + /// e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8 + #[arg(short, long, value_name = "IP/CIDR")] + pub bypass: Vec, /// TCP timeout in seconds #[arg(long, value_name = "seconds", default_value = "600")] @@ -158,7 +161,7 @@ impl Args { self } - pub fn bypass(&mut self, bypass: IpAddr) -> &mut Self { + pub fn bypass(&mut self, bypass: IpCidr) -> &mut Self { self.bypass.push(bypass); self } diff --git a/src/desktop_api.rs b/src/desktop_api.rs index d49a9b0..311f272 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -16,7 +16,7 @@ static TUN_QUIT: std::sync::Mutex> = /// Parameters: /// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" /// - tun: the tun device name, e.g. "utun5" -/// - bypass: the bypass ip, e.g. "123.45.67.89" +/// - bypass: the bypass IP/CIDR, e.g. "123.45.67.0/24" /// - dns_strategy: the dns strategy, see ArgDns enum /// - root_privilege: whether to run with root privilege /// - verbosity: the verbosity level, see ArgVerbosity enum From a9ef8f658b8b8a019da1e5d2291d2cf3ffdfc4db Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 20 Apr 2024 14:56:42 +0000 Subject: [PATCH 051/196] Android build script --- .github/workflows/publish-exe.yml | 4 ++ .gitignore | 2 + build-android.sh | 113 ++++++++++++++++++++++++++++++ src/apple.rs | 2 +- 4 files changed, 120 insertions(+), 1 deletion(-) create mode 100755 build-android.sh diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 6bcb516..8f31235 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -77,6 +77,10 @@ jobs: fi elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so + if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then + ./build-android.sh + cp ./tun2proxy-android-libs.zip ./mypubdir4/ + fi fi - name: Publish diff --git a/.gitignore b/.gitignore index 59800e0..1d9d317 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +tun2proxy-android-libs.zip +tun2proxy-android-libs/ tun2proxy.xcframework/ .env project.xcworkspace/ diff --git a/build-android.sh b/build-android.sh new file mode 100755 index 0000000..fd1ed13 --- /dev/null +++ b/build-android.sh @@ -0,0 +1,113 @@ +#! /bin/bash + +work_dir=$(pwd) + +export ANDROID_HOME=/tmp/Android/sdk +export NDK_HOME=${ANDROID_HOME}/ndk/25.2.9519653 +export PATH=$ANDROID_HOME/cmdline-tools/bin:$PATH +mkdir -p $ANDROID_HOME + +name=tun2proxy +package=tun2proxy +BASE=`dirname "$0"` +android_libs=$BASE/${name}-android-libs +mkdir -p $android_libs + +function setup_env() { + cargo install cbindgen + apt update && apt install -y make llvm-dev libclang-dev clang pkg-config zip unzip curl default-jdk build-essential + cd /tmp/ + curl -OL https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip + rm -rf /tmp/cmdline-tools + unzip commandlinetools-linux-6858069_latest.zip + rm -rf $ANDROID_HOME/cmdline-tools + mv cmdline-tools $ANDROID_HOME + yes | sdkmanager --sdk_root=$ANDROID_HOME --licenses + sdkmanager --sdk_root=$ANDROID_HOME "ndk;25.2.9519653" "platforms;android-21" +} + +function build_android() { + local manifest=./Cargo.toml + local mode=--release + local mode2=release + local targets= + + if [ ! -z "$2" ]; then + targets="$2" + else + targets="aarch64-linux-android armv7-linux-androideabi x86_64-linux-android i686-linux-android" + fi + + for target in $targets; do + rustup target add $target + done + + if [ "$1" = "debug" ]; then + mode= + mode2=debug + fi + + local BASE=`dirname "$0"` + local HOST_OS=`uname -s | tr "[:upper:]" "[:lower:]"` + local HOST_ARCH=`uname -m | tr "[:upper:]" "[:lower:]"` + + export PATH="$NDK_HOME/toolchains/llvm/prebuilt/$HOST_OS-$HOST_ARCH/bin/":$PATH + + local android_tools="$NDK_HOME/toolchains/llvm/prebuilt/$HOST_OS-$HOST_ARCH/bin" + local api=21 + + for target in $targets; do + local target_dir= + case $target in + 'armv7-linux-androideabi') + export CC_armv7_linux_androideabi="$android_tools/armv7a-linux-androideabi${api}-clang" + export AR_armv7_linux_androideabi="$android_tools/llvm-ar" + export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$android_tools/armv7a-linux-androideabi${api}-clang" + target_dir=armeabi-v7a + ;; + 'x86_64-linux-android') + export CC_x86_64_linux_android="$android_tools/${target}${api}-clang" + export AR_x86_64_linux_android="$android_tools/llvm-ar" + export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$android_tools/${target}${api}-clang" + target_dir=x86_64 + ;; + 'aarch64-linux-android') + export CC_aarch64_linux_android="$android_tools/${target}${api}-clang" + export AR_aarch64_linux_android="$android_tools/llvm-ar" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$android_tools/${target}${api}-clang" + target_dir=arm64-v8a + ;; + 'i686-linux-android') + export CC_i686_linux_android="$android_tools/${target}${api}-clang" + export AR_i686_linux_android="$android_tools/llvm-ar" + export CARGO_TARGET_I686_LINUX_ANDROID_LINKER="$android_tools/${target}${api}-clang" + target_dir=x86 + ;; + *) + echo "Unknown target $target" + ;; + esac + cargo build --target $target $mode + mkdir -p $android_libs/$target_dir + cp $BASE/target/$target/${mode2}/lib${name}.so $android_libs/${target_dir}/lib${name}.so + done + + cbindgen -c $BASE/cbindgen.toml -l C -o $android_libs/$name.h +} + +function main() { + echo "Setting up the build environment..." + setup_env + cd $work_dir + + echo "build android target" + build_android "$@" + cd $work_dir + + echo "Creating zip file" + rm -rf ${name}-android-libs.zip + zip -r ${name}-android-libs.zip ${name}-android-libs +} + +main "$@" + diff --git a/src/apple.rs b/src/apple.rs index 0a5d1c3..3776306 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -1,4 +1,4 @@ -#![cfg(any(target_os = "ios", target_os = "macos"))] +#![cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))] use crate::{ args::{ArgDns, ArgProxy}, From 04a0555101cfedc81d6bbc984dd8dd116738aa4a Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 23 Apr 2024 13:22:27 +0800 Subject: [PATCH 052/196] test scripts issues --- scripts/iperf3.sh | 2 +- scripts/rperf.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/iperf3.sh b/scripts/iperf3.sh index f491072..f5b388b 100755 --- a/scripts/iperf3.sh +++ b/scripts/iperf3.sh @@ -39,7 +39,7 @@ sleep 1 ip tuntap add name tun0 mode tun ip link set tun0 up ip route add 10.0.0.4 dev tun0 -"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off & +"$tun2proxy" --tun tun0 --proxy socks5://10.0.0.3:10800 -v off & sleep 3 diff --git a/scripts/rperf.sh b/scripts/rperf.sh index 1f06986..00b43a3 100755 --- a/scripts/rperf.sh +++ b/scripts/rperf.sh @@ -60,7 +60,7 @@ sleep 1 ip tuntap add name tun0 mode tun ip link set tun0 up ip route add 10.0.0.4 dev tun0 -"$tun2proxy" --proxy socks5://10.0.0.3:10800 -v off & +"$tun2proxy" --tun tun0 --proxy socks5://10.0.0.3:10800 -v off & sleep 3 From 5e32994f91bc8a1426f7655527c05903970b46a2 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 23 Apr 2024 19:54:19 +0800 Subject: [PATCH 053/196] unhandled transport --- src/lib.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 850d123..accc745 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -319,8 +319,13 @@ where } } } - _ => { - log::trace!("Unknown transport"); + IpStackStream::UnknownTransport(u) => { + let len = u.payload().len(); + log::info!("#0 unhandled transport - Ip Protocol {:?}, length {}", u.ip_protocol(), len); + continue; + } + IpStackStream::UnknownNetwork(pkt) => { + log::info!("#0 unknown transport - {} bytes", pkt.len()); continue; } } From 0f241325ad2f352e9cdfde3565524d387124134b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:13:45 +0800 Subject: [PATCH 054/196] Bump version 0.2.18 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e8ea745..313c0db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.17" +version = "0.2.18" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 64dd43c6f394d63a6ba3d2bc94d9d1aac60b31ee Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 29 Apr 2024 11:40:39 +0800 Subject: [PATCH 055/196] cbindgen issues in publish script --- .github/workflows/publish-exe.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 8f31235..898c3ee 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -50,6 +50,7 @@ jobs: run: | mkdir mypubdir4 rustup target add ${{ matrix.target }} + cargo install cbindgen if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then sudo .github/workflows/install-cross.sh fi From 3980b985f2aad09147e14ea6a0263cd70dfc6868 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 5 May 2024 17:02:58 +0800 Subject: [PATCH 056/196] warnings removed --- src/proxy_handler.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/proxy_handler.rs b/src/proxy_handler.rs index fb9d9f4..74ccfdb 100644 --- a/src/proxy_handler.rs +++ b/src/proxy_handler.rs @@ -14,7 +14,9 @@ pub(crate) trait ProxyHandler: Send + Sync { fn consume_data(&mut self, dir: OutgoingDirection, size: usize); fn peek_data(&mut self, dir: OutgoingDirection) -> OutgoingDataEvent; fn connection_established(&self) -> bool; + #[allow(dead_code)] fn data_len(&self, dir: OutgoingDirection) -> usize; + #[allow(dead_code)] fn reset_connection(&self) -> bool; fn get_udp_associate(&self) -> Option; } From 3202e7bbd2db814612ab31d83a3819227c6e6a65 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 9 May 2024 18:26:53 +0800 Subject: [PATCH 057/196] Bump version 0.2.19 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 313c0db..3aebc6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.18" +version = "0.2.19" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 588364d0604304489193adb0c33f91527ba0ddd6 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 16 May 2024 13:18:02 +0800 Subject: [PATCH 058/196] Add files via upload --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..219c13a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "daily" From e6360d83a7c2d6660236e3226eb22760a16a25de Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 16 May 2024 13:30:10 +0800 Subject: [PATCH 059/196] refine code --- src/http.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http.rs b/src/http.rs index 00a14b4..2b5f491 100644 --- a/src/http.rs +++ b/src/http.rs @@ -256,7 +256,7 @@ impl HttpConnection { // [RFC-9112](https://datatracker.ietf.org/doc/html/rfc9112#body.content-length) // Transfer-Encoding isn't supported yet - if headers_map.get(&UniCase::new(TRANSFER_ENCODING)).is_some() { + if headers_map.contains_key(&UniCase::new(TRANSFER_ENCODING)) { unimplemented!("Header Transfer-Encoding not supported"); } From d062b1b66a6e27b88372a4ebc70c0a26fe4d808f Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 23 May 2024 21:27:42 +0800 Subject: [PATCH 060/196] Fix #115 --- .github/workflows/publish-exe.yml | 2 +- build-aarch64-apple-ios-debug.sh | 2 +- build-aarch64-apple-ios.sh | 2 +- build-android.sh | 2 +- build-apple.sh | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 898c3ee..6b89324 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -63,7 +63,7 @@ jobs: else cargo build --all-features --release --target ${{ matrix.target }} fi - cbindgen --config cbindgen.toml -l C -o target/tun2proxy-ffi.h + cbindgen --config cbindgen.toml -l C --cpp-compat -o target/tun2proxy-ffi.h if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then powershell -Command "(Get-Item README.md).LastWriteTime = Get-Date" powershell -Command "(Get-Item target/${{ matrix.target }}/release/wintun.dll).LastWriteTime = Get-Date" diff --git a/build-aarch64-apple-ios-debug.sh b/build-aarch64-apple-ios-debug.sh index 538865b..9a740d4 100755 --- a/build-aarch64-apple-ios-debug.sh +++ b/build-aarch64-apple-ios-debug.sh @@ -10,7 +10,7 @@ cargo build --target aarch64-apple-ios echo "Generating includes..." mkdir -p target/include/ rm -rf target/include/* -cbindgen --config cbindgen.toml -l C -o target/include/tun2proxy.h +cbindgen --config cbindgen.toml -l C --cpp-compat -o target/include/tun2proxy.h cat > target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < Date: Sun, 26 May 2024 09:34:49 +0800 Subject: [PATCH 061/196] fix nix error cos upgrade --- src/socket_transfer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socket_transfer.rs b/src/socket_transfer.rs index 194c6f7..f10b3f2 100644 --- a/src/socket_transfer.rs +++ b/src/socket_transfer.rs @@ -168,7 +168,7 @@ where // Process received file descriptors let mut sockets = Vec::::with_capacity(number as usize); - for cmsg in msg.cmsgs() { + for cmsg in msg.cmsgs()? { if let ControlMessageOwned::ScmRights(fds) = cmsg { for fd in fds { if fd < 0 { From 30a54329e47822679828bebe6d9e95f41baf5ed3 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 26 May 2024 10:03:15 +0800 Subject: [PATCH 062/196] Fix #114 --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 3aebc6b..809d98f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,3 +57,6 @@ serde_json = "1.0" [[bin]] name = "tun2proxy" path = "src/bin/main.rs" + +[profile.release] +strip = "symbols" From 1023f00d12b84790b8e0b75d88271dbf358a0b69 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 26 May 2024 10:56:40 +0800 Subject: [PATCH 063/196] Bump version 0.2.20 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 809d98f..2f18fe1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.19" +version = "0.2.20" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 4b0ca087ebc71edcf278ff542fc04c84cdf971d7 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 30 May 2024 20:06:15 +0800 Subject: [PATCH 064/196] Don't play with fire --- src/android.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/android.rs b/src/android.rs index 52d6af3..d41bb74 100644 --- a/src/android.rs +++ b/src/android.rs @@ -35,7 +35,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( .with_filter(filter), ); let proxy_url = get_java_string(&mut env, &proxy_url).unwrap(); - let proxy = ArgProxy::try_from(proxy_url).unwrap(); + let proxy = ArgProxy::try_from(proxy_url.as_str()).unwrap(); let mut args = Args::default(); args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns).verbosity(verbosity); @@ -50,8 +50,6 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNI crate::mobile_api::mobile_stop() } -unsafe fn get_java_string<'a>(env: &'a mut JNIEnv, string: &'a JString) -> Result<&'a str, Error> { - let str_ptr = env.get_string(string)?.as_ptr(); - let s: &str = std::ffi::CStr::from_ptr(str_ptr).to_str()?; - Ok(s) +fn get_java_string(env: &mut JNIEnv, string: &JString) -> Result { + Ok(env.get_string(string)?.into()) } From a082a6f45ba0c4be4c5c56a152172c4362722029 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Fri, 31 May 2024 23:14:55 +0200 Subject: [PATCH 065/196] Enable build verification --- .github/workflows/publish-exe.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 6b89324..120b96b 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -84,6 +84,11 @@ jobs: fi fi + # Enable build verification + - name: Verifiable Build + shell: bash + run: sha256sum ./mypubdir4/* + - name: Publish uses: softprops/action-gh-release@v1 env: From 4554d3bc5500455e2a302aa37b0e770b6a46f249 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sat, 1 Jun 2024 02:00:45 +0200 Subject: [PATCH 066/196] Build provenance attestation --- .github/workflows/publish-exe.yml | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 120b96b..9347830 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -8,6 +8,12 @@ name: Publish Releases jobs: build_publish: name: Publishing Tasks + + permissions: + id-token: write + contents: read + attestations: write + strategy: matrix: target: @@ -84,10 +90,24 @@ jobs: fi fi - # Enable build verification - - name: Verifiable Build + # Support verifiable builds + - name: Calculate hashes shell: bash - run: sha256sum ./mypubdir4/* + run: | + echo "--- BEGIN SHA256SUM ---" + sha256sum ./mypubdir4/* + echo "--- END SHA256SUM ---" + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: bin + path: mypubdir4/* + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-path: mypubdir4/* - name: Publish uses: softprops/action-gh-release@v1 From 07ffbe057c741141256dcd9f681829af3577365e Mon Sep 17 00:00:00 2001 From: Koi to Coco Date: Sat, 1 Jun 2024 10:26:16 +0000 Subject: [PATCH 067/196] Write unshare pid into file for scripting purposes --- src/args.rs | 7 +++++++ src/bin/main.rs | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index 7578e10..5a676df 100644 --- a/src/args.rs +++ b/src/args.rs @@ -32,6 +32,11 @@ pub struct Args { #[arg(long)] pub unshare: bool, + /// Create a pidfile of `unshare` process when using `--unshare`. + #[cfg(target_os = "linux")] + #[arg(long)] + pub unshare_pidfile: Option, + /// File descriptor for UNIX datagram socket meant to transfer /// network sockets from global namespace to the new one. /// See `unshare(1)`, `namespaces(7)`, `sendmsg(2)`, `unix(7)`. @@ -103,6 +108,8 @@ impl Default for Args { #[cfg(target_os = "linux")] unshare: false, #[cfg(target_os = "linux")] + unshare_pidfile: None, + #[cfg(target_os = "linux")] socket_transfer_fd: None, #[cfg(target_os = "linux")] admin_command: Vec::new(), diff --git a/src/bin/main.rs b/src/bin/main.rs index 9360b54..e8a4818 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -72,6 +72,7 @@ async fn namespace_proxy_main( child => child?, }; + let unshare_pid = child.id().unwrap_or(0); log::info!("The tun proxy is running in unprivileged mode. See `namespaces(7)`."); log::info!(""); log::info!("If you need to run a process that relies on root-like capabilities (e.g. `openvpn`)"); @@ -80,10 +81,13 @@ async fn namespace_proxy_main( log::info!("To run a new process in the created namespace (e.g. a flatpak app)"); log::info!( "Use `nsenter --preserve-credentials --user --net --mount --target {} /bin/sh`", - child.id().unwrap_or(0) + unshare_pid ); log::info!(""); - + if let Some(pidfile) = _args.unshare_pidfile.as_ref() { + log::info!("Writing unshare pid to {}", pidfile); + std::fs::write(pidfile, unshare_pid.to_string()).ok(); + } tokio::spawn(async move { tun2proxy::socket_transfer::process_socket_requests(&socket).await }); Ok(child.wait().await?) From 1789259f6fdb305801260ea87e47512ab6a37e47 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 5 Jun 2024 19:51:46 +0800 Subject: [PATCH 068/196] Implementation of traffic status callback --- Cargo.toml | 1 + cbindgen.toml | 2 + src/bin/main.rs | 6 +++ src/lib.rs | 42 ++++++++++++++++++++- src/traffic_status.rs | 87 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 src/traffic_status.rs diff --git a/Cargo.toml b/Cargo.toml index 2f18fe1..7e3ad69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ env_logger = "0.11" hashlink = "0.9" httparse = "1.8" ipstack = { version = "0.0" } +lazy_static = "1" log = { version = "0.4", features = ["std"] } percent-encoding = "2" socks5-impl = { version = "0.5" } diff --git a/cbindgen.toml b/cbindgen.toml index 015b262..bed1b79 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -5,6 +5,7 @@ include = [ "tun2proxy_with_name_stop", "tun2proxy_with_fd_stop", "tun2proxy_set_log_callback", + "tun2proxy_set_traffic_status_callback", ] exclude = [ "Java_com_github_shadowsocks_bg_Tun2proxy_run", @@ -14,6 +15,7 @@ exclude = [ [export.rename] "ArgVerbosity" = "Tun2proxyVerbosity" "ArgDns" = "Tun2proxyDns" +"TrafficStatus" = "Tun2proxyTrafficStatus" [enum] prefix_with_name = true diff --git a/src/bin/main.rs b/src/bin/main.rs index 9360b54..8ae508f 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -21,6 +21,12 @@ async fn main() -> Result<(), BoxError> { return; } + unsafe extern "C" fn traffic_cb(status: *const tun2proxy::TrafficStatus, _: *mut std::ffi::c_void) { + let status = &*status; + log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); + } + unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; + if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { log::error!("main loop error: {}", err); } diff --git a/src/lib.rs b/src/lib.rs index accc745..406ad0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ use udp_stream::UdpStream; pub use { args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType}, error::{BoxError, Error, Result}, + traffic_status::{tun2proxy_set_traffic_status_callback, TrafficStatus}, }; #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] @@ -53,6 +54,7 @@ mod proxy_handler; mod session_info; pub mod socket_transfer; mod socks; +mod traffic_status; mod virtual_dns; const DNS_PORT: u16 = 53; @@ -354,6 +356,29 @@ async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc(reader: &mut R, writer: &mut W, is_tx: bool) -> tokio::io::Result +where + R: tokio::io::AsyncRead + Unpin + ?Sized, + W: tokio::io::AsyncWrite + Unpin + ?Sized, +{ + let mut buf = vec![0; 8192]; + let mut total = 0; + loop { + match reader.read(&mut buf).await? { + 0 => break, // EOF + n => { + total += n as u64; + let (tx, rx) = if is_tx { (n, 0) } else { (0, n) }; + if let Err(e) = crate::traffic_status::traffic_status_update(tx, rx) { + log::debug!("Record traffic status error: {}", e); + } + writer.write_all(&buf[..n]).await?; + } + } + } + Ok(total) +} + async fn handle_tcp_session( mut tcp_stack: IpStackTcpStream, proxy_handler: Arc>, @@ -379,14 +404,14 @@ async fn handle_tcp_session( let res = tokio::join!( async move { - let r = tokio::io::copy(&mut t_rx, &mut s_tx).await; + let r = copy_and_record_traffic(&mut t_rx, &mut s_tx, true).await; if let Err(err) = s_tx.shutdown().await { log::trace!("{} s_tx shutdown error {}", session_info, err); } r }, async move { - let r = tokio::io::copy(&mut s_rx, &mut t_tx).await; + let r = copy_and_record_traffic(&mut s_rx, &mut t_tx, false).await; if let Err(err) = t_tx.shutdown().await { log::trace!("{} t_tx shutdown error {}", session_info, err); } @@ -443,6 +468,8 @@ async fn handle_udp_associate_session( } let buf1 = &buf1[..len]; + crate::traffic_status::traffic_status_update(len, 0)?; + if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { let s5addr = if let Some(domain_name) = &domain_name { Address::DomainAddress(domain_name.clone(), session_info.dst.port()) @@ -467,6 +494,8 @@ async fn handle_udp_associate_session( } let buf2 = &buf2[..len]; + crate::traffic_status::traffic_status_update(0, len)?; + if let ProxyType::Socks4 | ProxyType::Socks5 = proxy_type { // Remove SOCKS5 UDP header from the server data let header = UdpHeader::retrieve_from_stream(&mut &buf2[..])?; @@ -533,6 +562,8 @@ async fn handle_dns_over_tcp_session( buf.extend_from_slice(buf1); server.write_all(&buf).await?; + + crate::traffic_status::traffic_status_update(buf.len(), 0)?; } len = server.read(&mut buf2) => { let len = len?; @@ -541,6 +572,8 @@ async fn handle_dns_over_tcp_session( } let mut buf = buf2[..len].to_vec(); + crate::traffic_status::traffic_status_update(0, len)?; + let mut to_send: VecDeque> = VecDeque::new(); loop { if buf.len() < 2 { @@ -590,6 +623,7 @@ async fn handle_proxy_session(server: &mut TcpStream, proxy_handler: Arc 0 { server.write_all(data).await?; proxy_handler.consume_data(dir, len); + tx += len; } } + crate::traffic_status::traffic_status_update(tx, rx)?; Ok(proxy_handler.get_udp_associate()) } diff --git a/src/traffic_status.rs b/src/traffic_status.rs new file mode 100644 index 0000000..ef38d91 --- /dev/null +++ b/src/traffic_status.rs @@ -0,0 +1,87 @@ +use crate::error::{Error, Result}; +use std::os::raw::c_void; + +/// # Safety +/// +/// set traffic status callback. +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_set_traffic_status_callback( + send_interval_secs: u32, + callback: Option, + ctx: *mut c_void, +) { + if let Ok(mut cb) = TRAFFIC_STATUS_CALLBACK.lock() { + *cb = Some(TrafficStatusCallback(callback, ctx)); + } else { + log::error!("set traffic status callback failed"); + } + if send_interval_secs > 0 { + SEND_INTERVAL_SECS.store(send_interval_secs as u64, std::sync::atomic::Ordering::Relaxed); + } +} + +#[repr(C)] +#[derive(Debug, Default, Copy, Clone)] +pub struct TrafficStatus { + pub tx: u64, + pub rx: u64, +} + +#[derive(Clone)] +struct TrafficStatusCallback(Option, *mut c_void); + +impl TrafficStatusCallback { + unsafe fn call(self, info: &TrafficStatus) { + if let Some(cb) = self.0 { + cb(info, self.1); + } + } +} + +unsafe impl Send for TrafficStatusCallback {} +unsafe impl Sync for TrafficStatusCallback {} + +static TRAFFIC_STATUS_CALLBACK: std::sync::Mutex> = std::sync::Mutex::new(None); +static SEND_INTERVAL_SECS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); + +lazy_static::lazy_static! { + static ref TRAFFIC_STATUS: std::sync::Mutex = std::sync::Mutex::new(TrafficStatus::default()); + static ref TIME_STAMP: std::sync::Mutex = std::sync::Mutex::new(std::time::Instant::now()); +} + +pub(crate) fn traffic_status_update(delta_tx: usize, delta_rx: usize) -> Result<()> { + { + let is_none_or_error = TRAFFIC_STATUS_CALLBACK.lock().map(|guard| guard.is_none()).unwrap_or_else(|e| { + log::error!("Failed to acquire lock: {}", e); + true + }); + if is_none_or_error { + return Ok(()); + } + } + let traffic_status = { + let mut traffic_status = TRAFFIC_STATUS.lock().map_err(|e| Error::from(e.to_string()))?; + traffic_status.tx += delta_tx as u64; + traffic_status.rx += delta_rx as u64; + *traffic_status + }; + let old_time = { *TIME_STAMP.lock().map_err(|e| Error::from(e.to_string()))? }; + let interval_secs = SEND_INTERVAL_SECS.load(std::sync::atomic::Ordering::Relaxed); + if std::time::Instant::now().duration_since(old_time).as_secs() >= interval_secs { + send_traffic_stat(&traffic_status)?; + { + let mut time_stamp = TIME_STAMP.lock().map_err(|e| Error::from(e.to_string()))?; + *time_stamp = std::time::Instant::now(); + } + } + Ok(()) +} + +fn send_traffic_stat(traffic_status: &TrafficStatus) -> Result<()> { + if let Ok(cb) = TRAFFIC_STATUS_CALLBACK.lock() { + if let Some(cb) = cb.clone() { + unsafe { cb.call(traffic_status) }; + } + } + Ok(()) +} From 9d9c152b54a8684700f4ead03f0083c01b91fe6b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 11 Jun 2024 19:08:51 +0800 Subject: [PATCH 069/196] Bump version 0.2.21 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7e3ad69..9a9ba4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.20" +version = "0.2.21" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 203cfba302219f3f06b008244bebd0c2f284f154 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 14 Jun 2024 14:59:08 +0800 Subject: [PATCH 070/196] update deps --- Cargo.toml | 18 +++++++++--------- src/desktop_api.rs | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9a9ba4a..a8746d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,27 +17,27 @@ crate-type = ["staticlib", "cdylib", "lib"] async-trait = "0.1" base64 = { version = "0.22" } chrono = "0.4" -clap = { version = "4.5", features = ["derive", "wrap_help", "color"] } -ctrlc2 = { version = "3.5", features = ["tokio", "termination"] } +clap = { version = "4", features = ["derive", "wrap_help", "color"] } +ctrlc2 = { version = "3", features = ["tokio", "termination"] } digest_auth = "0.3" dotenvy = "0.15" env_logger = "0.11" hashlink = "0.9" -httparse = "1.8" +httparse = "1" ipstack = { version = "0.0" } lazy_static = "1" log = { version = "0.4", features = ["std"] } percent-encoding = "2" socks5-impl = { version = "0.5" } -thiserror = "1.0" +thiserror = "1" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "5.0.0", features = ["log"] } +tproxy-config = { version = "5", features = ["log"] } trust-dns-proto = "0.23" -tun2 = { version = "1.3", features = ["async"] } +tun2 = { version = "2", features = ["async"] } udp-stream = { version = "0.0", default-features = false } -unicase = "2.7" -url = "2.5" +unicase = "2" +url = "2" [target.'cfg(target_os="linux")'.dependencies] serde = { version = "1", features = ["derive"] } @@ -53,7 +53,7 @@ android_logger = "0.13" jni = { version = "0.21", default-features = false } [build-dependencies] -serde_json = "1.0" +serde_json = "1" [[bin]] name = "tun2proxy" diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 311f272..f7c03bf 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -101,7 +101,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can #[cfg(target_os = "windows")] tun_config.platform_config(|cfg| { - cfg.device_guid(Some(12324323423423434234_u128)); + cfg.device_guid(12324323423423434234_u128); }); #[allow(unused_variables)] From 628e6cba8428a9b2ee22c1b1839a39d29a64a671 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 14 Jun 2024 15:11:28 +0800 Subject: [PATCH 071/196] Bump version 0.2.22 --- .github/workflows/publish-exe.yml | 25 ------------------------- Cargo.toml | 2 +- 2 files changed, 1 insertion(+), 26 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 9347830..6b89324 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -8,12 +8,6 @@ name: Publish Releases jobs: build_publish: name: Publishing Tasks - - permissions: - id-token: write - contents: read - attestations: write - strategy: matrix: target: @@ -90,25 +84,6 @@ jobs: fi fi - # Support verifiable builds - - name: Calculate hashes - shell: bash - run: | - echo "--- BEGIN SHA256SUM ---" - sha256sum ./mypubdir4/* - echo "--- END SHA256SUM ---" - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: bin - path: mypubdir4/* - - - name: Generate artifact attestation - uses: actions/attest-build-provenance@v1 - with: - subject-path: mypubdir4/* - - name: Publish uses: softprops/action-gh-release@v1 env: diff --git a/Cargo.toml b/Cargo.toml index a8746d9..557f4f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.21" +version = "0.2.22" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From b0432c76590987b0341e064cd724af9ad565ed90 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sat, 15 Jun 2024 16:30:46 +0200 Subject: [PATCH 072/196] Re-add tested build provenance attestations --- .github/workflows/publish-exe.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 6b89324..f502f19 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -8,6 +8,12 @@ name: Publish Releases jobs: build_publish: name: Publishing Tasks + + permissions: + contents: write + id-token: write + attestations: write + strategy: matrix: target: @@ -84,6 +90,17 @@ jobs: fi fi + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: bin-${{ matrix.target }} + path: mypubdir4/* + + - name: Generate artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-path: mypubdir4/* + - name: Publish uses: softprops/action-gh-release@v1 env: From dbf960884da2e36c2cb742fabca0226c186c3996 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 11:45:39 +0800 Subject: [PATCH 073/196] Update android_logger requirement from 0.13 to 0.14 (#122) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 557f4f0..7b9fb5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ nix = { version = "0", default-features = false, features = [ ] } [target.'cfg(target_os="android")'.dependencies] -android_logger = "0.13" +android_logger = "0.14" jni = { version = "0.21", default-features = false } [build-dependencies] From b8c22db037af267f498d80d945a3a0326ae8e291 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 18 Jun 2024 17:44:06 +0800 Subject: [PATCH 074/196] build-android.sh --- build-android.sh | 50 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/build-android.sh b/build-android.sh index 13cb969..d7f2bba 100755 --- a/build-android.sh +++ b/build-android.sh @@ -2,13 +2,18 @@ work_dir=$(pwd) +ANDROID_API_VERSION=21 +# NDK homepage: https://developer.android.com/ndk/downloads#lts-downloads +ANDROID_NDK_VERSION=26.3.11579264 +# Android commandline tools homepage: https://developer.android.com/studio/index.html#command-line-tools-only +CMDLINE_TOOLS_VERSION=6858069 + export ANDROID_HOME=/tmp/Android/sdk -export NDK_HOME=${ANDROID_HOME}/ndk/25.2.9519653 +export NDK_HOME=${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION} export PATH=$ANDROID_HOME/cmdline-tools/bin:$PATH mkdir -p $ANDROID_HOME name=tun2proxy -package=tun2proxy BASE=`dirname "$0"` android_libs=$BASE/${name}-android-libs mkdir -p $android_libs @@ -17,13 +22,21 @@ function setup_env() { cargo install cbindgen apt update && apt install -y make llvm-dev libclang-dev clang pkg-config zip unzip curl default-jdk build-essential cd /tmp/ - curl -OL https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip + curl -OL https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip rm -rf /tmp/cmdline-tools - unzip commandlinetools-linux-6858069_latest.zip + unzip commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip rm -rf $ANDROID_HOME/cmdline-tools mv cmdline-tools $ANDROID_HOME yes | sdkmanager --sdk_root=$ANDROID_HOME --licenses - sdkmanager --sdk_root=$ANDROID_HOME "ndk;25.2.9519653" "platforms;android-21" + if [ $? -ne 0 ]; then + echo "Failed to accept the licenses" + exit 1 + fi + sdkmanager --sdk_root=$ANDROID_HOME "ndk;${ANDROID_NDK_VERSION}" "platforms;android-${ANDROID_API_VERSION}" + if [ $? -ne 0 ]; then + echo "Failed to install NDK" + exit 1 + fi } function build_android() { @@ -50,37 +63,35 @@ function build_android() { local BASE=`dirname "$0"` local HOST_OS=`uname -s | tr "[:upper:]" "[:lower:]"` local HOST_ARCH=`uname -m | tr "[:upper:]" "[:lower:]"` - - export PATH="$NDK_HOME/toolchains/llvm/prebuilt/$HOST_OS-$HOST_ARCH/bin/":$PATH - local android_tools="$NDK_HOME/toolchains/llvm/prebuilt/$HOST_OS-$HOST_ARCH/bin" - local api=21 + + export PATH="${android_tools}/":$PATH for target in $targets; do local target_dir= case $target in 'armv7-linux-androideabi') - export CC_armv7_linux_androideabi="$android_tools/armv7a-linux-androideabi${api}-clang" + export CC_armv7_linux_androideabi="$android_tools/armv7a-linux-androideabi${ANDROID_API_VERSION}-clang" export AR_armv7_linux_androideabi="$android_tools/llvm-ar" - export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$android_tools/armv7a-linux-androideabi${api}-clang" + export CARGO_TARGET_ARMV7_LINUX_ANDROIDEABI_LINKER="$android_tools/armv7a-linux-androideabi${ANDROID_API_VERSION}-clang" target_dir=armeabi-v7a ;; 'x86_64-linux-android') - export CC_x86_64_linux_android="$android_tools/${target}${api}-clang" + export CC_x86_64_linux_android="$android_tools/${target}${ANDROID_API_VERSION}-clang" export AR_x86_64_linux_android="$android_tools/llvm-ar" - export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$android_tools/${target}${api}-clang" + export CARGO_TARGET_X86_64_LINUX_ANDROID_LINKER="$android_tools/${target}${ANDROID_API_VERSION}-clang" target_dir=x86_64 ;; 'aarch64-linux-android') - export CC_aarch64_linux_android="$android_tools/${target}${api}-clang" + export CC_aarch64_linux_android="$android_tools/${target}${ANDROID_API_VERSION}-clang" export AR_aarch64_linux_android="$android_tools/llvm-ar" - export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$android_tools/${target}${api}-clang" + export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER="$android_tools/${target}${ANDROID_API_VERSION}-clang" target_dir=arm64-v8a ;; 'i686-linux-android') - export CC_i686_linux_android="$android_tools/${target}${api}-clang" + export CC_i686_linux_android="$android_tools/${target}${ANDROID_API_VERSION}-clang" export AR_i686_linux_android="$android_tools/llvm-ar" - export CARGO_TARGET_I686_LINUX_ANDROID_LINKER="$android_tools/${target}${api}-clang" + export CARGO_TARGET_I686_LINUX_ANDROID_LINKER="$android_tools/${target}${ANDROID_API_VERSION}-clang" target_dir=x86 ;; *) @@ -88,8 +99,13 @@ function build_android() { ;; esac cargo build --target $target $mode + if [ $? -ne 0 ]; then + echo "Failed to build for target $target" + exit 1 + fi mkdir -p $android_libs/$target_dir cp $BASE/target/$target/${mode2}/lib${name}.so $android_libs/${target_dir}/lib${name}.so + cp $BASE/target/$target/${mode2}/lib${name}.a $android_libs/${target_dir}/lib${name}.a done cbindgen -c $BASE/cbindgen.toml -l C --cpp-compat -o $android_libs/$name.h From b525d3f99e1c09350a5fb2ca5099bdb70ed60d6e Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sat, 22 Jun 2024 16:39:25 +0200 Subject: [PATCH 075/196] Virtual DNS: Do not add trailing dot --- src/virtual_dns.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/virtual_dns.rs b/src/virtual_dns.rs index be32c71..5fb5989 100644 --- a/src/virtual_dns.rs +++ b/src/virtual_dns.rs @@ -19,6 +19,7 @@ struct NameCacheEntry { /// The IP addresses are in the range of private IP addresses. /// The DNS server is implemented as a LRU cache. pub struct VirtualDns { + trailing_dot: bool, lru_cache: LruCache, name_to_ip: HashMap, network_addr: IpAddr, @@ -35,6 +36,7 @@ impl Default for VirtualDns { let broadcast_addr = calculate_broadcast_addr(start_addr, prefix_len); Self { + trailing_dot: false, next_addr: start_addr.into(), name_to_ip: HashMap::default(), network_addr: IpAddr::from(network_addr), @@ -54,7 +56,11 @@ impl VirtualDns { use crate::dns; let message = dns::parse_data_to_dns_message(data, false)?; let qname = dns::extract_domain_from_dns_message(&message)?; - let ip = self.allocate_ip(qname.clone())?; + let mut insert_name = qname.clone(); + if insert_name.ends_with('.') && !self.trailing_dot { + insert_name = String::from(insert_name.trim_end_matches('.')); + } + let ip = self.allocate_ip(insert_name)?; let message = dns::build_dns_response(message, &qname, ip, 5)?; Ok((message.to_vec()?, qname, ip)) } From 01ba8f382f0666dc295430884f5c7f2582b87774 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 23 Jun 2024 20:31:08 +0200 Subject: [PATCH 076/196] Virtual DNS: Move name canonicalization into find_or_allocate_ip --- src/virtual_dns.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/virtual_dns.rs b/src/virtual_dns.rs index 5fb5989..f470bfe 100644 --- a/src/virtual_dns.rs +++ b/src/virtual_dns.rs @@ -56,11 +56,7 @@ impl VirtualDns { use crate::dns; let message = dns::parse_data_to_dns_message(data, false)?; let qname = dns::extract_domain_from_dns_message(&message)?; - let mut insert_name = qname.clone(); - if insert_name.ends_with('.') && !self.trailing_dot { - insert_name = String::from(insert_name.trim_end_matches('.')); - } - let ip = self.allocate_ip(insert_name)?; + let ip = self.find_or_allocate_ip(qname.clone())?; let message = dns::build_dns_response(message, &qname, ip, 5)?; Ok((message.to_vec()?, qname, ip)) } @@ -106,7 +102,15 @@ impl VirtualDns { self.lru_cache.get(addr).map(|entry| &entry.name) } - fn allocate_ip(&mut self, name: String) -> Result { + fn find_or_allocate_ip(&mut self, name: String) -> Result { + // This function is a search and creation function. + // Thus, it is sufficient to canonicalize the name here. + let insert_name = if name.ends_with('.') && !self.trailing_dot { + String::from(name.trim_end_matches('.')) + } else { + name + }; + let now = Instant::now(); loop { @@ -123,7 +127,7 @@ impl VirtualDns { break; } - if let Some(ip) = self.name_to_ip.get(&name) { + if let Some(ip) = self.name_to_ip.get(&insert_name) { let ip = *ip; self.touch_ip(&ip); return Ok(ip); @@ -134,8 +138,8 @@ impl VirtualDns { loop { if let RawEntryMut::Vacant(vacant) = self.lru_cache.raw_entry_mut().from_key(&self.next_addr) { let expiry = Instant::now() + Duration::from_secs(MAPPING_TIMEOUT); - let name0 = name.clone(); - vacant.insert(self.next_addr, NameCacheEntry { name, expiry }); + let name0 = insert_name.clone(); + vacant.insert(self.next_addr, NameCacheEntry { name: insert_name, expiry }); self.name_to_ip.insert(name0, self.next_addr); return Ok(self.next_addr); } From ea0c10a5c10397be571316c5b33b0b5c30655667 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 23 Jun 2024 20:37:26 +0200 Subject: [PATCH 077/196] Add more comments to virtual DNS implementation --- src/virtual_dns.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/virtual_dns.rs b/src/virtual_dns.rs index f470bfe..966ae5a 100644 --- a/src/virtual_dns.rs +++ b/src/virtual_dns.rs @@ -113,26 +113,32 @@ impl VirtualDns { let now = Instant::now(); + // Iterate through all entries of the LRU cache and remove those that have expired. loop { let (ip, entry) = match self.lru_cache.iter().next() { None => break, Some((ip, entry)) => (ip, entry), }; + + // The entry has expired. if now > entry.expiry { let name = entry.name.clone(); self.lru_cache.remove(&ip.clone()); self.name_to_ip.remove(&name); - continue; + continue; // There might be another expired entry after this one. } - break; + + break; // The entry has not expired and all following entries are newer. } + // Return the IP if it is stored inside our LRU cache. if let Some(ip) = self.name_to_ip.get(&insert_name) { let ip = *ip; self.touch_ip(&ip); return Ok(ip); } + // Otherwise, store name and IP pair inside the LRU cache. let started_at = self.next_addr; loop { From d8d40b09debcf93e11963be848ba82b2d4d79b6d Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 23 Jun 2024 21:07:33 +0200 Subject: [PATCH 078/196] Bump version 0.2.23 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7b9fb5e..9bb5c64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.22" +version = "0.2.23" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From bb1a1fe286b12f33cdbb6587d49666ca3bc34dcd Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 23 Jun 2024 21:27:59 +0200 Subject: [PATCH 079/196] Add build provenance attestation hint --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 543f7c6..17659d9 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,13 @@ To build an XCFramework for macOS and iOS, run the following: Download the binary from [releases](https://github.com/blechschmidt/tun2proxy/releases) and put it in your `PATH`. +Since v0.2.23 [build provenance attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli) +are supported. These allow you to ensure that the builds have been generated from the code on GitHub through the GitHub +CI/CD pipeline. To verify the authenticity of the build files, you can use the [GitHub CLI](https://cli.github.com/): +```shell +gh attestation verify <*.zip file> --owner tun2proxy +``` + ### Install from source If you have [rust](https://rustup.rs/) toolchain installed, this should work: From 060ca5740f848f27099555d36fc3e342cd6bed75 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Sun, 23 Jun 2024 21:30:43 +0200 Subject: [PATCH 080/196] Format build provenance attestation section --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 17659d9..777f22d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,9 @@ To build an XCFramework for macOS and iOS, run the following: Download the binary from [releases](https://github.com/blechschmidt/tun2proxy/releases) and put it in your `PATH`. +
+ Authenticity Verification + Since v0.2.23 [build provenance attestations](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds#verifying-artifact-attestations-with-the-github-cli) are supported. These allow you to ensure that the builds have been generated from the code on GitHub through the GitHub CI/CD pipeline. To verify the authenticity of the build files, you can use the [GitHub CLI](https://cli.github.com/): @@ -44,6 +47,8 @@ CI/CD pipeline. To verify the authenticity of the build files, you can use the [ gh attestation verify <*.zip file> --owner tun2proxy ``` +
+ ### Install from source If you have [rust](https://rustup.rs/) toolchain installed, this should work: From 48f527ad81f5c114ca0159d037a655977526f446 Mon Sep 17 00:00:00 2001 From: James Brown Date: Fri, 28 Jun 2024 01:35:49 +0800 Subject: [PATCH 081/196] Update lib.rs (#130) Fix #129 --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 406ad0b..5f66aad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,7 +134,7 @@ async fn create_udp_stream(socket_queue: &Option>, peer: Socket Some(queue) => { let socket = queue.recv_udp(peer.ip().into()).await?; socket.connect(peer).await?; - UdpStream::from_tokio(socket).await + UdpStream::from_tokio(socket, peer).await } } } From 1ba8f8b16735fa041dc028b8ff41f7189e16e121 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 28 Jun 2024 13:46:25 +0800 Subject: [PATCH 082/196] Update deps --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9bb5c64..e2e3d43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ dotenvy = "0.15" env_logger = "0.11" hashlink = "0.9" httparse = "1" -ipstack = { version = "0.0" } +ipstack = { version = "0.0.10" } lazy_static = "1" log = { version = "0.4", features = ["std"] } percent-encoding = "2" @@ -35,7 +35,7 @@ tokio-util = "0.7" tproxy-config = { version = "5", features = ["log"] } trust-dns-proto = "0.23" tun2 = { version = "2", features = ["async"] } -udp-stream = { version = "0.0", default-features = false } +udp-stream = { version = "0.0.12", default-features = false } unicase = "2" url = "2" From 3b2adf92cb608efec3f03ac53675c3b5ac849aa6 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 30 Jun 2024 16:06:43 +0800 Subject: [PATCH 083/196] update deps --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e2e3d43..68e5d49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ socks5-impl = { version = "0.5" } thiserror = "1" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "5", features = ["log"] } +tproxy-config = { version = "6" } trust-dns-proto = "0.23" tun2 = { version = "2", features = ["async"] } udp-stream = { version = "0.0.12", default-features = false } From d5a404fda73dea87733dce94c5ba1931e8841d98 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:35:18 +0800 Subject: [PATCH 084/196] update deps --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 68e5d49..cbca2b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ socks5-impl = { version = "0.5" } thiserror = "1" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" -tproxy-config = { version = "6" } +tproxy-config = { version = "6", default-features = false } trust-dns-proto = "0.23" tun2 = { version = "2", features = ["async"] } udp-stream = { version = "0.0.12", default-features = false } @@ -42,7 +42,7 @@ url = "2" [target.'cfg(target_os="linux")'.dependencies] serde = { version = "1", features = ["derive"] } bincode = "1" -nix = { version = "0", default-features = false, features = [ +nix = { version = "0.29", default-features = false, features = [ "fs", "socket", "uio", From 15fe95a2c6ce1e77ad813f4c78a2bd690a169cbe Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 16 Jul 2024 11:50:58 +0800 Subject: [PATCH 085/196] Bump version 0.2.24 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cbca2b1..7d8bffb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.23" +version = "0.2.24" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 635c7e557fa973aa63fd034369648ab4daed5feb Mon Sep 17 00:00:00 2001 From: BlackbirdBop <145344469+BlackbirdBop@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:01:11 +0900 Subject: [PATCH 086/196] Make close_fd_on_drop configurable (#132) --- src/args.rs | 18 ++++++++++++++++++ src/desktop_api.rs | 12 ++++++++++-- src/mobile_api.rs | 3 +++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/args.rs b/src/args.rs index 5a676df..10d9cb7 100644 --- a/src/args.rs +++ b/src/args.rs @@ -23,9 +23,17 @@ pub struct Args { pub tun: Option, /// File descriptor of the tun interface + #[cfg(unix)] #[arg(long, value_name = "fd", conflicts_with = "tun")] pub tun_fd: Option, + /// Set whether to close the received raw file descriptor on drop or not. + /// This setting is passed to the tun2 crate. + /// See [tun2::Configuration::close_fd_on_drop]. + #[cfg(unix)] + #[arg(long, conflicts_with = "tun")] + pub close_fd_on_drop: Option, + /// Create a tun interface in a newly created unprivileged namespace /// while maintaining proxy connectivity via the global network namespace. #[cfg(target_os = "linux")] @@ -104,7 +112,10 @@ impl Default for Args { Args { proxy: ArgProxy::default(), tun: None, + #[cfg(unix)] tun_fd: None, + #[cfg(unix)] + close_fd_on_drop: None, #[cfg(target_os = "linux")] unshare: false, #[cfg(target_os = "linux")] @@ -148,11 +159,18 @@ impl Args { self } + #[cfg(unix)] pub fn tun_fd(&mut self, tun_fd: Option) -> &mut Self { self.tun_fd = tun_fd; self } + #[cfg(unix)] + pub fn close_fd_on_drop(&mut self, close_fd_on_drop: bool) -> &mut Self { + self.close_fd_on_drop = Some(close_fd_on_drop); + self + } + pub fn verbosity(&mut self, verbosity: ArgVerbosity) -> &mut Self { self.verbosity = verbosity; self diff --git a/src/desktop_api.rs b/src/desktop_api.rs index f7c03bf..6e40ae3 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -86,11 +86,19 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can let mut tun_config = tun2::Configuration::default(); tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); tun_config.destination(TUN_GATEWAY); - if let Some(tun_fd) = args.tun_fd { - tun_config.raw_fd(tun_fd); + #[cfg(unix)] + if let Some(fd) = args.tun_fd { + tun_config.raw_fd(fd); + if let Some(v) = args.close_fd_on_drop { + tun_config.close_fd_on_drop(v); + }; } else if let Some(ref tun) = args.tun { tun_config.tun_name(tun); } + #[cfg(windows)] + if let Some(ref tun) = args.tun { + tun_config.tun_name(tun); + } #[cfg(target_os = "linux")] tun_config.platform_config(|cfg| { diff --git a/src/mobile_api.rs b/src/mobile_api.rs index 2750733..69e684c 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -33,6 +33,9 @@ pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int #[cfg(unix)] if let Some(fd) = args.tun_fd { config.raw_fd(fd); + if let Some(v) = args.close_fd_on_drop { + config.close_fd_on_drop(v); + }; } else if let Some(ref tun) = args.tun { config.tun_name(tun); } From 0ca92dcdc2aea83bbc7e848ee68ba99b607d97d3 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 18 Jul 2024 19:35:14 +0800 Subject: [PATCH 087/196] minor changes --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index 10d9cb7..7ecf8ed 100644 --- a/src/args.rs +++ b/src/args.rs @@ -31,7 +31,7 @@ pub struct Args { /// This setting is passed to the tun2 crate. /// See [tun2::Configuration::close_fd_on_drop]. #[cfg(unix)] - #[arg(long, conflicts_with = "tun")] + #[arg(long, value_name = "true or false", conflicts_with = "tun")] pub close_fd_on_drop: Option, /// Create a tun interface in a newly created unprivileged namespace From e879599e6bd8e5c8dbe54ce0b3f2d6811fe20537 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 20 Jul 2024 12:28:52 +0800 Subject: [PATCH 088/196] close_fd_on_drop issues --- src/android.rs | 19 ++++++++++++++++--- src/apple.rs | 8 +++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/android.rs b/src/android.rs index d41bb74..cca174c 100644 --- a/src/android.rs +++ b/src/android.rs @@ -7,19 +7,27 @@ use crate::{ }; use jni::{ objects::{JClass, JString}, - sys::{jchar, jint}, + sys::{jboolean, jchar, jint}, JNIEnv, }; /// # Safety /// -/// Running tun2proxy +/// Running tun2proxy with some arguments +/// Parameters: +/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" +/// - tun_fd: the tun file descriptor, it will be owned by tun2proxy +/// - close_fd_on_drop: whether close the tun_fd on drop +/// - tun_mtu: the tun mtu +/// - dns_strategy: the dns strategy, see ArgDns enum +/// - verbosity: the verbosity level, see ArgVerbosity enum #[no_mangle] pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( mut env: JNIEnv, _clazz: JClass, proxy_url: JString, tun_fd: jint, + close_fd_on_drop: jboolean, tun_mtu: jchar, verbosity: jint, dns_strategy: jint, @@ -36,9 +44,14 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( ); let proxy_url = get_java_string(&mut env, &proxy_url).unwrap(); let proxy = ArgProxy::try_from(proxy_url.as_str()).unwrap(); + let close_fd_on_drop = close_fd_on_drop != 0; let mut args = Args::default(); - args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns).verbosity(verbosity); + args.proxy(proxy) + .tun_fd(Some(tun_fd)) + .close_fd_on_drop(close_fd_on_drop) + .dns(dns) + .verbosity(verbosity); crate::mobile_api::mobile_run(args, tun_mtu, false) } diff --git a/src/apple.rs b/src/apple.rs index 3776306..155b101 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -12,6 +12,7 @@ use std::os::raw::{c_char, c_int, c_ushort}; /// Parameters: /// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" /// - tun_fd: the tun file descriptor, it will be owned by tun2proxy +/// - close_fd_on_drop: whether close the tun_fd on drop /// - packet_information: whether exists packet information in tun_fd /// - tun_mtu: the tun mtu /// - dns_strategy: the dns strategy, see ArgDns enum @@ -20,6 +21,7 @@ use std::os::raw::{c_char, c_int, c_ushort}; pub unsafe extern "C" fn tun2proxy_with_fd_run( proxy_url: *const c_char, tun_fd: c_int, + close_fd_on_drop: bool, packet_information: bool, tun_mtu: c_ushort, dns_strategy: ArgDns, @@ -34,7 +36,11 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( let proxy = ArgProxy::try_from(proxy_url).unwrap(); let mut args = Args::default(); - args.proxy(proxy).tun_fd(Some(tun_fd)).dns(dns_strategy).verbosity(verbosity); + args.proxy(proxy) + .tun_fd(Some(tun_fd)) + .close_fd_on_drop(close_fd_on_drop) + .dns(dns_strategy) + .verbosity(verbosity); crate::mobile_api::mobile_run(args, tun_mtu, packet_information) } From 06ed9946551af37f925a07837787aebb9929a596 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 20 Jul 2024 12:55:01 +0800 Subject: [PATCH 089/196] Check semver script --- .github/workflows/rust.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e7609d7..af4a5be 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -36,4 +36,19 @@ jobs: - name: Abort on error if: ${{ failure() }} - run: echo "Some of jobs failed" && false \ No newline at end of file + run: echo "Some of jobs failed" && false + + semver: + name: Check semver + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: obi1kenobi/cargo-semver-checks-action@v2 From 824b443d2b4fb49598aba23a96b3cd56edf1bfff Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 20 Jul 2024 13:00:21 +0800 Subject: [PATCH 090/196] Bump version 0.3.0 --- Cargo.toml | 4 ++-- src/dns.rs | 6 +++--- src/error.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7d8bffb..96c6f8b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.2.24" +version = "0.3.0" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" @@ -23,6 +23,7 @@ digest_auth = "0.3" dotenvy = "0.15" env_logger = "0.11" hashlink = "0.9" +hickory-proto = "0.24" httparse = "1" ipstack = { version = "0.0.10" } lazy_static = "1" @@ -33,7 +34,6 @@ thiserror = "1" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" tproxy-config = { version = "6", default-features = false } -trust-dns-proto = "0.23" tun2 = { version = "2", features = ["async"] } udp-stream = { version = "0.0.12", default-features = false } unicase = "2" diff --git a/src/dns.rs b/src/dns.rs index a5ce30c..6142ba3 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -1,9 +1,9 @@ -use std::{net::IpAddr, str::FromStr}; -use trust_dns_proto::op::MessageType; -use trust_dns_proto::{ +use hickory_proto::op::MessageType; +use hickory_proto::{ op::{Message, ResponseCode}, rr::{record_type::RecordType, Name, RData, Record}, }; +use std::{net::IpAddr, str::FromStr}; pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result { let record = match ip { diff --git a/src/error.rs b/src/error.rs index 2afd19b..f460b62 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,7 +26,7 @@ pub enum Error { IpStack(#[from] ipstack::IpStackError), #[error("DnsProtoError {0:?}")] - DnsProto(#[from] trust_dns_proto::error::ProtoError), + DnsProto(#[from] hickory_proto::error::ProtoError), #[error("httparse::Error {0:?}")] Httparse(#[from] httparse::Error), From 016aaa6128ae9b68f0969f286604d909fec11bb4 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:11:23 +0800 Subject: [PATCH 091/196] Bump version 0.3.1 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 96c6f8b..d5c0914 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.3.0" +version = "0.3.1" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" @@ -25,7 +25,7 @@ env_logger = "0.11" hashlink = "0.9" hickory-proto = "0.24" httparse = "1" -ipstack = { version = "0.0.10" } +ipstack = { version = "0.1" } lazy_static = "1" log = { version = "0.4", features = ["std"] } percent-encoding = "2" From 6567b6bc003c9df2bc33e1dca4e36b4e76182bfe Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 27 Jul 2024 00:08:07 +0800 Subject: [PATCH 092/196] LazyLock usage --- Cargo.toml | 3 +-- src/traffic_status.rs | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5c0914..5e9b6ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ homepage = "https://github.com/blechschmidt/tun2proxy" authors = ["B. Blechschmidt", "ssrlive"] description = "Tunnel interface to proxy" readme = "README.md" -rust-version = "1.77" +rust-version = "1.80" [lib] crate-type = ["staticlib", "cdylib", "lib"] @@ -26,7 +26,6 @@ hashlink = "0.9" hickory-proto = "0.24" httparse = "1" ipstack = { version = "0.1" } -lazy_static = "1" log = { version = "0.4", features = ["std"] } percent-encoding = "2" socks5-impl = { version = "0.5" } diff --git a/src/traffic_status.rs b/src/traffic_status.rs index ef38d91..3117a22 100644 --- a/src/traffic_status.rs +++ b/src/traffic_status.rs @@ -1,5 +1,6 @@ use crate::error::{Error, Result}; use std::os::raw::c_void; +use std::sync::{LazyLock, Mutex}; /// # Safety /// @@ -44,10 +45,8 @@ unsafe impl Sync for TrafficStatusCallback {} static TRAFFIC_STATUS_CALLBACK: std::sync::Mutex> = std::sync::Mutex::new(None); static SEND_INTERVAL_SECS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); -lazy_static::lazy_static! { - static ref TRAFFIC_STATUS: std::sync::Mutex = std::sync::Mutex::new(TrafficStatus::default()); - static ref TIME_STAMP: std::sync::Mutex = std::sync::Mutex::new(std::time::Instant::now()); -} +static TRAFFIC_STATUS: LazyLock> = LazyLock::new(|| Mutex::new(TrafficStatus::default())); +static TIME_STAMP: LazyLock> = LazyLock::new(|| Mutex::new(std::time::Instant::now())); pub(crate) fn traffic_status_update(delta_tx: usize, delta_rx: usize) -> Result<()> { { From 1dd6746bbc8eb8c8b177dfd9d986041d08833ba0 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:18:11 +0800 Subject: [PATCH 093/196] mimalloc usage --- Cargo.toml | 1 + src/lib.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 5e9b6ac..0ee5f79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ hickory-proto = "0.24" httparse = "1" ipstack = { version = "0.1" } log = { version = "0.4", features = ["std"] } +mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" socks5-impl = { version = "0.5" } thiserror = "1" diff --git a/src/lib.rs b/src/lib.rs index 5f66aad..3df7040 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,6 +30,10 @@ pub use { traffic_status::{tun2proxy_set_traffic_status_callback, TrafficStatus}, }; +#[cfg(feature = "mimalloc")] +#[global_allocator] +static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; + #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] pub use desktop_api::desktop_run_async; From f787ff6d2342b6fe35f02439d57afdf326b69806 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:35:05 +0800 Subject: [PATCH 094/196] rust toolchain version issues --- .github/workflows/publish-exe.yml | 3 ++- .github/workflows/rust.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index f502f19..87f5a5a 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -49,7 +49,8 @@ jobs: runs-on: ${{ matrix.host_os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - name: Prepare shell: bash diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index af4a5be..17304a1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -16,7 +16,8 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - name: rustfmt if: ${{ !cancelled() }} From 3f76ccec97340392acc595057e10ac0895f3b834 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 3 Aug 2024 18:21:02 +0800 Subject: [PATCH 095/196] Apply mimalloc to iOS only --- build-aarch64-apple-ios-debug.sh | 3 +-- build-aarch64-apple-ios.sh | 3 +-- build-apple.sh | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/build-aarch64-apple-ios-debug.sh b/build-aarch64-apple-ios-debug.sh index 9a740d4..7642758 100755 --- a/build-aarch64-apple-ios-debug.sh +++ b/build-aarch64-apple-ios-debug.sh @@ -5,7 +5,7 @@ rustup target add aarch64-apple-ios cargo install cbindgen echo "Building target aarch64-apple-ios..." -cargo build --target aarch64-apple-ios +cargo build --target aarch64-apple-ios --features mimalloc echo "Generating includes..." mkdir -p target/include/ @@ -14,7 +14,6 @@ cbindgen --config cbindgen.toml -l C --cpp-compat -o target/include/tun2proxy.h cat > target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < Date: Wed, 28 Aug 2024 23:06:37 +0200 Subject: [PATCH 096/196] Implement --virtual-dns-pool --- src/args.rs | 6 ++++++ src/lib.rs | 2 +- src/virtual_dns.rs | 51 ++++++---------------------------------------- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/src/args.rs b/src/args.rs index 7ecf8ed..ef6e636 100644 --- a/src/args.rs +++ b/src/args.rs @@ -6,6 +6,7 @@ use tproxy_config::IpCidr; use std::ffi::OsString; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; +use std::str::FromStr; #[derive(Debug, Clone, clap::Parser)] #[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] @@ -76,6 +77,10 @@ pub struct Args { #[arg(long, value_name = "IP", default_value = "8.8.8.8")] pub dns_addr: IpAddr, + /// IP address pool to be used by virtual DNS in CIDR notation. + #[arg(long, value_name = "CIDR", default_value = "198.18.0.0/15")] + pub virtual_dns_pool: IpCidr, + /// IPs used in routing setup which should bypass the tunnel, /// in the form of IP or IP/CIDR. Multiple IPs can be specified, /// e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8 @@ -132,6 +137,7 @@ impl Default for Args { tcp_timeout: 600, udp_timeout: 10, verbosity: ArgVerbosity::Info, + virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(), } } } diff --git a/src/lib.rs b/src/lib.rs index 3df7040..033c542 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,7 +161,7 @@ where let dns_addr = args.dns_addr; let ipv6_enabled = args.ipv6_enabled; let virtual_dns = if args.dns == ArgDns::Virtual { - Some(Arc::new(Mutex::new(VirtualDns::new()))) + Some(Arc::new(Mutex::new(VirtualDns::new(args.virtual_dns_pool)))) } else { None }; diff --git a/src/virtual_dns.rs b/src/virtual_dns.rs index 966ae5a..8dae2a7 100644 --- a/src/virtual_dns.rs +++ b/src/virtual_dns.rs @@ -4,9 +4,9 @@ use std::{ collections::HashMap, convert::TryInto, net::{IpAddr, Ipv4Addr, Ipv6Addr}, - str::FromStr, time::{Duration, Instant}, }; +use tproxy_config::IpCidr; const MAPPING_TIMEOUT: u64 = 60; // Mapping timeout in seconds @@ -27,29 +27,17 @@ pub struct VirtualDns { next_addr: IpAddr, } -impl Default for VirtualDns { - fn default() -> Self { - let start_addr = Ipv4Addr::from_str("198.18.0.0").unwrap(); - let prefix_len = 15; - - let network_addr = calculate_network_addr(start_addr, prefix_len); - let broadcast_addr = calculate_broadcast_addr(start_addr, prefix_len); - +impl VirtualDns { + pub fn new(ip_pool: IpCidr) -> Self { Self { trailing_dot: false, - next_addr: start_addr.into(), + next_addr: ip_pool.first_address(), name_to_ip: HashMap::default(), - network_addr: IpAddr::from(network_addr), - broadcast_addr: IpAddr::from(broadcast_addr), + network_addr: ip_pool.first_address(), + broadcast_addr: ip_pool.last_address(), lru_cache: LruCache::new_unbounded(), } } -} - -impl VirtualDns { - pub fn new() -> Self { - VirtualDns::default() - } /// Returns the DNS response to send back to the client. pub fn generate_query(&mut self, data: &[u8]) -> Result<(Vec, String, IpAddr)> { @@ -160,30 +148,3 @@ impl VirtualDns { } } } - -fn calculate_network_addr(ip: std::net::Ipv4Addr, prefix_len: u8) -> std::net::Ipv4Addr { - let mask = (!0u32) << (32 - prefix_len); - let ip_u32 = u32::from_be_bytes(ip.octets()); - std::net::Ipv4Addr::from((ip_u32 & mask).to_be_bytes()) -} - -fn calculate_broadcast_addr(ip: std::net::Ipv4Addr, prefix_len: u8) -> std::net::Ipv4Addr { - let mask = (!0u32) >> prefix_len; - let ip_u32 = u32::from_be_bytes(ip.octets()); - std::net::Ipv4Addr::from((ip_u32 | mask).to_be_bytes()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_cidr_addr() { - let start_addr = Ipv4Addr::from_str("198.18.0.0").unwrap(); - let prefix_len = 15; - let network_addr = calculate_network_addr(start_addr, prefix_len); - let broadcast_addr = calculate_broadcast_addr(start_addr, prefix_len); - assert_eq!(network_addr, Ipv4Addr::from_str("198.18.0.0").unwrap()); - assert_eq!(broadcast_addr, Ipv4Addr::from_str("198.19.255.255").unwrap()); - } -} From 1e7f64919289c386d8b62cbf768d53f2cce80cce Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Wed, 28 Aug 2024 23:12:21 +0200 Subject: [PATCH 097/196] Remove unused import with updated dependencies --- src/desktop_api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 6e40ae3..edf3d8e 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::os::raw::{c_char, c_int}; use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; -use tun2::{AbstractDevice, DEFAULT_MTU as MTU}; +use tun2::DEFAULT_MTU as MTU; static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); From 8334acd085ae50fef2f4ff6187489f6a8063cddd Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Wed, 28 Aug 2024 23:17:59 +0200 Subject: [PATCH 098/196] Update version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0ee5f79..e874c3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.3.1" +version = "1.0.0" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From beb3d364a843de82ab2018fed5c7c15423779a9a Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 29 Aug 2024 09:18:46 +0800 Subject: [PATCH 099/196] fix windows issues --- Cargo.toml | 2 +- build.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e874c3c..c597d68 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "1.0.0" +version = "0.4.0" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" diff --git a/build.rs b/build.rs index a89c129..a7dc782 100644 --- a/build.rs +++ b/build.rs @@ -5,8 +5,8 @@ fn main() -> Result<(), Box> { use std::io::Write; f.write_all(format!("CARGO_TARGET_DIR: '{}'\r\n", cargo_target_dir.display()).as_bytes())?; - // The wintun crate's root directory - let crate_dir = get_crate_dir("wintun")?; + // The wintun-bindings crate's root directory + let crate_dir = get_crate_dir("wintun-bindings")?; // The path to the DLL file, relative to the crate root, depending on the target architecture let dll_path = get_wintun_bin_relative_path()?; From 15646925a79af9c8e45985cb4b0c460ded821d72 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:01:58 +0800 Subject: [PATCH 100/196] issues of parameter constraint for 'tun' --- src/args.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index ef6e636..f4f5331 100644 --- a/src/args.rs +++ b/src/args.rs @@ -20,7 +20,8 @@ pub struct Args { /// Name of the tun interface, such as tun0, utun4, etc. /// If this option is not provided, the OS will generate a random one. - #[arg(short, long, value_name = "name", conflicts_with = "tun_fd", value_parser = validate_tun)] + #[arg(short, long, value_name = "name", value_parser = validate_tun)] + #[cfg_attr(unix, arg(conflicts_with = "tun_fd"))] pub tun: Option, /// File descriptor of the tun interface From 187e251142e73c5afe0da956cbfe18a43150aaa5 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 2 Sep 2024 23:10:15 +0800 Subject: [PATCH 101/196] Bump version 0.4.1 --- .github/workflows/rust.yml | 5 ++++- Cargo.toml | 4 ++-- src/desktop_api.rs | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 17304a1..8be8418 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -33,7 +33,10 @@ jobs: - name: Build if: ${{ !cancelled() }} - run: cargo build --verbose --tests --all-features + run: | + cargo build --verbose --tests --all-features + cargo clean + cargo build --verbose - name: Abort on error if: ${{ failure() }} diff --git a/Cargo.toml b/Cargo.toml index c597d68..05103b9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.4.0" +version = "0.4.1" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" @@ -34,7 +34,7 @@ thiserror = "1" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" tproxy-config = { version = "6", default-features = false } -tun2 = { version = "2", features = ["async"] } +tun2 = { version = "3", features = ["async"] } udp-stream = { version = "0.0.12", default-features = false } unicase = "2" url = "2" diff --git a/src/desktop_api.rs b/src/desktop_api.rs index edf3d8e..02245ff 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::os::raw::{c_char, c_int}; use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; -use tun2::DEFAULT_MTU as MTU; +use tun2::{AbstractDevice, DEFAULT_MTU as MTU}; static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); @@ -124,7 +124,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can let device = tun2::create_as_async(&tun_config)?; - if let Ok(tun_name) = device.as_ref().tun_name() { + if let Ok(tun_name) = device.tun_name() { tproxy_args = tproxy_args.tun_name(&tun_name); } From 6dadc1504a44360632e678893bca374230f4e85e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 09:55:27 +0800 Subject: [PATCH 102/196] Support windows service, fix #143 --- Cargo.toml | 3 ++ src/args.rs | 7 ++++ src/bin/main.rs | 6 +++ src/lib.rs | 2 + src/win_svc.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 src/win_svc.rs diff --git a/Cargo.toml b/Cargo.toml index 05103b9..1c994e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,9 @@ nix = { version = "0.29", default-features = false, features = [ android_logger = "0.14" jni = { version = "0.21", default-features = false } +[target.'cfg(target_os = "windows")'.dependencies] +windows-service = "0.7" + [build-dependencies] serde_json = "1" diff --git a/src/args.rs b/src/args.rs index f4f5331..d7a5539 100644 --- a/src/args.rs +++ b/src/args.rs @@ -99,6 +99,11 @@ pub struct Args { /// Verbosity level #[arg(short, long, value_name = "level", value_enum, default_value = "info")] pub verbosity: ArgVerbosity, + + /// Daemonize the process as Windows service + #[cfg(target_os = "windows")] + #[arg(long)] + pub daemonize: bool, } fn validate_tun(p: &str) -> Result { @@ -139,6 +144,8 @@ impl Default for Args { udp_timeout: 10, verbosity: ArgVerbosity::Info, virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(), + #[cfg(target_os = "windows")] + daemonize: false, } } } diff --git a/src/bin/main.rs b/src/bin/main.rs index 760835f..8d4715f 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -5,6 +5,12 @@ async fn main() -> Result<(), BoxError> { dotenvy::dotenv().ok(); let args = Args::parse_args(); + #[cfg(target_os = "windows")] + if args.daemonize { + tun2proxy::win_svc::start_service()?; + return Ok(()); + } + // let default = format!("{}={:?},trust_dns_proto=warn", module_path!(), args.verbosity); let default = format!("{:?},trust_dns_proto=warn", args.verbosity); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); diff --git a/src/lib.rs b/src/lib.rs index 033c542..bb19e43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,6 +60,8 @@ pub mod socket_transfer; mod socks; mod traffic_status; mod virtual_dns; +#[doc(hidden)] +pub mod win_svc; const DNS_PORT: u16 = 53; diff --git a/src/win_svc.rs b/src/win_svc.rs new file mode 100644 index 0000000..44bb7bf --- /dev/null +++ b/src/win_svc.rs @@ -0,0 +1,98 @@ +#![cfg(windows)] + +const SERVICE_NAME: &str = "tun2proxy"; + +windows_service::define_windows_service!(ffi_service_main, my_service_main); + +pub fn start_service() -> Result<(), windows_service::Error> { + // Register generated `ffi_service_main` with the system and start the service, + // blocking this thread until the service is stopped. + windows_service::service_dispatcher::start(SERVICE_NAME, ffi_service_main)?; + Ok(()) +} + +fn my_service_main(arguments: Vec) { + // The entry point where execution will start on a background thread after a call to + // `service_dispatcher::start` from `main`. + + if let Err(_e) = run_service(arguments) { + log::error!("Error: {:?}", _e); + } +} + +fn run_service(_arguments: Vec) -> Result<(), crate::BoxError> { + use windows_service::service::ServiceControl; + use windows_service::service_control_handler::{self, ServiceControlHandlerResult}; + + let shutdown_token = crate::CancellationToken::new(); + let shutdown_token_clone = shutdown_token.clone(); + + let event_handler = move |control_event| -> ServiceControlHandlerResult { + match control_event { + ServiceControl::Stop => { + // Handle stop event and return control back to the system. + shutdown_token_clone.cancel(); + ServiceControlHandlerResult::NoError + } + // All services must accept Interrogate even if it's a no-op. + ServiceControl::Interrogate => ServiceControlHandlerResult::NoError, + _ => ServiceControlHandlerResult::NotImplemented, + } + }; + + // Register system service event handler + let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)?; + + let mut next_status = windows_service::service::ServiceStatus { + // Should match the one from system service registry + service_type: windows_service::service::ServiceType::OWN_PROCESS, + // The new state + current_state: windows_service::service::ServiceState::Running, + // Accept stop events when running + controls_accepted: windows_service::service::ServiceControlAccept::STOP, + // Used to report an error when starting or stopping only, otherwise must be zero + exit_code: windows_service::service::ServiceExitCode::Win32(0), + // Only used for pending states, otherwise must be zero + checkpoint: 0, + // Only used for pending states, otherwise must be zero + wait_hint: std::time::Duration::default(), + // Unused for setting status + process_id: None, + }; + + // Tell the system that the service is running now + status_handle.set_service_status(next_status.clone())?; + + let args = crate::Args::parse_args(); + + let default = format!("{:?},trust_dns_proto=warn", args.verbosity); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); + + let join_handle = tokio::spawn({ + async move { + unsafe extern "C" fn traffic_cb(status: *const crate::TrafficStatus, _: *mut std::ffi::c_void) { + let status = &*status; + log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); + } + unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; + + if let Err(err) = crate::desktop_run_async(args, shutdown_token).await { + log::error!("main loop error: {}", err); + } + } + }); + + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + rt.block_on(async { + if let Err(err) = join_handle.await { + log::error!("main_entry error {}", err); + } + Ok::<(), crate::Error>(()) + })?; + + // Tell the system that the service is stopped now + next_status.current_state = windows_service::service::ServiceState::Stopped; + status_handle.set_service_status(next_status)?; + + Ok(()) +} From 02b15951b63588d0cc10d943d81138c8f70f2df1 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 10:05:47 +0800 Subject: [PATCH 103/196] update Semver checking script --- .github/workflows/rust.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8be8418..6d4d6a7 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -45,14 +45,16 @@ jobs: semver: name: Check semver strategy: + fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true - - uses: obi1kenobi/cargo-semver-checks-action@v2 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - name: Check semver + if: ${{ !cancelled() }} + uses: obi1kenobi/cargo-semver-checks-action@v2 + - name: Abort on error + if: ${{ failure() }} + run: echo "Semver check failed" && false From c167f45a5e49509a420aabfdbc20d3f966746539 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 10:11:25 +0800 Subject: [PATCH 104/196] Bump version 0.4.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1c994e9..46a24a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.4.1" +version = "0.4.2" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From 1e6c6f4f66cb851ebadae2c2fa056d02a935db0e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 16:08:54 +0800 Subject: [PATCH 105/196] Fix #144 --- .gitignore | 1 + src/win_svc.rs | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 1d9d317..e163239 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ tun2proxy.xcframework/ .env project.xcworkspace/ xcuserdata/ +.vs/ .vscode/ .VSCodeCounter/ build/ diff --git a/src/win_svc.rs b/src/win_svc.rs index 44bb7bf..5d65158 100644 --- a/src/win_svc.rs +++ b/src/win_svc.rs @@ -68,8 +68,9 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro let default = format!("{:?},trust_dns_proto=warn", args.verbosity); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); - let join_handle = tokio::spawn({ - async move { + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + rt.block_on(async { + { unsafe extern "C" fn traffic_cb(status: *const crate::TrafficStatus, _: *mut std::ffi::c_void) { let status = &*status; log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); @@ -80,13 +81,6 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro log::error!("main loop error: {}", err); } } - }); - - let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; - rt.block_on(async { - if let Err(err) = join_handle.await { - log::error!("main_entry error {}", err); - } Ok::<(), crate::Error>(()) })?; From c583e884b5ea224bcb5fed433125c36f5524758d Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 16:10:33 +0800 Subject: [PATCH 106/196] Bump version 0.4.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 46a24a4..1a224c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.4.2" +version = "0.4.3" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From e8172578666bebaf8bcfbfa794d7e693e43e097e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 21:38:15 +0800 Subject: [PATCH 107/196] refine code --- src/win_svc.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/win_svc.rs b/src/win_svc.rs index 5d65158..afca2f1 100644 --- a/src/win_svc.rs +++ b/src/win_svc.rs @@ -63,14 +63,15 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro // Tell the system that the service is running now status_handle.set_service_status(next_status.clone())?; - let args = crate::Args::parse_args(); + // main logic here + { + let args = crate::Args::parse_args(); - let default = format!("{:?},trust_dns_proto=warn", args.verbosity); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); + let default = format!("{:?},trust_dns_proto=warn", args.verbosity); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); - let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; - rt.block_on(async { - { + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + rt.block_on(async { unsafe extern "C" fn traffic_cb(status: *const crate::TrafficStatus, _: *mut std::ffi::c_void) { let status = &*status; log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); @@ -80,9 +81,9 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro if let Err(err) = crate::desktop_run_async(args, shutdown_token).await { log::error!("main loop error: {}", err); } - } - Ok::<(), crate::Error>(()) - })?; + Ok::<(), crate::Error>(()) + })?; + } // Tell the system that the service is stopped now next_status.current_state = windows_service::service::ServiceState::Stopped; From 607d709c0368ae12471b8b0ab3d10f7aaaf808cc Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 22:02:05 +0800 Subject: [PATCH 108/196] Apply daemonize for unix --- Cargo.toml | 3 +++ src/args.rs | 4 +--- src/bin/main.rs | 13 +++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1a224c1..7afb0e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,9 @@ nix = { version = "0.29", default-features = false, features = [ android_logger = "0.14" jni = { version = "0.21", default-features = false } +[target.'cfg(unix)'.dependencies] +daemonize = "0.5" + [target.'cfg(target_os = "windows")'.dependencies] windows-service = "0.7" diff --git a/src/args.rs b/src/args.rs index d7a5539..6233350 100644 --- a/src/args.rs +++ b/src/args.rs @@ -100,8 +100,7 @@ pub struct Args { #[arg(short, long, value_name = "level", value_enum, default_value = "info")] pub verbosity: ArgVerbosity, - /// Daemonize the process as Windows service - #[cfg(target_os = "windows")] + /// Daemonize for unix family or run as Windows service #[arg(long)] pub daemonize: bool, } @@ -144,7 +143,6 @@ impl Default for Args { udp_timeout: 10, verbosity: ArgVerbosity::Info, virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(), - #[cfg(target_os = "windows")] daemonize: false, } } diff --git a/src/bin/main.rs b/src/bin/main.rs index 8d4715f..a2b9b39 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -5,6 +5,19 @@ async fn main() -> Result<(), BoxError> { dotenvy::dotenv().ok(); let args = Args::parse_args(); + #[cfg(unix)] + if args.daemonize { + let stdout = std::fs::File::create("/tmp/tun2proxy.out")?; + let stderr = std::fs::File::create("/tmp/tun2proxy.err")?; + let daemonize = daemonize::Daemonize::new() + .working_directory("/tmp") + .umask(0o777) + .stdout(stdout) + .stderr(stderr) + .privileged_action(|| "Executed before drop privileges"); + let _ = daemonize.start()?; + } + #[cfg(target_os = "windows")] if args.daemonize { tun2proxy::win_svc::start_service()?; From 1ccba182736e8581f161126b4d2f94aacc1981bc Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Sep 2024 22:12:10 +0800 Subject: [PATCH 109/196] Bump version 0.4.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7afb0e8..a5bc5c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.4.3" +version = "0.4.4" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From a5bc8f49b4494b3ea044db7104768f065224fe6b Mon Sep 17 00:00:00 2001 From: Paper-Dragon <2678885646@qq.com> Date: Mon, 16 Sep 2024 17:10:56 +0800 Subject: [PATCH 110/196] multi-arch build docker images (#141) --- .github/workflows/publish-docker.yml | 12 ++++++++++++ Dockerfile | 4 ++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index ca6ae54..10db660 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -23,6 +23,15 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + + # Add support for more platforms with QEMU (optional) + # https://github.com/docker/setup-qemu-action + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + # Uses the `docker/login-action` action to log in to the Container registry registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 @@ -30,18 +39,21 @@ jobs: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - name: Build and push Docker image uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: + platforms: linux/amd64,linux/arm64 context: . push: true tags: ${{ steps.meta.outputs.tags }} diff --git a/Dockerfile b/Dockerfile index 708d60f..6680cde 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ FROM rust:latest AS builder WORKDIR /worker COPY ./ . -RUN cargo build --release --target x86_64-unknown-linux-gnu +RUN cargo build --release #################################################################################################### @@ -15,6 +15,6 @@ FROM ubuntu:latest RUN apt update && apt install -y iproute2 && apt clean all -COPY --from=builder /worker/target/x86_64-unknown-linux-gnu/release/tun2proxy /usr/bin/tun2proxy +COPY --from=builder /worker/target/release/tun2proxy /usr/bin/tun2proxy ENTRYPOINT ["/usr/bin/tun2proxy", "--setup"] From 143f203fde45b05bf7efec200d572417445427c6 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:18:14 +0800 Subject: [PATCH 111/196] Bump version 0.4.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a5bc5c8..133d915 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.4.4" +version = "0.4.5" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From febd654f350a83f91cab765416b54909a4490992 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:01:34 +0800 Subject: [PATCH 112/196] CI testing scripts --- .github/workflows/rust.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 6d4d6a7..13992eb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,7 +1,12 @@ name: Push or PR on: - [push, pull_request] + push: + branches: + - '**' + pull_request: + branches: + - '**' env: CARGO_TERM_COLOR: always From 77d651dc70d18721268a9a5443c31fee36f7a04e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:06:52 +0800 Subject: [PATCH 113/196] minor changes --- src/bin/main.rs | 3 +-- src/dns.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index a2b9b39..8986a8f 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -24,8 +24,7 @@ async fn main() -> Result<(), BoxError> { return Ok(()); } - // let default = format!("{}={:?},trust_dns_proto=warn", module_path!(), args.verbosity); - let default = format!("{:?},trust_dns_proto=warn", args.verbosity); + let default = format!("{:?},hickory_proto=warn", args.verbosity); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); let shutdown_token = tokio_util::sync::CancellationToken::new(); diff --git a/src/dns.rs b/src/dns.rs index 6142ba3..e18d218 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -1,6 +1,5 @@ -use hickory_proto::op::MessageType; use hickory_proto::{ - op::{Message, ResponseCode}, + op::{Message, MessageType, ResponseCode}, rr::{record_type::RecordType, Name, RData, Record}, }; use std::{net::IpAddr, str::FromStr}; From 6c8ae7a33fb9f89a3965e9e894f0f7f431fa03b1 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 26 Sep 2024 10:54:54 +0800 Subject: [PATCH 114/196] rename target from 'tun2proxy' to 'tun2proxy-bin' make rust compiler happy --- .github/workflows/publish-exe.yml | 6 +++--- Cargo.toml | 2 +- Dockerfile | 4 ++-- README.md | 16 ++++++++-------- scripts/iperf3.sh | 4 ++-- scripts/linux.sh | 2 +- scripts/rperf.sh | 4 ++-- src/bin/main.rs | 2 +- tests/iperf/test.sh | 4 ++-- tests/tests.py | 4 ++-- 10 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 87f5a5a..b81cae7 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -74,9 +74,9 @@ jobs: if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then powershell -Command "(Get-Item README.md).LastWriteTime = Get-Date" powershell -Command "(Get-Item target/${{ matrix.target }}/release/wintun.dll).LastWriteTime = Get-Date" - powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip + powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy-bin.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then ./build-aarch64-apple-ios.sh zip -r mypubdir4/tun2proxy-aarch64-apple-ios-xcframework.zip ./tun2proxy.xcframework/ @@ -84,7 +84,7 @@ jobs: zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/ fi elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then ./build-android.sh cp ./tun2proxy-android-libs.zip ./mypubdir4/ diff --git a/Cargo.toml b/Cargo.toml index 133d915..d26e7d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ windows-service = "0.7" serde_json = "1" [[bin]] -name = "tun2proxy" +name = "tun2proxy-bin" path = "src/bin/main.rs" [profile.release] diff --git a/Dockerfile b/Dockerfile index 6680cde..e6ad592 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,6 @@ FROM ubuntu:latest RUN apt update && apt install -y iproute2 && apt clean all -COPY --from=builder /worker/target/release/tun2proxy /usr/bin/tun2proxy +COPY --from=builder /worker/target/release/tun2proxy-bin /usr/bin/tun2proxy-bin -ENTRYPOINT ["/usr/bin/tun2proxy", "--setup"] +ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"] diff --git a/README.md b/README.md index 777f22d..a236c7f 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and ![tun2proxy](https://docs.rs/tun2proxy/badge.svg) [![Documentation](https://img.shields.io/badge/docs-release-brightgreen.svg?style=flat)](https://docs.rs/tun2proxy) [![Download](https://img.shields.io/crates/d/tun2proxy.svg)](https://crates.io/crates/tun2proxy) -[![License](https://img.shields.io/crates/l/tun2proxy.svg?style=flat)](https://github.com/blechschmidt/tun2proxy/blob/master/LICENSE) +[![License](https://img.shields.io/crates/l/tun2proxy.svg?style=flat)](https://github.com/tun2proxy/tun2proxy/blob/master/LICENSE) -> Additional information can be found in the [wiki](https://github.com/blechschmidt/tun2proxy/wiki) +> Additional information can be found in the [wiki](https://github.com/tun2proxy/tun2proxy/wiki) ## Features - HTTP proxy support (unauthenticated, basic and digest auth) @@ -15,7 +15,7 @@ A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and - SOCKS4a and SOCKS5h support (through the virtual DNS feature) - Minimal configuration setup for routing all traffic - IPv4 and IPv6 support -- GFW evasion mechanism for certain use cases (see [issue #35](https://github.com/blechschmidt/tun2proxy/issues/35)) +- GFW evasion mechanism for certain use cases (see [issue #35](https://github.com/tun2proxy/tun2proxy/issues/35)) - SOCKS5 UDP support - Native support for proxying DNS over TCP @@ -35,7 +35,7 @@ To build an XCFramework for macOS and iOS, run the following: ### Install from binary -Download the binary from [releases](https://github.com/blechschmidt/tun2proxy/releases) and put it in your `PATH`. +Download the binary from [releases](https://github.com/tun2proxy/tun2proxy/releases) and put it in your `PATH`.
Authenticity Verification @@ -66,7 +66,7 @@ describing the manual setup, except that a bind mount is used to overlay the `/e You would then run the tool as follows: ```bash -sudo ./target/release/tun2proxy --setup --proxy "socks5://1.2.3.4:1080" +sudo ./target/release/tun2proxy-bin --setup --proxy "socks5://1.2.3.4:1080" ``` Apart from SOCKS5, SOCKS4 and HTTP are supported. @@ -105,7 +105,7 @@ sudo ip route add 8000::/1 dev tun0 # Make sure that DNS queries are routed through the tunnel. sudo sh -c "echo nameserver 198.18.0.1 > /etc/resolv.conf" -./target/release/tun2proxy --tun tun0 --proxy "$PROXY_TYPE://$PROXY_IP:$PROXY_PORT" +./target/release/tun2proxy-bin --tun tun0 --proxy "$PROXY_TYPE://$PROXY_IP:$PROXY_PORT" ``` This tool implements a virtual DNS feature that is used by switch `--dns virtual`. When a DNS packet to port 53 is detected, an IP @@ -126,7 +126,7 @@ sudo ip link del tun0 ``` Tunnel interface to proxy. -Usage: tun2proxy [OPTIONS] --proxy [ADMIN_COMMAND]... +Usage: tun2proxy-bin [OPTIONS] --proxy [ADMIN_COMMAND]... Arguments: [ADMIN_COMMAND]... Specify a command to run with root-like capabilities in the new namespace when using `--unshare`. @@ -173,7 +173,7 @@ docker run -d \ --sysctl net.ipv6.conf.default.disable_ipv6=0 \ --cap-add NET_ADMIN \ --name tun2proxy \ - tun2proxy --proxy proto://[username[:password]@]host:port + tun2proxy-bin --proxy proto://[username[:password]@]host:port ``` You can then provide the running container's network to another worker container by sharing the network namespace (like kubernetes sidecar): diff --git a/scripts/iperf3.sh b/scripts/iperf3.sh index f5b388b..e6a0134 100755 --- a/scripts/iperf3.sh +++ b/scripts/iperf3.sh @@ -8,7 +8,7 @@ echo $SCRIPT_DIR netns="test" dante="danted" -tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy" +tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy-bin" ip netns add "$netns" @@ -51,4 +51,4 @@ sleep 3 iperf3 -c 10.0.0.4 -P 10 # Clean up -# sudo sh -c "pkill tun2proxy; pkill iperf3; pkill danted; ip link del tun0; ip netns del test" +# sudo sh -c "pkill tun2proxy-bin; pkill iperf3; pkill danted; ip link del tun0; ip netns del test" diff --git a/scripts/linux.sh b/scripts/linux.sh index dc1d805..4cdefaf 100755 --- a/scripts/linux.sh +++ b/scripts/linux.sh @@ -30,7 +30,7 @@ function core_function() { else trap 'echo "" && echo "tun2proxy exited with code: $?" && restore' EXIT local SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" - local APP_BIN_PATH="${SCRIPT_DIR}/../target/release/tun2proxy" + local APP_BIN_PATH="${SCRIPT_DIR}/../target/release/tun2proxy-bin" "${APP_BIN_PATH}" --tun tun0 --proxy "${PROXY_TYPE}://${PROXY_IP}:${PROXY_PORT}" -v trace fi } diff --git a/scripts/rperf.sh b/scripts/rperf.sh index 00b43a3..db34d25 100755 --- a/scripts/rperf.sh +++ b/scripts/rperf.sh @@ -29,7 +29,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" netns="test" dante="danted" -tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy" +tun2proxy="${SCRIPT_DIR}/../target/release/tun2proxy-bin" ip netns add "$netns" @@ -80,4 +80,4 @@ sleep 3 rperf -c 10.0.0.4 -v trace -P 1 -u -r # Clean up -# sudo sh -c "pkill tun2proxy; pkill rperf; pkill danted; ip link del tun0; ip netns del test" +# sudo sh -c "pkill tun2proxy-bin; pkill rperf; pkill danted; ip link del tun0; ip netns del test" diff --git a/src/bin/main.rs b/src/bin/main.rs index 8986a8f..4b93d37 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -100,7 +100,7 @@ async fn namespace_proxy_main( log::info!("The tun proxy is running in unprivileged mode. See `namespaces(7)`."); log::info!(""); log::info!("If you need to run a process that relies on root-like capabilities (e.g. `openvpn`)"); - log::info!("Use `tun2proxy --unshare --setup [...] -- openvpn --config [...]`"); + log::info!("Use `tun2proxy-bin --unshare --setup [...] -- openvpn --config [...]`"); log::info!(""); log::info!("To run a new process in the created namespace (e.g. a flatpak app)"); log::info!( diff --git a/tests/iperf/test.sh b/tests/iperf/test.sh index 6332152..0fece2c 100755 --- a/tests/iperf/test.sh +++ b/tests/iperf/test.sh @@ -8,7 +8,7 @@ echo $SCRIPT_DIR netns="test" dante="danted" -tun2proxy="${SCRIPT_DIR}/../../target/release/tun2proxy" +tun2proxy="${SCRIPT_DIR}/../../target/release/tun2proxy-bin" ip netns add "$netns" @@ -47,4 +47,4 @@ iperf3 -c 10.0.0.4 iperf3 -c 10.0.0.4 -R -P 10 # Clean up -# sudo sh -c "pkill tun2proxy; pkill iperf3; pkill danted; ip link del tun0; ip netns del test" +# sudo sh -c "pkill tun2proxy-bin; pkill iperf3; pkill danted; ip link del tun0; ip netns del test" diff --git a/tests/tests.py b/tests/tests.py index 0ec0891..a2f4597 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -29,8 +29,8 @@ def get_ip(version=None): def get_tool_path(): - default = glob.glob(os.path.join(os.path.dirname(__file__), '..', 'target', '*', 'tun2proxy')) - default = default[0] if len(default) > 0 else 'tun2proxy' + default = glob.glob(os.path.join(os.path.dirname(__file__), '..', 'target', '*', 'tun2proxy-bin')) + default = default[0] if len(default) > 0 else 'tun2proxy-bin' return os.environ.get('TOOL_PATH', default) From b24d48a04282026a7e95f848fc2a2e69f0c2102c Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 26 Sep 2024 11:02:33 +0800 Subject: [PATCH 115/196] testing python script issues --- .github/workflows/tests.yml | 26 ++++++++++++++++++++------ tests/tests.py | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9e6f1ab..c274674 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -24,10 +24,24 @@ jobs: env: DOTENV: ${{ secrets.DOTENV }} run: echo "$DOTENV" > .env + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.x' + + - name: Create virtual environment + run: python -m venv venv + + - name: Activate virtual environment and install dependencies + run: | + source venv/bin/activate + pip install -r tests/requirements.txt + + - name: Build project + run: cargo build --release + - name: Run tests - run: >- - pwd; - ls -la; - sudo python -m pip install -r tests/requirements.txt; - cargo build --release; - sudo python tests/tests.py \ No newline at end of file + run: | + source venv/bin/activate + python tests/tests.py diff --git a/tests/tests.py b/tests/tests.py index a2f4597..3ff9dff 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -40,7 +40,7 @@ class Tun2ProxyTest(unittest.TestCase): ip_noproxy = get_ip(ip_version) additional = ['-6'] if ip_version == 6 else [] p = subprocess.Popen( - [get_tool_path(), "--proxy", os.getenv(proxy_var), '--setup', '-v', 'trace', '--dns', dns, *additional]) + ['sudo', get_tool_path(), "--proxy", os.getenv(proxy_var), '--setup', '-v', 'trace', '--dns', dns, *additional]) try: time.sleep(1) ip_withproxy = get_ip(ip_version) From 2396d769d27c97938736534382c4a84132adef06 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 26 Sep 2024 12:29:56 +0800 Subject: [PATCH 116/196] Bump version 0.5.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d26e7d7..73987b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.4.5" +version = "0.5.0" edition = "2021" license = "MIT" repository = "https://github.com/blechschmidt/tun2proxy" From c9b24a865ca539e0eff0f5d47cb8661d413fa6de Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 29 Sep 2024 18:17:45 +0800 Subject: [PATCH 117/196] minor changes --- Cargo.toml | 4 ++-- src/desktop_api.rs | 2 +- src/lib.rs | 2 +- src/session_info.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 73987b3..2153556 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,8 +3,8 @@ name = "tun2proxy" version = "0.5.0" edition = "2021" license = "MIT" -repository = "https://github.com/blechschmidt/tun2proxy" -homepage = "https://github.com/blechschmidt/tun2proxy" +repository = "https://github.com/tun2proxy/tun2proxy" +homepage = "https://github.com/tun2proxy/tun2proxy" authors = ["B. Blechschmidt", "ssrlive"] description = "Tunnel interface to proxy" readme = "README.md" diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 02245ff..0617ad3 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -129,7 +129,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can } // TproxyState implements the Drop trait to restore network configuration, - // so we we need to assign it to a variable, even if it is not used. + // so we need to assign it to a variable, even if it is not used. let mut _restore: Option = None; #[cfg(target_os = "linux")] diff --git a/src/lib.rs b/src/lib.rs index bb19e43..46cdec9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -329,7 +329,7 @@ where } IpStackStream::UnknownTransport(u) => { let len = u.payload().len(); - log::info!("#0 unhandled transport - Ip Protocol {:?}, length {}", u.ip_protocol(), len); + log::info!("#0 unhandled transport - Ip Protocol 0x{:02X}, length {}", u.ip_protocol(), len); continue; } IpStackStream::UnknownNetwork(pkt) => { diff --git a/src/session_info.rs b/src/session_info.rs index dc73cf9..fc4e938 100644 --- a/src/session_info.rs +++ b/src/session_info.rs @@ -16,7 +16,7 @@ impl std::fmt::Display for IpProtocol { IpProtocol::Tcp => write!(f, "TCP"), IpProtocol::Udp => write!(f, "UDP"), IpProtocol::Icmp => write!(f, "ICMP"), - IpProtocol::Other(v) => write!(f, "Other({})", v), + IpProtocol::Other(v) => write!(f, "Other(0x{:02X})", v), } } } From b74aeab1826caead5c38b1202636f8a2b3d11bd2 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 30 Sep 2024 12:53:42 +0800 Subject: [PATCH 118/196] target armv7-unknown-linux-musleabi --- .github/workflows/publish-exe.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index b81cae7..491f2a0 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -21,7 +21,8 @@ jobs: - x86_64-unknown-linux-musl - i686-unknown-linux-musl - aarch64-unknown-linux-gnu - - armv7-unknown-linux-gnueabihf + - armv7-unknown-linux-musleabi + - armv7-unknown-linux-musleabihf - x86_64-apple-darwin - aarch64-apple-darwin - x86_64-pc-windows-msvc @@ -36,7 +37,9 @@ jobs: host_os: ubuntu-latest - target: aarch64-unknown-linux-gnu host_os: ubuntu-latest - - target: armv7-unknown-linux-gnueabihf + - target: armv7-unknown-linux-musleabi + host_os: ubuntu-latest + - target: armv7-unknown-linux-musleabihf host_os: ubuntu-latest - target: x86_64-apple-darwin host_os: macos-latest From 93e15e0a8bb1bd3851acd4c67ca1fd4b7935db10 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:50:23 +0800 Subject: [PATCH 119/196] build x86_64-win7-windows-msvc target --- .github/workflows/publish-exe.yml | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 491f2a0..778203d 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -27,6 +27,9 @@ jobs: - aarch64-apple-darwin - x86_64-pc-windows-msvc - i686-pc-windows-msvc + - aarch64-pc-windows-msvc + - x86_64-win7-windows-msvc + - i686-win7-windows-msvc include: - target: x86_64-unknown-linux-gnu @@ -49,6 +52,12 @@ jobs: host_os: windows-latest - target: i686-pc-windows-msvc host_os: windows-latest + - target: aarch64-pc-windows-msvc + host_os: windows-latest + - target: x86_64-win7-windows-msvc + host_os: windows-latest + - target: i686-win7-windows-msvc + host_os: windows-latest runs-on: ${{ matrix.host_os }} steps: @@ -59,7 +68,9 @@ jobs: shell: bash run: | mkdir mypubdir4 - rustup target add ${{ matrix.target }} + if [[ "${{ matrix.target }}" != "x86_64-win7-windows-msvc" && "${{ matrix.target }}" != "i686-win7-windows-msvc" ]]; then + rustup target add ${{ matrix.target }} + fi cargo install cbindgen if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then sudo .github/workflows/install-cross.sh @@ -71,7 +82,13 @@ jobs: if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then cross build --all-features --release --target ${{ matrix.target }} else - cargo build --all-features --release --target ${{ matrix.target }} + if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then + rustup toolchain install nightly + rustup component add rust-src --toolchain nightly + cargo +nightly build --release -Z build-std --target ${{ matrix.target }} + else + cargo build --all-features --release --target ${{ matrix.target }} + fi fi cbindgen --config cbindgen.toml -l C --cpp-compat -o target/tun2proxy-ffi.h if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then From fe32a65291371d771ecea3500ee4783164d46f90 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 3 Oct 2024 08:50:51 +0800 Subject: [PATCH 120/196] Bump version 0.5.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 2153556..0f9d112 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.5.0" +version = "0.5.1" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From c991006f4c932c96625f1c7be2e834364bd43f0c Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:23:11 +0800 Subject: [PATCH 121/196] --exit-on-fatal-error option --- src/args.rs | 5 +++++ src/lib.rs | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/src/args.rs b/src/args.rs index 6233350..b991512 100644 --- a/src/args.rs +++ b/src/args.rs @@ -103,6 +103,10 @@ pub struct Args { /// Daemonize for unix family or run as Windows service #[arg(long)] pub daemonize: bool, + + /// Exit immediately when fatal error occurs, useful for running as a service + #[arg(long)] + pub exit_on_fatal_error: bool, } fn validate_tun(p: &str) -> Result { @@ -144,6 +148,7 @@ impl Default for Args { verbosity: ArgVerbosity::Info, virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(), daemonize: false, + exit_on_fatal_error: false, } } } diff --git a/src/lib.rs b/src/lib.rs index 46cdec9..4e500c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,6 +248,9 @@ where IpStackStream::Tcp(tcp) => { if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); + if args.exit_on_fatal_error { + break; + } continue; } log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); @@ -271,6 +274,9 @@ where IpStackStream::Udp(udp) => { if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); + if args.exit_on_fatal_error { + break; + } continue; } log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); From b03032b8cd4e4e562cf5c856950dfb564427841b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 7 Oct 2024 13:34:48 +0800 Subject: [PATCH 122/196] Bump version 0.5.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0f9d112..883f3a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.5.1" +version = "0.5.2" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 4ef71a5b4cfee5b7f092b6a561e803be36bc603e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:57:14 +0800 Subject: [PATCH 123/196] --max-sessions option --- src/args.rs | 5 +++++ src/lib.rs | 13 +++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/args.rs b/src/args.rs index b991512..352cb57 100644 --- a/src/args.rs +++ b/src/args.rs @@ -107,6 +107,10 @@ pub struct Args { /// Exit immediately when fatal error occurs, useful for running as a service #[arg(long)] pub exit_on_fatal_error: bool, + + /// Maximum number of sessions to be handled concurrently + #[arg(long, value_name = "number", default_value = "200")] + pub max_sessions: usize, } fn validate_tun(p: &str) -> Result { @@ -149,6 +153,7 @@ impl Default for Args { virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(), daemonize: false, exit_on_fatal_error: false, + max_sessions: 200, } } } diff --git a/src/lib.rs b/src/lib.rs index 4e500c4..554fe14 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,8 +65,6 @@ pub mod win_svc; const DNS_PORT: u16 = 53; -const MAX_SESSIONS: u64 = 200; - static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); use std::sync::atomic::Ordering::Relaxed; @@ -244,13 +242,15 @@ where ip_stack_stream? } }; + let max_sessions = args.max_sessions as u64; match ip_stack_stream { IpStackStream::Tcp(tcp) => { - if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { - log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); + if TASK_COUNT.load(Relaxed) > max_sessions { if args.exit_on_fatal_error { + log::info!("Too many sessions that over {max_sessions}, exiting..."); break; } + log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); @@ -272,11 +272,12 @@ where }); } IpStackStream::Udp(udp) => { - if TASK_COUNT.load(Relaxed) > MAX_SESSIONS { - log::warn!("Too many sessions that over {MAX_SESSIONS}, dropping new session"); + if TASK_COUNT.load(Relaxed) > max_sessions { if args.exit_on_fatal_error { + log::info!("Too many sessions that over {max_sessions}, exiting..."); break; } + log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); From d09397316098f6b750129867c38092cd8bb05455 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:58:37 +0800 Subject: [PATCH 124/196] refine ctrl-c logic --- src/bin/main.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 4b93d37..91e88ec 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -28,7 +28,7 @@ async fn main() -> Result<(), BoxError> { env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); let shutdown_token = tokio_util::sync::CancellationToken::new(); - let join_handle = tokio::spawn({ + let main_loop_handle = tokio::spawn({ let shutdown_token = shutdown_token.clone(); async move { #[cfg(target_os = "linux")] @@ -51,14 +51,20 @@ async fn main() -> Result<(), BoxError> { } }); - ctrlc2::set_async_handler(async move { + let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let ctrlc_fired_clone = ctrlc_fired.clone(); + let ctrlc_handel = ctrlc2::set_async_handler(async move { log::info!("Ctrl-C received, exiting..."); + ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst); shutdown_token.cancel(); }) .await; - if let Err(err) = join_handle.await { - log::error!("main_entry error {}", err); + main_loop_handle.await?; + + if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) { + log::info!("Ctrl-C fired, waiting the handler to finish..."); + ctrlc_handel.await.map_err(|err| err.to_string())?; } Ok(()) From 918e6137abc205c9228e9646d6c11937e07c7131 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:00:40 +0800 Subject: [PATCH 125/196] Bump version 0.5.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 883f3a2..77a6c41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.5.2" +version = "0.5.3" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 9aa2afb0fd0c918b3da04dc6c904b0083f4b25ca Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:54:25 +0800 Subject: [PATCH 126/196] Fix daemonize issues --- src/bin/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 91e88ec..e39b7b4 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,7 +1,6 @@ use tun2proxy::{Args, BoxError}; -#[tokio::main] -async fn main() -> Result<(), BoxError> { +fn main() -> Result<(), BoxError> { dotenvy::dotenv().ok(); let args = Args::parse_args(); @@ -24,6 +23,11 @@ async fn main() -> Result<(), BoxError> { return Ok(()); } + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + rt.block_on(main_async(args)) +} + +async fn main_async(args: Args) -> Result<(), BoxError> { let default = format!("{:?},hickory_proto=warn", args.verbosity); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); From f823202b334e5833c691938362098ac4ad8bd951 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 10 Oct 2024 00:35:30 +0800 Subject: [PATCH 127/196] Bump version 0.5.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 77a6c41..d237122 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.5.3" +version = "0.5.4" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From b6bb9bedfc7696411d0313e4491005ff4ce1a1d0 Mon Sep 17 00:00:00 2001 From: sujiacong Date: Sat, 26 Oct 2024 02:15:52 +0800 Subject: [PATCH 128/196] support udp gateway mode (#155) --- Cargo.toml | 21 +- src/args.rs | 26 ++ src/bin/udpgw_server.rs | 210 ++++++++++++++++ src/error.rs | 2 +- src/lib.rs | 162 ++++++++++++ src/udpgw.rs | 542 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 956 insertions(+), 7 deletions(-) create mode 100644 src/bin/udpgw_server.rs create mode 100644 src/udpgw.rs diff --git a/Cargo.toml b/Cargo.toml index d237122..3ad63ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.5.4" +version = "0.6.0" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" @@ -13,6 +13,10 @@ rust-version = "1.80" [lib] crate-type = ["staticlib", "cdylib", "lib"] +[features] +default = ["udpgw"] +udpgw = [] + [dependencies] async-trait = "0.1" base64 = { version = "0.22" } @@ -42,11 +46,6 @@ url = "2" [target.'cfg(target_os="linux")'.dependencies] serde = { version = "1", features = ["derive"] } bincode = "1" -nix = { version = "0.29", default-features = false, features = [ - "fs", - "socket", - "uio", -] } [target.'cfg(target_os="android")'.dependencies] android_logger = "0.14" @@ -54,6 +53,11 @@ jni = { version = "0.21", default-features = false } [target.'cfg(unix)'.dependencies] daemonize = "0.5" +nix = { version = "0.29", default-features = false, features = [ + "fs", + "socket", + "uio", +] } [target.'cfg(target_os = "windows")'.dependencies] windows-service = "0.7" @@ -65,5 +69,10 @@ serde_json = "1" name = "tun2proxy-bin" path = "src/bin/main.rs" +[[bin]] +name = "udpgw-server" +path = "src/bin/udpgw_server.rs" +required-features = ["udpgw"] + [profile.release] strip = "symbols" diff --git a/src/args.rs b/src/args.rs index 352cb57..65d0998 100644 --- a/src/args.rs +++ b/src/args.rs @@ -111,6 +111,16 @@ pub struct Args { /// Maximum number of sessions to be handled concurrently #[arg(long, value_name = "number", default_value = "200")] pub max_sessions: usize, + + /// UDP gateway server address, similar to badvpn-udpgw + #[cfg(feature = "udpgw")] + #[arg(long, value_name = "IP:PORT")] + pub udpgw_server: Option, + + /// Max udpgw connections, default value is 100 + #[cfg(feature = "udpgw")] + #[arg(long, value_name = "number", requires = "udpgw_server")] + pub udpgw_max_connections: Option, } fn validate_tun(p: &str) -> Result { @@ -154,6 +164,10 @@ impl Default for Args { daemonize: false, exit_on_fatal_error: false, max_sessions: 200, + #[cfg(feature = "udpgw")] + udpgw_server: None, + #[cfg(feature = "udpgw")] + udpgw_max_connections: None, } } } @@ -181,6 +195,18 @@ impl Args { self } + #[cfg(feature = "udpgw")] + pub fn udpgw_server(&mut self, udpgw: SocketAddr) -> &mut Self { + self.udpgw_server = Some(udpgw); + self + } + + #[cfg(feature = "udpgw")] + pub fn udpgw_max_connections(&mut self, udpgw_max_connections: u16) -> &mut Self { + self.udpgw_max_connections = Some(udpgw_max_connections); + self + } + #[cfg(unix)] pub fn tun_fd(&mut self, tun_fd: Option) -> &mut Self { self.tun_fd = tun_fd; diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs new file mode 100644 index 0000000..6f6d080 --- /dev/null +++ b/src/bin/udpgw_server.rs @@ -0,0 +1,210 @@ +use socks5_impl::protocol::{AddressType, AsyncStreamOperation}; +use std::{net::SocketAddr, sync::Arc}; +use tokio::{ + io::AsyncWriteExt, + net::{ + tcp::{ReadHalf, WriteHalf}, + UdpSocket, + }, + sync::mpsc::{self, Receiver, Sender}, +}; +use tun2proxy::{ + udpgw::{Packet, UDPGW_FLAG_KEEPALIVE}, + ArgVerbosity, Result, +}; + +pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60); + +#[derive(Debug, Clone)] +pub struct Client { + addr: SocketAddr, + last_activity: std::time::Instant, +} + +impl Client { + pub fn new(addr: SocketAddr) -> Self { + let last_activity = std::time::Instant::now(); + Self { addr, last_activity } + } +} + +#[derive(Debug, Clone, clap::Parser)] +pub struct UdpGwArgs { + /// UDP gateway listen address + #[arg(short, long, value_name = "IP:PORT", default_value = "127.0.0.1:7300")] + pub listen_addr: SocketAddr, + + /// UDP mtu + #[arg(short = 'm', long, value_name = "udp mtu", default_value = "10240")] + pub udp_mtu: u16, + + /// UDP timeout in seconds + #[arg(short = 't', long, value_name = "seconds", default_value = "3")] + pub udp_timeout: u64, + + /// Daemonize for unix family or run as Windows service + #[cfg(unix)] + #[arg(long)] + pub daemonize: bool, + + /// Verbosity level + #[arg(short, long, value_name = "level", value_enum, default_value = "info")] + pub verbosity: ArgVerbosity, +} + +impl UdpGwArgs { + #[allow(clippy::let_and_return)] + pub fn parse_args() -> Self { + use clap::Parser; + Self::parse() + } +} + +async fn send_error(tx: Sender, conn_id: u16) { + let error_packet = Packet::build_error_packet(conn_id); + if let Err(e) = tx.send(error_packet).await { + log::error!("send error response error {:?}", e); + } +} + +async fn send_keepalive_response(tx: Sender, conn_id: u16) { + let keepalive_packet = Packet::build_keepalive_packet(conn_id); + if let Err(e) = tx.send(keepalive_packet).await { + log::error!("send keepalive response error {:?}", e); + } +} + +/// Send data field of packet from client to destination server and receive response, +/// then wrap response data to the packet's data field and send packet back to client. +async fn process_udp(client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Sender, mut packet: Packet) -> Result<()> { + let Some(dst_addr) = &packet.address else { + log::error!("client {} udp request address is None", client); + return Ok(()); + }; + let std_sock = if dst_addr.get_type() == AddressType::IPv6 { + std::net::UdpSocket::bind("[::]:0")? + } else { + std::net::UdpSocket::bind("0.0.0.0:0")? + }; + std_sock.set_nonblocking(true)?; + #[cfg(unix)] + nix::sys::socket::setsockopt(&std_sock, nix::sys::socket::sockopt::ReuseAddr, &true)?; + let socket = UdpSocket::from_std(std_sock)?; + use std::net::ToSocketAddrs; + let Some(dst_addr) = dst_addr.to_socket_addrs()?.next() else { + log::error!("client {} udp request address to_socket_addrs", client); + return Ok(()); + }; + // 1. send udp data to destination server + socket.send_to(&packet.data, &dst_addr).await?; + packet.data.resize(udp_mtu as usize, 0); + // 2. receive response from destination server + let (len, _addr) = tokio::time::timeout(tokio::time::Duration::from_secs(udp_timeout), socket.recv_from(&mut packet.data)) + .await + .map_err(std::io::Error::from)??; + packet.data.truncate(len); + // 3. send response back to client + use std::io::{Error, ErrorKind::BrokenPipe}; + tx.send(packet).await.map_err(|e| Error::new(BrokenPipe, e))?; + Ok(()) +} + +async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client: Client, mut reader: ReadHalf<'_>) -> std::io::Result<()> { + let udp_timeout = args.udp_timeout; + let udp_mtu = args.udp_mtu; + + loop { + // 1. read udpgw packet from client + let res = tokio::time::timeout(tokio::time::Duration::from_secs(2), Packet::retrieve_from_async_stream(&mut reader)).await; + let packet = match res { + Ok(Ok(packet)) => packet, + Ok(Err(e)) => { + log::error!("client {} retrieve_from_async_stream \"{}\"", client.addr, e); + break; + } + Err(e) => { + if client.last_activity.elapsed() >= CLIENT_DISCONNECT_TIMEOUT { + log::debug!("client {} last_activity elapsed \"{e}\"", client.addr); + break; + } + continue; + } + }; + client.last_activity = std::time::Instant::now(); + + let flags = packet.header.flags; + let conn_id = packet.header.conn_id; + if flags & UDPGW_FLAG_KEEPALIVE != 0 { + log::trace!("client {} send keepalive", client.addr); + // 2. if keepalive packet, do nothing, send keepalive response to client + send_keepalive_response(tx.clone(), conn_id).await; + continue; + } + log::trace!("client {} received udp data {}", client.addr, packet); + + // 3. process client udpgw packet in a new task + let tx = tx.clone(); + tokio::spawn(async move { + if let Err(e) = process_udp(client.addr, udp_mtu, udp_timeout, tx.clone(), packet).await { + send_error(tx, conn_id).await; + log::error!("client {} process udp function {}", client.addr, e); + } + }); + } + Ok(()) +} + +async fn write_to_client(addr: SocketAddr, mut writer: WriteHalf<'_>, mut rx: Receiver) -> std::io::Result<()> { + loop { + use std::io::{Error, ErrorKind::BrokenPipe}; + let packet = rx.recv().await.ok_or(Error::new(BrokenPipe, "recv error"))?; + log::trace!("send response to client {} with {}", addr, packet); + let data: Vec = packet.into(); + let _r = writer.write(&data).await?; + } +} + +#[tokio::main] +async fn main() -> Result<()> { + let args = Arc::new(UdpGwArgs::parse_args()); + + let tcp_listener = tokio::net::TcpListener::bind(args.listen_addr).await?; + + let default = format!("{:?}", args.verbosity); + + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); + + log::info!("{} {} starting...", module_path!(), env!("CARGO_PKG_VERSION")); + log::info!("UDP Gateway Server running at {}", args.listen_addr); + + #[cfg(unix)] + if args.daemonize { + let stdout = std::fs::File::create("/tmp/udpgw.out")?; + let stderr = std::fs::File::create("/tmp/udpgw.err")?; + let daemonize = daemonize::Daemonize::new() + .working_directory("/tmp") + .umask(0o777) + .stdout(stdout) + .stderr(stderr) + .privileged_action(|| "Executed before drop privileges"); + let _ = daemonize + .start() + .map_err(|e| format!("Failed to daemonize process, error:{:?}", e))?; + } + + loop { + let (mut tcp_stream, addr) = tcp_listener.accept().await?; + let client = Client::new(addr); + log::info!("client {} connected", addr); + let params = args.clone(); + tokio::spawn(async move { + let (tx, rx) = mpsc::channel::(100); + let (tcp_read_stream, tcp_write_stream) = tcp_stream.split(); + let res = tokio::select! { + v = process_client_udp_req(¶ms, tx, client, tcp_read_stream) => v, + v = write_to_client(addr, tcp_write_stream, rx) => v, + }; + log::info!("client {} disconnected with {:?}", addr, res); + }); + } +} diff --git a/src/error.rs b/src/error.rs index f460b62..755ee0c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,7 +6,7 @@ pub enum Error { #[error(transparent)] Io(#[from] std::io::Error), - #[cfg(target_os = "linux")] + #[cfg(unix)] #[error("nix::errno::Errno {0:?}")] NixErrno(#[from] nix::errno::Errno), diff --git a/src/lib.rs b/src/lib.rs index 554fe14..242ba15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "udpgw")] +use crate::udpgw::UdpGwClient; use crate::{ directions::{IncomingDataEvent, IncomingDirection, OutgoingDirection}, http::HttpManager, @@ -9,6 +11,8 @@ use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; use proxy_handler::{ProxyHandler, ProxyHandlerManager}; use socks::SocksProxyManager; pub use socks5_impl::protocol::UserKey; +#[cfg(feature = "udpgw")] +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6}; use std::{ collections::VecDeque, io::ErrorKind, @@ -23,6 +27,8 @@ use tokio::{ pub use tokio_util::sync::CancellationToken; use tproxy_config::is_private_ip; use udp_stream::UdpStream; +#[cfg(feature = "udpgw")] +use udpgw::{UdpGwClientStream, UdpGwResponse, UDPGW_KEEPALIVE_TIME, UDPGW_MAX_CONNECTIONS}; pub use { args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType}, @@ -59,6 +65,8 @@ mod session_info; pub mod socket_transfer; mod socks; mod traffic_status; +#[cfg(feature = "udpgw")] +pub mod udpgw; mod virtual_dns; #[doc(hidden)] pub mod win_svc; @@ -231,6 +239,23 @@ where let mut ip_stack = ipstack::IpStack::new(ipstack_config, device); + #[cfg(feature = "udpgw")] + let udpgw_client = args.udpgw_server.as_ref().map(|addr| { + log::info!("UDPGW enabled"); + let client = Arc::new(UdpGwClient::new( + mtu, + args.udpgw_max_connections.unwrap_or(UDPGW_MAX_CONNECTIONS), + UDPGW_KEEPALIVE_TIME, + args.udp_timeout, + *addr, + )); + let client_keepalive = client.clone(); + tokio::spawn(async move { + client_keepalive.heartbeat_task().await; + }); + client + }); + loop { let virtual_dns = virtual_dns.clone(); let ip_stack_stream = tokio::select! { @@ -318,6 +343,24 @@ where } else { None }; + #[cfg(feature = "udpgw")] + if let Some(udpgw) = udpgw_client.clone() { + let tcp_src = match udp.peer_addr() { + SocketAddr::V4(_) => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), + SocketAddr::V6(_) => SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)), + }; + let tcpinfo = SessionInfo::new(tcp_src, udpgw.get_server_addr(), IpProtocol::Tcp); + let proxy_handler = mgr.new_proxy_handler(tcpinfo, None, false).await?; + let queue = socket_queue.clone(); + tokio::spawn(async move { + let dst = info.dst; // real UDP destination address + if let Err(e) = handle_udp_gateway_session(udp, udpgw, dst, domain_name, proxy_handler, queue, ipv6_enabled).await { + log::info!("Ending {} with \"{}\"", info, e); + } + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + }); + continue; + } match mgr.new_proxy_handler(info, domain_name, true).await { Ok(proxy_handler) => { let socket_queue = socket_queue.clone(); @@ -436,6 +479,125 @@ async fn handle_tcp_session( Ok(()) } +#[cfg(feature = "udpgw")] +async fn handle_udp_gateway_session( + mut udp_stack: IpStackUdpStream, + udpgw_client: Arc, + udp_dst: SocketAddr, + domain_name: Option, + proxy_handler: Arc>, + socket_queue: Option>, + ipv6_enabled: bool, +) -> crate::Result<()> { + let proxy_server_addr = { proxy_handler.lock().await.get_server_addr() }; + let udp_mtu = udpgw_client.get_udp_mtu(); + let udp_timeout = udpgw_client.get_udp_timeout(); + let mut stream = match udpgw_client.get_server_connection().await { + Some(server) => server, + None => { + if udpgw_client.is_full() { + return Err("max udpgw connection limit reached".into()); + } + let mut tcp_server_stream = create_tcp_stream(&socket_queue, proxy_server_addr).await?; + if let Err(e) = handle_proxy_session(&mut tcp_server_stream, proxy_handler).await { + return Err(format!("udpgw connection error: {}", e).into()); + } + UdpGwClientStream::new(tcp_server_stream) + } + }; + + let tcp_local_addr = stream.local_addr().clone(); + + match domain_name { + Some(ref d) => log::info!("[UdpGw] Beginning {} -> {}, domain:{}", &tcp_local_addr, udp_dst, d), + None => log::info!("[UdpGw] Beginning {} -> {}", &tcp_local_addr, udp_dst), + } + + let Some(mut reader) = stream.get_reader() else { + return Err("get reader failed".into()); + }; + + let Some(mut writer) = stream.get_writer() else { + return Err("get writer failed".into()); + }; + + let mut tmp_buf = vec![0; udp_mtu.into()]; + + loop { + tokio::select! { + len = udp_stack.read(&mut tmp_buf) => { + let read_len = match len { + Ok(0) => { + log::info!("[UdpGw] Ending {} <> {}", &tcp_local_addr, udp_dst); + break; + } + Ok(n) => n, + Err(e) => { + log::info!("[UdpGw] Ending {} <> {} with recv_udp_packet {}", &tcp_local_addr, udp_dst, e); + break; + } + }; + crate::traffic_status::traffic_status_update(read_len, 0)?; + let new_id = stream.new_id(); + let remote_addr = match domain_name { + Some(ref d) => socks5_impl::protocol::Address::from((d.clone(), udp_dst.port())), + None => udp_dst.into(), + }; + if let Err(e) = UdpGwClient::send_udpgw_packet(ipv6_enabled, &tmp_buf[0..read_len], &remote_addr, new_id, &mut writer).await { + log::info!("[UdpGw] Ending {} <> {} with send_udpgw_packet {}", &tcp_local_addr, udp_dst, e); + break; + } + log::debug!("[UdpGw] {} -> {} send len {}", &tcp_local_addr, udp_dst, read_len); + stream.update_activity(); + } + ret = UdpGwClient::recv_udpgw_packet(udp_mtu, udp_timeout, &mut reader) => { + match ret { + Ok(packet) => match packet { + //should not received keepalive + UdpGwResponse::KeepAlive => { + log::error!("[UdpGw] Ending {} <> {} with recv keepalive", &tcp_local_addr, udp_dst); + stream.close(); + break; + } + //server udp may be timeout,can continue to receive udp data? + UdpGwResponse::Error => { + log::info!("[UdpGw] Ending {} <> {} with recv udp error", &tcp_local_addr, udp_dst); + stream.update_activity(); + continue; + } + UdpGwResponse::TcpClose => { + log::error!("[UdpGw] Ending {} <> {} with tcp closed", &tcp_local_addr, udp_dst); + stream.close(); + break; + } + UdpGwResponse::Data(data) => { + use socks5_impl::protocol::StreamOperation; + let len = data.len(); + log::debug!("[UdpGw] {} <- {} receive len {}", &tcp_local_addr, udp_dst, len); + if let Err(e) = udp_stack.write_all(&data.data).await { + log::error!("[UdpGw] Ending {} <> {} with send_udp_packet {}", &tcp_local_addr, udp_dst, e); + break; + } + crate::traffic_status::traffic_status_update(0, len)?; + } + }, + Err(e) => { + log::warn!("[UdpGw] Ending {} <> {} with recv_udpgw_packet {}", &tcp_local_addr, udp_dst, e); + break; + } + } + stream.update_activity(); + } + } + } + + if !stream.is_closed() { + udpgw_client.release_server_connection_full(stream, reader, writer).await; + } + + Ok(()) +} + async fn handle_udp_associate_session( mut udp_stack: IpStackUdpStream, proxy_type: ProxyType, diff --git a/src/udpgw.rs b/src/udpgw.rs new file mode 100644 index 0000000..4d7f018 --- /dev/null +++ b/src/udpgw.rs @@ -0,0 +1,542 @@ +use crate::error::Result; +use socks5_impl::protocol::{Address, AsyncStreamOperation, BufMut, StreamOperation}; +use std::{collections::VecDeque, hash::Hash, net::SocketAddr, sync::atomic::Ordering::Relaxed}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::{ + tcp::{OwnedReadHalf, OwnedWriteHalf}, + TcpStream, + }, + sync::Mutex, + time::{sleep, Duration}, +}; + +pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::(); +pub(crate) const UDPGW_MAX_CONNECTIONS: u16 = 100; +pub(crate) const UDPGW_KEEPALIVE_TIME: tokio::time::Duration = std::time::Duration::from_secs(10); + +pub const UDPGW_FLAG_KEEPALIVE: u8 = 0x01; +pub const UDPGW_FLAG_ERR: u8 = 0x20; +pub const UDPGW_FLAG_DATA: u8 = 0x02; + +static TCP_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + +/// UDP Gateway Packet Format +/// +/// The format is referenced from SOCKS5 packet format, with additional flags and connection ID fields. +/// +/// `LEN`: This field is indicated the length of the packet, not including the length field itself. +/// +/// `FLAGS`: This field is used to indicate the packet type. The flags are defined as follows: +/// - `0x01`: Keepalive packet without address and data +/// - `0x20`: Error packet without address and data +/// - `0x02`: Data packet with address and data +/// +/// `CONN_ID`: This field is used to indicate the unique connection ID for the packet. +/// +/// `ATYP` & `DST.ADDR` & `DST.PORT`: This fields are used to indicate the remote address and port. +/// It can be either an IPv4 address, an IPv6 address, or a domain name, depending on the `ATYP` field. +/// The address format directly uses the address format of the [SOCKS5](https://datatracker.ietf.org/doc/html/rfc1928#section-4) protocol. +/// - `ATYP`: Address Type, 1 byte, indicating the type of address ( 0x01-IPv4, 0x04-IPv6, or 0x03-domain name ) +/// - `DST.ADDR`: Destination Address. If `ATYP` is 0x01 or 0x04, it is 4 or 16 bytes of IP address; +/// If `ATYP` is 0x03, it is a domain name, `DST.ADDR` is a variable length field, +/// it begins with a 1-byte length field and then the domain name without null-termination, +/// since the length field is 1 byte, the maximum length of the domain name is 255 bytes. +/// - `DST.PORT`: Destination Port, 2 bytes, the port number of the destination address. +/// +/// `DATA`: The data field, a variable length field, the length is determined by the `LEN` field. +/// +/// All the digits fields are in big-endian byte order. +/// +/// ```plain +/// +-----+ +-------+---------+ +------+----------+----------+ +----------+ +/// | LEN | | FLAGS | CONN_ID | | ATYP | DST.ADDR | DST.PORT | | DATA | +/// +-----+ +-------+---------+ +------+----------+----------+ +----------+ +/// | 2 | | 1 | 2 | | 1 | Variable | 2 | | Variable | +/// +-----+ +-------+---------+ +------+----------+----------+ +----------+ +/// ``` +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Packet { + pub header: UdpgwHeader, + pub address: Option
, + pub data: Vec, +} + +impl std::fmt::Display for Packet { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let addr = self.address.as_ref().map_or("None".to_string(), |addr| addr.to_string()); + let len = self.data.len(); + write!(f, "Packet {{ {}, address: {}, payload length: {} }}", self.header, addr, len) + } +} + +impl From for Vec { + fn from(packet: Packet) -> Vec { + (&packet).into() + } +} + +impl From<&Packet> for Vec { + fn from(packet: &Packet) -> Vec { + let mut bytes: Vec = vec![]; + packet.write_to_buf(&mut bytes); + bytes + } +} + +impl TryFrom<&[u8]> for Packet { + type Error = std::io::Error; + + fn try_from(value: &[u8]) -> std::result::Result { + if value.len() < UDPGW_LENGTH_FIELD_SIZE { + return Err(std::io::ErrorKind::InvalidData.into()); + } + let mut iter = std::io::Cursor::new(value); + use tokio_util::bytes::Buf; + let length = iter.get_u16(); + if value.len() < length as usize + UDPGW_LENGTH_FIELD_SIZE { + return Err(std::io::ErrorKind::InvalidData.into()); + } + let header = UdpgwHeader::retrieve_from_stream(&mut iter)?; + let address = if header.flags & UDPGW_FLAG_DATA != 0 { + Some(Address::retrieve_from_stream(&mut iter)?) + } else { + None + }; + Ok(Packet::new(header, address, iter.chunk())) + } +} + +impl Packet { + pub fn new(header: UdpgwHeader, address: Option
, data: &[u8]) -> Self { + let data = data.to_vec(); + Packet { header, address, data } + } + + pub fn build_keepalive_packet(conn_id: u16) -> Self { + Packet::new(UdpgwHeader::new(UDPGW_FLAG_KEEPALIVE, conn_id), None, &[]) + } + + pub fn build_error_packet(conn_id: u16) -> Self { + Packet::new(UdpgwHeader::new(UDPGW_FLAG_ERR, conn_id), None, &[]) + } + + pub fn build_packet_from_address(conn_id: u16, remote_addr: &Address, data: &[u8]) -> std::io::Result { + use socks5_impl::protocol::Address::{DomainAddress, SocketAddress}; + let packet = match remote_addr { + SocketAddress(addr) => Packet::build_ip_packet(conn_id, *addr, data), + DomainAddress(domain, port) => Packet::build_domain_packet(conn_id, *port, domain, data)?, + }; + Ok(packet) + } + + pub fn build_ip_packet(conn_id: u16, remote_addr: SocketAddr, data: &[u8]) -> Self { + let addr: Address = remote_addr.into(); + Packet::new(UdpgwHeader::new(UDPGW_FLAG_DATA, conn_id), Some(addr), data) + } + + pub fn build_domain_packet(conn_id: u16, port: u16, domain: &str, data: &[u8]) -> std::io::Result { + if domain.len() > 255 { + return Err(std::io::ErrorKind::InvalidInput.into()); + } + let addr = Address::from((domain, port)); + Ok(Packet::new(UdpgwHeader::new(UDPGW_FLAG_DATA, conn_id), Some(addr), data)) + } +} + +impl StreamOperation for Packet { + fn retrieve_from_stream(stream: &mut R) -> std::io::Result + where + R: std::io::Read, + Self: Sized, + { + let mut buf = [0; UDPGW_LENGTH_FIELD_SIZE]; + stream.read_exact(&mut buf)?; + let length = u16::from_be_bytes(buf) as usize; + let header = UdpgwHeader::retrieve_from_stream(stream)?; + let address = if header.flags & UDPGW_FLAG_DATA != 0 { + Some(Address::retrieve_from_stream(stream)?) + } else { + None + }; + let read_len = header.len() + address.as_ref().map_or(0, |addr| addr.len()); + if length < read_len { + return Err(std::io::ErrorKind::InvalidData.into()); + } + let mut data = vec![0; length - read_len]; + stream.read_exact(&mut data)?; + Ok(Packet::new(header, address, &data)) + } + + fn write_to_buf(&self, buf: &mut B) { + let len = self.len() - UDPGW_LENGTH_FIELD_SIZE; + buf.put_u16(len as u16); + self.header.write_to_buf(buf); + if let Some(addr) = &self.address { + addr.write_to_buf(buf); + } + buf.put_slice(&self.data); + } + + fn len(&self) -> usize { + UDPGW_LENGTH_FIELD_SIZE + self.header.len() + self.address.as_ref().map_or(0, |addr| addr.len()) + self.data.len() + } +} + +#[async_trait::async_trait] +impl AsyncStreamOperation for Packet { + async fn retrieve_from_async_stream(r: &mut R) -> std::io::Result + where + R: tokio::io::AsyncRead + Unpin + Send, + Self: Sized, + { + let mut buf = [0; UDPGW_LENGTH_FIELD_SIZE]; + r.read_exact(&mut buf).await?; + let length = u16::from_be_bytes(buf) as usize; + let header = UdpgwHeader::retrieve_from_async_stream(r).await?; + let address = if header.flags & UDPGW_FLAG_DATA != 0 { + Some(Address::retrieve_from_async_stream(r).await?) + } else { + None + }; + let read_len = header.len() + address.as_ref().map_or(0, |addr| addr.len()); + if length < read_len { + return Err(std::io::ErrorKind::InvalidData.into()); + } + let mut data = vec![0; length - read_len]; + r.read_exact(&mut data).await?; + Ok(Packet::new(header, address, &data)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct UdpgwHeader { + pub flags: u8, + pub conn_id: u16, +} + +impl std::fmt::Display for UdpgwHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let id = self.conn_id; + write!(f, "flags: 0x{:02x}, conn_id: {}", self.flags, id) + } +} + +impl StreamOperation for UdpgwHeader { + fn retrieve_from_stream(stream: &mut R) -> std::io::Result + where + R: std::io::Read, + Self: Sized, + { + let mut buf = [0; UdpgwHeader::static_len()]; + stream.read_exact(&mut buf)?; + UdpgwHeader::try_from(&buf[..]) + } + + fn write_to_buf(&self, buf: &mut B) { + let bytes: Vec = self.into(); + buf.put_slice(&bytes); + } + + fn len(&self) -> usize { + Self::static_len() + } +} + +#[async_trait::async_trait] +impl AsyncStreamOperation for UdpgwHeader { + async fn retrieve_from_async_stream(r: &mut R) -> std::io::Result + where + R: tokio::io::AsyncRead + Unpin + Send, + Self: Sized, + { + let mut buf = [0; UdpgwHeader::static_len()]; + r.read_exact(&mut buf).await?; + UdpgwHeader::try_from(&buf[..]) + } +} + +impl UdpgwHeader { + pub fn new(flags: u8, conn_id: u16) -> Self { + UdpgwHeader { flags, conn_id } + } + + pub const fn static_len() -> usize { + std::mem::size_of::() + std::mem::size_of::() + } +} + +impl TryFrom<&[u8]> for UdpgwHeader { + type Error = std::io::Error; + + fn try_from(value: &[u8]) -> std::result::Result { + if value.len() < UdpgwHeader::static_len() { + return Err(std::io::ErrorKind::InvalidData.into()); + } + let conn_id = u16::from_be_bytes([value[1], value[2]]); + Ok(UdpgwHeader { flags: value[0], conn_id }) + } +} + +impl From<&UdpgwHeader> for Vec { + fn from(header: &UdpgwHeader) -> Vec { + let mut bytes = vec![0; header.len()]; + bytes[0] = header.flags; + bytes[1..3].copy_from_slice(&header.conn_id.to_be_bytes()); + bytes + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub(crate) enum UdpGwResponse { + KeepAlive, + Error, + TcpClose, + Data(Packet), +} + +#[derive(Debug)] +pub(crate) struct UdpGwClientStream { + local_addr: String, + writer: Option, + reader: Option, + conn_id: u16, + closed: bool, + last_activity: std::time::Instant, +} + +impl Drop for UdpGwClientStream { + fn drop(&mut self) { + TCP_COUNTER.fetch_sub(1, Relaxed); + } +} + +impl UdpGwClientStream { + pub fn close(&mut self) { + self.closed = true; + } + + pub fn get_reader(&mut self) -> Option { + self.reader.take() + } + + pub fn set_reader(&mut self, reader: Option) { + self.reader = reader; + } + + pub fn set_writer(&mut self, writer: Option) { + self.writer = writer; + } + + pub fn get_writer(&mut self) -> Option { + self.writer.take() + } + + pub fn local_addr(&self) -> &String { + &self.local_addr + } + + pub fn update_activity(&mut self) { + self.last_activity = std::time::Instant::now(); + } + + pub fn is_closed(&mut self) -> bool { + self.closed + } + + pub fn id(&mut self) -> u16 { + self.conn_id + } + + pub fn new_id(&mut self) -> u16 { + self.conn_id += 1; + self.conn_id + } + + pub fn new(tcp_server_stream: TcpStream) -> Self { + let default = "0.0.0.0:0".parse::().unwrap(); + let local_addr = tcp_server_stream.local_addr().unwrap_or(default).to_string(); + let (rx, tx) = tcp_server_stream.into_split(); + let writer = tx; + let reader = rx; + TCP_COUNTER.fetch_add(1, Relaxed); + UdpGwClientStream { + local_addr, + reader: Some(reader), + writer: Some(writer), + last_activity: std::time::Instant::now(), + closed: false, + conn_id: 0, + } + } +} + +#[derive(Debug)] +pub(crate) struct UdpGwClient { + udp_mtu: u16, + max_connections: u16, + udp_timeout: u64, + keepalive_time: Duration, + server_addr: SocketAddr, + server_connections: Mutex>, +} + +impl UdpGwClient { + pub fn new(udp_mtu: u16, max_connections: u16, keepalive_time: Duration, udp_timeout: u64, server_addr: SocketAddr) -> Self { + let server_connections = Mutex::new(VecDeque::with_capacity(max_connections as usize)); + UdpGwClient { + udp_mtu, + max_connections, + udp_timeout, + server_addr, + keepalive_time, + server_connections, + } + } + + pub(crate) fn get_udp_mtu(&self) -> u16 { + self.udp_mtu + } + + pub(crate) fn get_udp_timeout(&self) -> u64 { + self.udp_timeout + } + + pub(crate) fn is_full(&self) -> bool { + TCP_COUNTER.load(Relaxed) >= self.max_connections as u32 + } + + pub(crate) async fn get_server_connection(&self) -> Option { + self.server_connections.lock().await.pop_front() + } + + pub(crate) async fn release_server_connection(&self, stream: UdpGwClientStream) { + if self.server_connections.lock().await.len() < self.max_connections as usize { + self.server_connections.lock().await.push_back(stream); + } + } + + pub(crate) async fn release_server_connection_full( + &self, + mut stream: UdpGwClientStream, + reader: OwnedReadHalf, + writer: OwnedWriteHalf, + ) { + if self.server_connections.lock().await.len() < self.max_connections as usize { + stream.set_reader(Some(reader)); + stream.set_writer(Some(writer)); + self.server_connections.lock().await.push_back(stream); + } + } + + pub(crate) fn get_server_addr(&self) -> SocketAddr { + self.server_addr + } + + /// Heartbeat task asynchronous function to periodically check and maintain the active state of the server connection. + pub(crate) async fn heartbeat_task(&self) { + loop { + sleep(self.keepalive_time).await; + if let Some(mut stream) = self.get_server_connection().await { + if stream.last_activity.elapsed() < self.keepalive_time { + self.release_server_connection(stream).await; + continue; + } + + let Some(mut stream_reader) = stream.get_reader() else { + continue; + }; + + let Some(mut stream_writer) = stream.get_writer() else { + continue; + }; + let local_addr = stream_writer.local_addr(); + log::debug!("{:?}:{} send keepalive", local_addr, stream.id()); + let keepalive_packet: Vec = Packet::build_keepalive_packet(stream.id()).into(); + if let Err(e) = stream_writer.write_all(&keepalive_packet).await { + log::warn!("{:?}:{} send keepalive failed: {}", local_addr, stream.id(), e); + continue; + } + match UdpGwClient::recv_udpgw_packet(self.udp_mtu, 10, &mut stream_reader).await { + Ok(UdpGwResponse::KeepAlive) => { + stream.update_activity(); + self.release_server_connection_full(stream, stream_reader, stream_writer).await; + } + Ok(v) => log::warn!("{:?}:{} keepalive unexpected response: {:?}", local_addr, stream.id(), v), + Err(e) => log::warn!("{:?}:{} keepalive no response, error \"{}\"", local_addr, stream.id(), e), + } + } + } + } + + /// Parses the UDP response data. + pub(crate) fn parse_udp_response(udp_mtu: u16, data: &[u8]) -> Result { + let packet = Packet::try_from(data)?; + let flags = packet.header.flags; + if flags & UDPGW_FLAG_ERR != 0 { + return Ok(UdpGwResponse::Error); + } + if flags & UDPGW_FLAG_KEEPALIVE != 0 { + return Ok(UdpGwResponse::KeepAlive); + } + if packet.data.len() > udp_mtu as usize { + return Err("too much data".into()); + } + Ok(UdpGwResponse::Data(packet)) + } + + /// Receives a UDP gateway packet. + /// + /// This function is responsible for receiving packets from the UDP gateway + /// + /// # Arguments + /// - `udp_mtu`: The maximum transmission unit size for UDP packets. + /// - `udp_timeout`: The timeout in seconds for receiving UDP packets. + /// - `stream`: A mutable reference to the UDP gateway client stream reader. + /// + /// # Returns + /// - `Result`: Returns a result type containing the parsed UDP gateway response, or an error if one occurs. + pub(crate) async fn recv_udpgw_packet(udp_mtu: u16, udp_timeout: u64, stream: &mut OwnedReadHalf) -> Result { + let mut data = vec![0; udp_mtu.into()]; + let data_len = tokio::time::timeout(tokio::time::Duration::from_secs(udp_timeout + 2), stream.read(&mut data)) + .await + .map_err(std::io::Error::from)??; + if data_len == 0 { + return Ok(UdpGwResponse::TcpClose); + } + UdpGwClient::parse_udp_response(udp_mtu, &data[..data_len]) + } + + /// Sends a UDP gateway packet. + /// + /// This function constructs and sends a UDP gateway packet based on the IPv6 enabled status, data length, + /// remote address, domain (if any), connection ID, and the UDP gateway client writer stream. + /// + /// # Arguments + /// + /// * `ipv6_enabled` - Whether IPv6 is enabled + /// * `data` - The data packet + /// * `remote_addr` - Remote address + /// * `conn_id` - Connection ID + /// * `stream` - UDP gateway client writer stream + /// + /// # Returns + /// + /// Returns `Ok(())` if the packet is sent successfully, otherwise returns an error. + pub(crate) async fn send_udpgw_packet( + ipv6_enabled: bool, + data: &[u8], + remote_addr: &socks5_impl::protocol::Address, + conn_id: u16, + stream: &mut OwnedWriteHalf, + ) -> Result<()> { + if !ipv6_enabled && remote_addr.get_type() == socks5_impl::protocol::AddressType::IPv6 { + return Err("ipv6 not support".into()); + } + let out_data: Vec = Packet::build_packet_from_address(conn_id, remote_addr, data)?.into(); + stream.write_all(&out_data).await?; + + Ok(()) + } +} From e3cc5ea1ce574696801abac49dc6b009f48439e2 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 26 Oct 2024 09:08:35 +0800 Subject: [PATCH 129/196] fix daemonize issues --- src/bin/udpgw_server.rs | 54 ++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index 6f6d080..ac0a052 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -1,5 +1,5 @@ use socks5_impl::protocol::{AddressType, AsyncStreamOperation}; -use std::{net::SocketAddr, sync::Arc}; +use std::net::SocketAddr; use tokio::{ io::AsyncWriteExt, net::{ @@ -10,7 +10,7 @@ use tokio::{ }; use tun2proxy::{ udpgw::{Packet, UDPGW_FLAG_KEEPALIVE}, - ArgVerbosity, Result, + ArgVerbosity, BoxError, Error, Result, }; pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60); @@ -164,19 +164,13 @@ async fn write_to_client(addr: SocketAddr, mut writer: WriteHalf<'_>, mut rx: Re } } -#[tokio::main] -async fn main() -> Result<()> { - let args = Arc::new(UdpGwArgs::parse_args()); - - let tcp_listener = tokio::net::TcpListener::bind(args.listen_addr).await?; +fn main() -> Result<(), BoxError> { + dotenvy::dotenv().ok(); + let args = UdpGwArgs::parse_args(); let default = format!("{:?}", args.verbosity); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); - log::info!("{} {} starting...", module_path!(), env!("CARGO_PKG_VERSION")); - log::info!("UDP Gateway Server running at {}", args.listen_addr); - #[cfg(unix)] if args.daemonize { let stdout = std::fs::File::create("/tmp/udpgw.out")?; @@ -192,8 +186,43 @@ async fn main() -> Result<()> { .map_err(|e| format!("Failed to daemonize process, error:{:?}", e))?; } + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + rt.block_on(main_async(args)) +} + +async fn main_async(args: UdpGwArgs) -> Result<(), BoxError> { + log::info!("{} {} starting...", module_path!(), env!("CARGO_PKG_VERSION")); + log::info!("UDP Gateway Server running at {}", args.listen_addr); + + let shutdown_token = tokio_util::sync::CancellationToken::new(); + let main_loop_handle = tokio::spawn(run(args, shutdown_token.clone())); + + let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); + let ctrlc_fired_clone = ctrlc_fired.clone(); + let ctrlc_handel = ctrlc2::set_async_handler(async move { + log::info!("Ctrl-C received, exiting..."); + ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst); + shutdown_token.cancel(); + }) + .await; + + let _ = main_loop_handle.await?; + + if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) { + log::info!("Ctrl-C fired, waiting the handler to finish..."); + ctrlc_handel.await.map_err(|err| err.to_string())?; + } + + Ok(()) +} + +pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::CancellationToken) -> crate::Result<()> { + let tcp_listener = tokio::net::TcpListener::bind(args.listen_addr).await?; loop { - let (mut tcp_stream, addr) = tcp_listener.accept().await?; + let (mut tcp_stream, addr) = tokio::select! { + v = tcp_listener.accept() => v?, + _ = shutdown_token.cancelled() => break, + }; let client = Client::new(addr); log::info!("client {} connected", addr); let params = args.clone(); @@ -207,4 +236,5 @@ async fn main() -> Result<()> { log::info!("client {} disconnected with {:?}", addr, res); }); } + Ok::<(), Error>(()) } From 2ade72e79dd068d54509a93a4d74894f6d358945 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 26 Oct 2024 10:17:45 +0800 Subject: [PATCH 130/196] publish version 0.6.0 --- .github/workflows/publish-exe.yml | 6 +++--- tests/requirements.txt | 3 ++- tests/tests.py | 10 ++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 778203d..f9c20a0 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -94,9 +94,9 @@ jobs: if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then powershell -Command "(Get-Item README.md).LastWriteTime = Get-Date" powershell -Command "(Get-Item target/${{ matrix.target }}/release/wintun.dll).LastWriteTime = Get-Date" - powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy-bin.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip + powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy-bin.exe, target/${{ matrix.target }}/release/udpgw-server.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then ./build-aarch64-apple-ios.sh zip -r mypubdir4/tun2proxy-aarch64-apple-ios-xcframework.zip ./tun2proxy.xcframework/ @@ -104,7 +104,7 @@ jobs: zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/ fi elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then ./build-android.sh cp ./tun2proxy-android-libs.zip ./mypubdir4/ diff --git a/tests/requirements.txt b/tests/requirements.txt index d44fe44..e0fed8e 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,2 +1,3 @@ requests -python-dotenv \ No newline at end of file +python-dotenv +psutil \ No newline at end of file diff --git a/tests/tests.py b/tests/tests.py index 3ff9dff..7175f64 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -4,6 +4,7 @@ import os import subprocess import time import unittest +import psutil import dotenv import requests @@ -33,6 +34,14 @@ def get_tool_path(): default = default[0] if len(default) > 0 else 'tun2proxy-bin' return os.environ.get('TOOL_PATH', default) +def sudo_kill_process_and_children(proc): + try: + for child in psutil.Process(proc.pid).children(recursive=True): + if child.name() == 'tun2proxy-bin': + subprocess.run(['sudo', 'kill', str(child.pid)]) + subprocess.run(['sudo', 'kill', str(proc.pid)]) + except psutil.NoSuchProcess: + pass class Tun2ProxyTest(unittest.TestCase): @staticmethod @@ -49,6 +58,7 @@ class Tun2ProxyTest(unittest.TestCase): except Exception as e: raise e finally: + sudo_kill_process_and_children(p) p.terminate() p.wait() From b9cf06da33fac91bb211674e855aa7a55ceaaa69 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 27 Oct 2024 15:27:50 +0800 Subject: [PATCH 131/196] refine code --- src/lib.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 242ba15..4d855b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -354,7 +354,11 @@ where let queue = socket_queue.clone(); tokio::spawn(async move { let dst = info.dst; // real UDP destination address - if let Err(e) = handle_udp_gateway_session(udp, udpgw, dst, domain_name, proxy_handler, queue, ipv6_enabled).await { + let dst_addr = match domain_name { + Some(ref d) => socks5_impl::protocol::Address::from((d.clone(), dst.port())), + None => dst.into(), + }; + if let Err(e) = handle_udp_gateway_session(udp, udpgw, &dst_addr, proxy_handler, queue, ipv6_enabled).await { log::info!("Ending {} with \"{}\"", info, e); } log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); @@ -483,8 +487,7 @@ async fn handle_tcp_session( async fn handle_udp_gateway_session( mut udp_stack: IpStackUdpStream, udpgw_client: Arc, - udp_dst: SocketAddr, - domain_name: Option, + udp_dst: &socks5_impl::protocol::Address, proxy_handler: Arc>, socket_queue: Option>, ipv6_enabled: bool, @@ -508,10 +511,7 @@ async fn handle_udp_gateway_session( let tcp_local_addr = stream.local_addr().clone(); - match domain_name { - Some(ref d) => log::info!("[UdpGw] Beginning {} -> {}, domain:{}", &tcp_local_addr, udp_dst, d), - None => log::info!("[UdpGw] Beginning {} -> {}", &tcp_local_addr, udp_dst), - } + log::info!("[UdpGw] Beginning {} -> {}", &tcp_local_addr, udp_dst); let Some(mut reader) = stream.get_reader() else { return Err("get reader failed".into()); @@ -539,11 +539,7 @@ async fn handle_udp_gateway_session( }; crate::traffic_status::traffic_status_update(read_len, 0)?; let new_id = stream.new_id(); - let remote_addr = match domain_name { - Some(ref d) => socks5_impl::protocol::Address::from((d.clone(), udp_dst.port())), - None => udp_dst.into(), - }; - if let Err(e) = UdpGwClient::send_udpgw_packet(ipv6_enabled, &tmp_buf[0..read_len], &remote_addr, new_id, &mut writer).await { + if let Err(e) = UdpGwClient::send_udpgw_packet(ipv6_enabled, &tmp_buf[0..read_len], udp_dst, new_id, &mut writer).await { log::info!("[UdpGw] Ending {} <> {} with send_udpgw_packet {}", &tcp_local_addr, udp_dst, e); break; } From 3fb02f0fc7d2e058d7b0660f068ed58df81cbc1b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 28 Oct 2024 14:03:35 +0800 Subject: [PATCH 132/196] switch to tun crate instead of tun2 --- Cargo.toml | 2 +- src/args.rs | 5 ++--- src/desktop_api.rs | 6 +++--- src/mobile_api.rs | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3ad63ce..650e60f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ thiserror = "1" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" tproxy-config = { version = "6", default-features = false } -tun2 = { version = "3", features = ["async"] } +tun = { version = "0.7", features = ["async"] } udp-stream = { version = "0.0.12", default-features = false } unicase = "2" url = "2" diff --git a/src/args.rs b/src/args.rs index 65d0998..4e71166 100644 --- a/src/args.rs +++ b/src/args.rs @@ -30,10 +30,9 @@ pub struct Args { pub tun_fd: Option, /// Set whether to close the received raw file descriptor on drop or not. - /// This setting is passed to the tun2 crate. - /// See [tun2::Configuration::close_fd_on_drop]. + /// This setting is dependent on [tun_fd]. #[cfg(unix)] - #[arg(long, value_name = "true or false", conflicts_with = "tun")] + #[arg(long, value_name = "true or false", conflicts_with = "tun", requires = "tun_fd")] pub close_fd_on_drop: Option, /// Create a tun interface in a newly created unprivileged namespace diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 0617ad3..17428fb 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -6,7 +6,7 @@ use crate::{ }; use std::os::raw::{c_char, c_int}; use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; -use tun2::{AbstractDevice, DEFAULT_MTU as MTU}; +use tun::{AbstractDevice, DEFAULT_MTU as MTU}; static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); @@ -83,7 +83,7 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::CancellationToken) -> std::io::Result<()> { let bypass_ips = args.bypass.clone(); - let mut tun_config = tun2::Configuration::default(); + let mut tun_config = tun::Configuration::default(); tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); tun_config.destination(TUN_GATEWAY); #[cfg(unix)] @@ -122,7 +122,7 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can #[allow(unused_mut, unused_assignments, unused_variables)] let mut setup = true; - let device = tun2::create_as_async(&tun_config)?; + let device = tun::create_as_async(&tun_config)?; if let Ok(tun_name) = device.tun_name() { tproxy_args = tproxy_args.tun_name(&tun_name); diff --git a/src/mobile_api.rs b/src/mobile_api.rs index 69e684c..a32f68d 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -28,7 +28,7 @@ pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int } let block = async move { - let mut config = tun2::Configuration::default(); + let mut config = tun::Configuration::default(); #[cfg(unix)] if let Some(fd) = args.tun_fd { @@ -49,7 +49,7 @@ pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int config.packet_information(_packet_information); }); - let device = tun2::create_as_async(&config).map_err(std::io::Error::from)?; + let device = tun::create_as_async(&config).map_err(std::io::Error::from)?; let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); join_handle.await.map_err(std::io::Error::from)? From 0aad0d17098af91173dc7a8472de05601c6531d0 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:00:28 +0800 Subject: [PATCH 133/196] refactor udpgw --- src/bin/udpgw_server.rs | 8 +- src/lib.rs | 65 ++++++++------- src/udpgw.rs | 174 ++++++++++++++++++++++++---------------- 3 files changed, 149 insertions(+), 98 deletions(-) diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index ac0a052..a6cb365 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -6,10 +6,10 @@ use tokio::{ tcp::{ReadHalf, WriteHalf}, UdpSocket, }, - sync::mpsc::{self, Receiver, Sender}, + sync::mpsc::{Receiver, Sender}, }; use tun2proxy::{ - udpgw::{Packet, UDPGW_FLAG_KEEPALIVE}, + udpgw::{Packet, UdpFlag}, ArgVerbosity, BoxError, Error, Result, }; @@ -134,7 +134,7 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client let flags = packet.header.flags; let conn_id = packet.header.conn_id; - if flags & UDPGW_FLAG_KEEPALIVE != 0 { + if flags & UdpFlag::KEEPALIVE == UdpFlag::KEEPALIVE { log::trace!("client {} send keepalive", client.addr); // 2. if keepalive packet, do nothing, send keepalive response to client send_keepalive_response(tx.clone(), conn_id).await; @@ -227,7 +227,7 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation log::info!("client {} connected", addr); let params = args.clone(); tokio::spawn(async move { - let (tx, rx) = mpsc::channel::(100); + let (tx, rx) = tokio::sync::mpsc::channel::(100); let (tcp_read_stream, tcp_write_stream) = tcp_stream.split(); let res = tokio::select! { v = process_client_udp_req(¶ms, tx, client, tcp_read_stream) => v, diff --git a/src/lib.rs b/src/lib.rs index 4d855b6..5d69388 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -251,7 +251,7 @@ where )); let client_keepalive = client.clone(); tokio::spawn(async move { - client_keepalive.heartbeat_task().await; + let _ = client_keepalive.heartbeat_task().await; }); client }); @@ -349,7 +349,7 @@ where SocketAddr::V4(_) => SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0)), SocketAddr::V6(_) => SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0)), }; - let tcpinfo = SessionInfo::new(tcp_src, udpgw.get_server_addr(), IpProtocol::Tcp); + let tcpinfo = SessionInfo::new(tcp_src, udpgw.get_udpgw_server_addr(), IpProtocol::Tcp); let proxy_handler = mgr.new_proxy_handler(tcpinfo, None, false).await?; let queue = socket_queue.clone(); tokio::spawn(async move { @@ -495,23 +495,33 @@ async fn handle_udp_gateway_session( let proxy_server_addr = { proxy_handler.lock().await.get_server_addr() }; let udp_mtu = udpgw_client.get_udp_mtu(); let udp_timeout = udpgw_client.get_udp_timeout(); - let mut stream = match udpgw_client.get_server_connection().await { - Some(server) => server, - None => { - if udpgw_client.is_full() { - return Err("max udpgw connection limit reached".into()); + + let mut stream = loop { + match udpgw_client.pop_server_connection_from_queue().await { + Some(stream) => { + if stream.is_closed() { + continue; + } else { + break stream; + } } - let mut tcp_server_stream = create_tcp_stream(&socket_queue, proxy_server_addr).await?; - if let Err(e) = handle_proxy_session(&mut tcp_server_stream, proxy_handler).await { - return Err(format!("udpgw connection error: {}", e).into()); + None => { + if udpgw_client.is_full() { + return Err("max udpgw connection limit reached".into()); + } + let mut tcp_server_stream = create_tcp_stream(&socket_queue, proxy_server_addr).await?; + if let Err(e) = handle_proxy_session(&mut tcp_server_stream, proxy_handler).await { + return Err(format!("udpgw connection error: {}", e).into()); + } + break UdpGwClientStream::new(tcp_server_stream); } - UdpGwClientStream::new(tcp_server_stream) } }; let tcp_local_addr = stream.local_addr().clone(); + let sn = stream.serial_number(); - log::info!("[UdpGw] Beginning {} -> {}", &tcp_local_addr, udp_dst); + log::info!("[UdpGw] Beginning stream {} {} -> {}", sn, &tcp_local_addr, udp_dst); let Some(mut reader) = stream.get_reader() else { return Err("get reader failed".into()); @@ -528,58 +538,59 @@ async fn handle_udp_gateway_session( len = udp_stack.read(&mut tmp_buf) => { let read_len = match len { Ok(0) => { - log::info!("[UdpGw] Ending {} <> {}", &tcp_local_addr, udp_dst); + log::info!("[UdpGw] Ending stream {} {} <> {}", sn, &tcp_local_addr, udp_dst); break; } Ok(n) => n, Err(e) => { - log::info!("[UdpGw] Ending {} <> {} with recv_udp_packet {}", &tcp_local_addr, udp_dst, e); + log::info!("[UdpGw] Ending stream {} {} <> {} with recv_udp_packet {}", sn, &tcp_local_addr, udp_dst, e); break; } }; crate::traffic_status::traffic_status_update(read_len, 0)?; - let new_id = stream.new_id(); + let new_id = stream.new_packet_id(); if let Err(e) = UdpGwClient::send_udpgw_packet(ipv6_enabled, &tmp_buf[0..read_len], udp_dst, new_id, &mut writer).await { - log::info!("[UdpGw] Ending {} <> {} with send_udpgw_packet {}", &tcp_local_addr, udp_dst, e); + log::info!("[UdpGw] Ending stream {} {} <> {} with send_udpgw_packet {}", sn, &tcp_local_addr, udp_dst, e); break; } - log::debug!("[UdpGw] {} -> {} send len {}", &tcp_local_addr, udp_dst, read_len); + log::debug!("[UdpGw] stream {} {} -> {} send len {}", sn, &tcp_local_addr, udp_dst, read_len); stream.update_activity(); } ret = UdpGwClient::recv_udpgw_packet(udp_mtu, udp_timeout, &mut reader) => { match ret { + Err(e) => { + log::warn!("[UdpGw] Ending stream {} {} <> {} with recv_udpgw_packet {}", sn, &tcp_local_addr, udp_dst, e); + stream.close(); + break; + } Ok(packet) => match packet { //should not received keepalive UdpGwResponse::KeepAlive => { - log::error!("[UdpGw] Ending {} <> {} with recv keepalive", &tcp_local_addr, udp_dst); + log::error!("[UdpGw] Ending stream {} {} <> {} with recv keepalive", sn, &tcp_local_addr, udp_dst); stream.close(); break; } //server udp may be timeout,can continue to receive udp data? UdpGwResponse::Error => { - log::info!("[UdpGw] Ending {} <> {} with recv udp error", &tcp_local_addr, udp_dst); + log::info!("[UdpGw] Ending stream {} {} <> {} with recv udp error", sn, &tcp_local_addr, udp_dst); stream.update_activity(); continue; } UdpGwResponse::TcpClose => { - log::error!("[UdpGw] Ending {} <> {} with tcp closed", &tcp_local_addr, udp_dst); + log::error!("[UdpGw] Ending stream {} {} <> {} with tcp closed", sn, &tcp_local_addr, udp_dst); stream.close(); break; } UdpGwResponse::Data(data) => { use socks5_impl::protocol::StreamOperation; let len = data.len(); - log::debug!("[UdpGw] {} <- {} receive len {}", &tcp_local_addr, udp_dst, len); + log::debug!("[UdpGw] stream {} {} <- {} receive len {}", sn, &tcp_local_addr, udp_dst, len); if let Err(e) = udp_stack.write_all(&data.data).await { - log::error!("[UdpGw] Ending {} <> {} with send_udp_packet {}", &tcp_local_addr, udp_dst, e); + log::error!("[UdpGw] Ending stream {} {} <> {} with send_udp_packet {}", sn, &tcp_local_addr, udp_dst, e); break; } crate::traffic_status::traffic_status_update(0, len)?; } - }, - Err(e) => { - log::warn!("[UdpGw] Ending {} <> {} with recv_udpgw_packet {}", &tcp_local_addr, udp_dst, e); - break; } } stream.update_activity(); @@ -588,7 +599,7 @@ async fn handle_udp_gateway_session( } if !stream.is_closed() { - udpgw_client.release_server_connection_full(stream, reader, writer).await; + udpgw_client.store_server_connection_full(stream, reader, writer).await; } Ok(()) diff --git a/src/udpgw.rs b/src/udpgw.rs index 4d7f018..295be25 100644 --- a/src/udpgw.rs +++ b/src/udpgw.rs @@ -15,9 +15,42 @@ pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::(); pub(crate) const UDPGW_MAX_CONNECTIONS: u16 = 100; pub(crate) const UDPGW_KEEPALIVE_TIME: tokio::time::Duration = std::time::Duration::from_secs(10); -pub const UDPGW_FLAG_KEEPALIVE: u8 = 0x01; -pub const UDPGW_FLAG_ERR: u8 = 0x20; -pub const UDPGW_FLAG_DATA: u8 = 0x02; +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct UdpFlag(pub u8); + +impl UdpFlag { + pub const ZERO: UdpFlag = UdpFlag(0x00); + pub const KEEPALIVE: UdpFlag = UdpFlag(0x01); + pub const ERR: UdpFlag = UdpFlag(0x20); + pub const DATA: UdpFlag = UdpFlag(0x02); +} + +impl std::fmt::Display for UdpFlag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let flag = match self.0 { + 0x00 => "ZERO", + 0x01 => "KEEPALIVE", + 0x20 => "ERR", + 0x02 => "DATA", + n => return write!(f, "Unknown UdpFlag(0x{:02X})", n), + }; + write!(f, "UdpFlag({})", flag) + } +} + +impl std::ops::BitAnd for UdpFlag { + type Output = Self; + fn bitand(self, rhs: Self) -> Self::Output { + UdpFlag(self.0 & rhs.0) + } +} + +impl std::ops::BitOr for UdpFlag { + type Output = Self; + fn bitor(self, rhs: Self) -> Self::Output { + UdpFlag(self.0 | rhs.0) + } +} static TCP_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); @@ -98,7 +131,7 @@ impl TryFrom<&[u8]> for Packet { return Err(std::io::ErrorKind::InvalidData.into()); } let header = UdpgwHeader::retrieve_from_stream(&mut iter)?; - let address = if header.flags & UDPGW_FLAG_DATA != 0 { + let address = if header.flags & UdpFlag::DATA != UdpFlag::ZERO { Some(Address::retrieve_from_stream(&mut iter)?) } else { None @@ -114,11 +147,11 @@ impl Packet { } pub fn build_keepalive_packet(conn_id: u16) -> Self { - Packet::new(UdpgwHeader::new(UDPGW_FLAG_KEEPALIVE, conn_id), None, &[]) + Packet::new(UdpgwHeader::new(UdpFlag::KEEPALIVE, conn_id), None, &[]) } pub fn build_error_packet(conn_id: u16) -> Self { - Packet::new(UdpgwHeader::new(UDPGW_FLAG_ERR, conn_id), None, &[]) + Packet::new(UdpgwHeader::new(UdpFlag::ERR, conn_id), None, &[]) } pub fn build_packet_from_address(conn_id: u16, remote_addr: &Address, data: &[u8]) -> std::io::Result { @@ -132,7 +165,7 @@ impl Packet { pub fn build_ip_packet(conn_id: u16, remote_addr: SocketAddr, data: &[u8]) -> Self { let addr: Address = remote_addr.into(); - Packet::new(UdpgwHeader::new(UDPGW_FLAG_DATA, conn_id), Some(addr), data) + Packet::new(UdpgwHeader::new(UdpFlag::DATA, conn_id), Some(addr), data) } pub fn build_domain_packet(conn_id: u16, port: u16, domain: &str, data: &[u8]) -> std::io::Result { @@ -140,7 +173,7 @@ impl Packet { return Err(std::io::ErrorKind::InvalidInput.into()); } let addr = Address::from((domain, port)); - Ok(Packet::new(UdpgwHeader::new(UDPGW_FLAG_DATA, conn_id), Some(addr), data)) + Ok(Packet::new(UdpgwHeader::new(UdpFlag::DATA, conn_id), Some(addr), data)) } } @@ -154,7 +187,7 @@ impl StreamOperation for Packet { stream.read_exact(&mut buf)?; let length = u16::from_be_bytes(buf) as usize; let header = UdpgwHeader::retrieve_from_stream(stream)?; - let address = if header.flags & UDPGW_FLAG_DATA != 0 { + let address = if header.flags & UdpFlag::DATA == UdpFlag::DATA { Some(Address::retrieve_from_stream(stream)?) } else { None @@ -194,7 +227,7 @@ impl AsyncStreamOperation for Packet { r.read_exact(&mut buf).await?; let length = u16::from_be_bytes(buf) as usize; let header = UdpgwHeader::retrieve_from_async_stream(r).await?; - let address = if header.flags & UDPGW_FLAG_DATA != 0 { + let address = if header.flags & UdpFlag::DATA == UdpFlag::DATA { Some(Address::retrieve_from_async_stream(r).await?) } else { None @@ -211,14 +244,14 @@ impl AsyncStreamOperation for Packet { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct UdpgwHeader { - pub flags: u8, + pub flags: UdpFlag, pub conn_id: u16, } impl std::fmt::Display for UdpgwHeader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let id = self.conn_id; - write!(f, "flags: 0x{:02x}, conn_id: {}", self.flags, id) + write!(f, "flags: {}, conn_id: {}", self.flags, id) } } @@ -257,7 +290,7 @@ impl AsyncStreamOperation for UdpgwHeader { } impl UdpgwHeader { - pub fn new(flags: u8, conn_id: u16) -> Self { + pub fn new(flags: UdpFlag, conn_id: u16) -> Self { UdpgwHeader { flags, conn_id } } @@ -274,14 +307,14 @@ impl TryFrom<&[u8]> for UdpgwHeader { return Err(std::io::ErrorKind::InvalidData.into()); } let conn_id = u16::from_be_bytes([value[1], value[2]]); - Ok(UdpgwHeader { flags: value[0], conn_id }) + Ok(UdpgwHeader::new(UdpFlag(value[0]), conn_id)) } } impl From<&UdpgwHeader> for Vec { fn from(header: &UdpgwHeader) -> Vec { let mut bytes = vec![0; header.len()]; - bytes[0] = header.flags; + bytes[0] = header.flags.0; bytes[1..3].copy_from_slice(&header.conn_id.to_be_bytes()); bytes } @@ -296,14 +329,17 @@ pub(crate) enum UdpGwResponse { Data(Packet), } +static SERIAL_NUMBER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); + #[derive(Debug)] pub(crate) struct UdpGwClientStream { - local_addr: String, + local_addr: SocketAddr, writer: Option, reader: Option, conn_id: u16, closed: bool, last_activity: std::time::Instant, + serial_number: u64, } impl Drop for UdpGwClientStream { @@ -333,34 +369,33 @@ impl UdpGwClientStream { self.writer.take() } - pub fn local_addr(&self) -> &String { - &self.local_addr + pub fn local_addr(&self) -> SocketAddr { + self.local_addr } pub fn update_activity(&mut self) { self.last_activity = std::time::Instant::now(); } - pub fn is_closed(&mut self) -> bool { + pub fn is_closed(&self) -> bool { self.closed } - pub fn id(&mut self) -> u16 { - self.conn_id + pub fn serial_number(&self) -> u64 { + self.serial_number } - pub fn new_id(&mut self) -> u16 { + pub fn new_packet_id(&mut self) -> u16 { self.conn_id += 1; self.conn_id } pub fn new(tcp_server_stream: TcpStream) -> Self { let default = "0.0.0.0:0".parse::().unwrap(); - let local_addr = tcp_server_stream.local_addr().unwrap_or(default).to_string(); - let (rx, tx) = tcp_server_stream.into_split(); - let writer = tx; - let reader = rx; + let local_addr = tcp_server_stream.local_addr().unwrap_or(default); + let (reader, writer) = tcp_server_stream.into_split(); TCP_COUNTER.fetch_add(1, Relaxed); + let serial_number = SERIAL_NUMBER.fetch_add(1, Relaxed); UdpGwClientStream { local_addr, reader: Some(reader), @@ -368,6 +403,7 @@ impl UdpGwClientStream { last_activity: std::time::Instant::now(), closed: false, conn_id: 0, + serial_number, } } } @@ -378,18 +414,18 @@ pub(crate) struct UdpGwClient { max_connections: u16, udp_timeout: u64, keepalive_time: Duration, - server_addr: SocketAddr, + udpgw_server: SocketAddr, server_connections: Mutex>, } impl UdpGwClient { - pub fn new(udp_mtu: u16, max_connections: u16, keepalive_time: Duration, udp_timeout: u64, server_addr: SocketAddr) -> Self { + pub fn new(udp_mtu: u16, max_connections: u16, keepalive_time: Duration, udp_timeout: u64, udpgw_server: SocketAddr) -> Self { let server_connections = Mutex::new(VecDeque::with_capacity(max_connections as usize)); UdpGwClient { udp_mtu, max_connections, udp_timeout, - server_addr, + udpgw_server, keepalive_time, server_connections, } @@ -407,22 +443,17 @@ impl UdpGwClient { TCP_COUNTER.load(Relaxed) >= self.max_connections as u32 } - pub(crate) async fn get_server_connection(&self) -> Option { + pub(crate) async fn pop_server_connection_from_queue(&self) -> Option { self.server_connections.lock().await.pop_front() } - pub(crate) async fn release_server_connection(&self, stream: UdpGwClientStream) { + pub(crate) async fn store_server_connection(&self, stream: UdpGwClientStream) { if self.server_connections.lock().await.len() < self.max_connections as usize { self.server_connections.lock().await.push_back(stream); } } - pub(crate) async fn release_server_connection_full( - &self, - mut stream: UdpGwClientStream, - reader: OwnedReadHalf, - writer: OwnedWriteHalf, - ) { + pub(crate) async fn store_server_connection_full(&self, mut stream: UdpGwClientStream, reader: OwnedReadHalf, writer: OwnedWriteHalf) { if self.server_connections.lock().await.len() < self.max_connections as usize { stream.set_reader(Some(reader)); stream.set_writer(Some(writer)); @@ -430,42 +461,51 @@ impl UdpGwClient { } } - pub(crate) fn get_server_addr(&self) -> SocketAddr { - self.server_addr + pub(crate) fn get_udpgw_server_addr(&self) -> SocketAddr { + self.udpgw_server } /// Heartbeat task asynchronous function to periodically check and maintain the active state of the server connection. - pub(crate) async fn heartbeat_task(&self) { + pub(crate) async fn heartbeat_task(&self) -> std::io::Result<()> { loop { sleep(self.keepalive_time).await; - if let Some(mut stream) = self.get_server_connection().await { - if stream.last_activity.elapsed() < self.keepalive_time { - self.release_server_connection(stream).await; - continue; - } + let Some(mut stream) = self.pop_server_connection_from_queue().await else { + continue; + }; - let Some(mut stream_reader) = stream.get_reader() else { - continue; - }; + if stream.is_closed() { + // This stream will be dropped + continue; + } - let Some(mut stream_writer) = stream.get_writer() else { - continue; - }; - let local_addr = stream_writer.local_addr(); - log::debug!("{:?}:{} send keepalive", local_addr, stream.id()); - let keepalive_packet: Vec = Packet::build_keepalive_packet(stream.id()).into(); - if let Err(e) = stream_writer.write_all(&keepalive_packet).await { - log::warn!("{:?}:{} send keepalive failed: {}", local_addr, stream.id(), e); - continue; - } - match UdpGwClient::recv_udpgw_packet(self.udp_mtu, 10, &mut stream_reader).await { - Ok(UdpGwResponse::KeepAlive) => { - stream.update_activity(); - self.release_server_connection_full(stream, stream_reader, stream_writer).await; - } - Ok(v) => log::warn!("{:?}:{} keepalive unexpected response: {:?}", local_addr, stream.id(), v), - Err(e) => log::warn!("{:?}:{} keepalive no response, error \"{}\"", local_addr, stream.id(), e), + if stream.last_activity.elapsed() < self.keepalive_time { + self.store_server_connection(stream).await; + continue; + } + + let Some(mut stream_reader) = stream.get_reader() else { + continue; + }; + + let Some(mut stream_writer) = stream.get_writer() else { + continue; + }; + let local_addr = stream_writer.local_addr()?; + let sn = stream.serial_number(); + log::trace!("stream {} {:?} send keepalive", sn, local_addr); + let keepalive_packet: Vec = Packet::build_keepalive_packet(stream.new_packet_id()).into(); + if let Err(e) = stream_writer.write_all(&keepalive_packet).await { + log::warn!("stream {} {:?} send keepalive failed: {}", sn, local_addr, e); + continue; + } + match UdpGwClient::recv_udpgw_packet(self.udp_mtu, 10, &mut stream_reader).await { + Ok(UdpGwResponse::KeepAlive) => { + stream.update_activity(); + self.store_server_connection_full(stream, stream_reader, stream_writer).await; + log::trace!("stream {} {:?} keepalive success", sn, local_addr); } + Ok(v) => log::warn!("stream {} {:?} keepalive unexpected response: {:?}", sn, local_addr, v), + Err(e) => log::warn!("stream {} {:?} keepalive no response, error \"{}\"", sn, local_addr, e), } } } @@ -474,10 +514,10 @@ impl UdpGwClient { pub(crate) fn parse_udp_response(udp_mtu: u16, data: &[u8]) -> Result { let packet = Packet::try_from(data)?; let flags = packet.header.flags; - if flags & UDPGW_FLAG_ERR != 0 { + if flags & UdpFlag::ERR == UdpFlag::ERR { return Ok(UdpGwResponse::Error); } - if flags & UDPGW_FLAG_KEEPALIVE != 0 { + if flags & UdpFlag::KEEPALIVE == UdpFlag::KEEPALIVE { return Ok(UdpGwResponse::KeepAlive); } if packet.data.len() > udp_mtu as usize { From b4142453fdebd7fa6050eaa25b6b031cf2af09c2 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 30 Oct 2024 19:01:58 +0800 Subject: [PATCH 134/196] Bump version 0.6.1 --- Cargo.toml | 2 +- src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 650e60f..98224b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.0" +version = "0.6.1" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" diff --git a/src/lib.rs b/src/lib.rs index 5d69388..ae4621c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -518,7 +518,7 @@ async fn handle_udp_gateway_session( } }; - let tcp_local_addr = stream.local_addr().clone(); + let tcp_local_addr = stream.local_addr(); let sn = stream.serial_number(); log::info!("[UdpGw] Beginning stream {} {} -> {}", sn, &tcp_local_addr, udp_dst); From 52d814ce79ddd1ae2f36d7a1b1a4a2a74ebb1481 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 31 Oct 2024 10:28:12 +0800 Subject: [PATCH 135/196] refine udpgw --- src/args.rs | 6 +-- src/lib.rs | 11 ++-- src/udpgw.rs | 150 +++++++++++++++++++++++++-------------------------- 3 files changed, 84 insertions(+), 83 deletions(-) diff --git a/src/args.rs b/src/args.rs index 4e71166..919c673 100644 --- a/src/args.rs +++ b/src/args.rs @@ -116,10 +116,10 @@ pub struct Args { #[arg(long, value_name = "IP:PORT")] pub udpgw_server: Option, - /// Max udpgw connections, default value is 100 + /// Max udpgw connections, default value is 5 #[cfg(feature = "udpgw")] #[arg(long, value_name = "number", requires = "udpgw_server")] - pub udpgw_max_connections: Option, + pub udpgw_max_connections: Option, } fn validate_tun(p: &str) -> Result { @@ -201,7 +201,7 @@ impl Args { } #[cfg(feature = "udpgw")] - pub fn udpgw_max_connections(&mut self, udpgw_max_connections: u16) -> &mut Self { + pub fn udpgw_max_connections(&mut self, udpgw_max_connections: usize) -> &mut Self { self.udpgw_max_connections = Some(udpgw_max_connections); self } diff --git a/src/lib.rs b/src/lib.rs index ae4621c..41b901a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -506,7 +506,7 @@ async fn handle_udp_gateway_session( } } None => { - if udpgw_client.is_full() { + if !udpgw_client.is_in_heartbeat_progress() && udpgw_client.is_full().await { return Err("max udpgw connection limit reached".into()); } let mut tcp_server_stream = create_tcp_stream(&socket_queue, proxy_server_addr).await?; @@ -543,13 +543,13 @@ async fn handle_udp_gateway_session( } Ok(n) => n, Err(e) => { - log::info!("[UdpGw] Ending stream {} {} <> {} with recv_udp_packet {}", sn, &tcp_local_addr, udp_dst, e); + log::info!("[UdpGw] Ending stream {} {} <> {} with udp stack \"{}\"", sn, &tcp_local_addr, udp_dst, e); break; } }; crate::traffic_status::traffic_status_update(read_len, 0)?; - let new_id = stream.new_packet_id(); - if let Err(e) = UdpGwClient::send_udpgw_packet(ipv6_enabled, &tmp_buf[0..read_len], udp_dst, new_id, &mut writer).await { + let sn = stream.serial_number(); + if let Err(e) = UdpGwClient::send_udpgw_packet(ipv6_enabled, &tmp_buf[0..read_len], udp_dst, sn, &mut writer).await { log::info!("[UdpGw] Ending stream {} {} <> {} with send_udpgw_packet {}", sn, &tcp_local_addr, udp_dst, e); break; } @@ -584,7 +584,8 @@ async fn handle_udp_gateway_session( UdpGwResponse::Data(data) => { use socks5_impl::protocol::StreamOperation; let len = data.len(); - log::debug!("[UdpGw] stream {} {} <- {} receive len {}", sn, &tcp_local_addr, udp_dst, len); + let f = data.header.flags; + log::debug!("[UdpGw] stream {sn} {} <- {} receive {f} len {len}", &tcp_local_addr, udp_dst); if let Err(e) = udp_stack.write_all(&data.data).await { log::error!("[UdpGw] Ending stream {} {} <> {} with send_udp_packet {}", sn, &tcp_local_addr, udp_dst, e); break; diff --git a/src/udpgw.rs b/src/udpgw.rs index 295be25..9a2f5eb 100644 --- a/src/udpgw.rs +++ b/src/udpgw.rs @@ -12,7 +12,7 @@ use tokio::{ }; pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::(); -pub(crate) const UDPGW_MAX_CONNECTIONS: u16 = 100; +pub(crate) const UDPGW_MAX_CONNECTIONS: usize = 5; pub(crate) const UDPGW_KEEPALIVE_TIME: tokio::time::Duration = std::time::Duration::from_secs(10); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -34,7 +34,7 @@ impl std::fmt::Display for UdpFlag { 0x02 => "DATA", n => return write!(f, "Unknown UdpFlag(0x{:02X})", n), }; - write!(f, "UdpFlag({})", flag) + write!(f, "{}", flag) } } @@ -52,8 +52,6 @@ impl std::ops::BitOr for UdpFlag { } } -static TCP_COUNTER: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); - /// UDP Gateway Packet Format /// /// The format is referenced from SOCKS5 packet format, with additional flags and connection ID fields. @@ -250,8 +248,7 @@ pub struct UdpgwHeader { impl std::fmt::Display for UdpgwHeader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let id = self.conn_id; - write!(f, "flags: {}, conn_id: {}", self.flags, id) + write!(f, "{} conn_id: {}", self.flags, self.conn_id) } } @@ -329,23 +326,27 @@ pub(crate) enum UdpGwResponse { Data(Packet), } -static SERIAL_NUMBER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1); +impl std::fmt::Display for UdpGwResponse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UdpGwResponse::KeepAlive => write!(f, "KeepAlive"), + UdpGwResponse::Error => write!(f, "Error"), + UdpGwResponse::TcpClose => write!(f, "TcpClose"), + UdpGwResponse::Data(packet) => write!(f, "Data({})", packet), + } + } +} + +static SERIAL_NUMBER: std::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new(1); #[derive(Debug)] pub(crate) struct UdpGwClientStream { local_addr: SocketAddr, writer: Option, reader: Option, - conn_id: u16, closed: bool, last_activity: std::time::Instant, - serial_number: u64, -} - -impl Drop for UdpGwClientStream { - fn drop(&mut self) { - TCP_COUNTER.fetch_sub(1, Relaxed); - } + serial_number: u16, } impl UdpGwClientStream { @@ -381,20 +382,14 @@ impl UdpGwClientStream { self.closed } - pub fn serial_number(&self) -> u64 { + pub fn serial_number(&self) -> u16 { self.serial_number } - pub fn new_packet_id(&mut self) -> u16 { - self.conn_id += 1; - self.conn_id - } - pub fn new(tcp_server_stream: TcpStream) -> Self { let default = "0.0.0.0:0".parse::().unwrap(); let local_addr = tcp_server_stream.local_addr().unwrap_or(default); let (reader, writer) = tcp_server_stream.into_split(); - TCP_COUNTER.fetch_add(1, Relaxed); let serial_number = SERIAL_NUMBER.fetch_add(1, Relaxed); UdpGwClientStream { local_addr, @@ -402,7 +397,6 @@ impl UdpGwClientStream { writer: Some(writer), last_activity: std::time::Instant::now(), closed: false, - conn_id: 0, serial_number, } } @@ -411,16 +405,17 @@ impl UdpGwClientStream { #[derive(Debug)] pub(crate) struct UdpGwClient { udp_mtu: u16, - max_connections: u16, + max_connections: usize, udp_timeout: u64, keepalive_time: Duration, udpgw_server: SocketAddr, server_connections: Mutex>, + is_in_heartbeat: std::sync::atomic::AtomicBool, } impl UdpGwClient { - pub fn new(udp_mtu: u16, max_connections: u16, keepalive_time: Duration, udp_timeout: u64, udpgw_server: SocketAddr) -> Self { - let server_connections = Mutex::new(VecDeque::with_capacity(max_connections as usize)); + pub fn new(udp_mtu: u16, max_connections: usize, keepalive_time: Duration, udp_timeout: u64, udpgw_server: SocketAddr) -> Self { + let server_connections = Mutex::new(VecDeque::with_capacity(max_connections)); UdpGwClient { udp_mtu, max_connections, @@ -428,6 +423,7 @@ impl UdpGwClient { udpgw_server, keepalive_time, server_connections, + is_in_heartbeat: std::sync::atomic::AtomicBool::new(false), } } @@ -439,8 +435,8 @@ impl UdpGwClient { self.udp_timeout } - pub(crate) fn is_full(&self) -> bool { - TCP_COUNTER.load(Relaxed) >= self.max_connections as u32 + pub(crate) async fn is_full(&self) -> bool { + self.server_connections.lock().await.len() >= self.max_connections } pub(crate) async fn pop_server_connection_from_queue(&self) -> Option { @@ -448,13 +444,13 @@ impl UdpGwClient { } pub(crate) async fn store_server_connection(&self, stream: UdpGwClientStream) { - if self.server_connections.lock().await.len() < self.max_connections as usize { + if self.server_connections.lock().await.len() < self.max_connections { self.server_connections.lock().await.push_back(stream); } } pub(crate) async fn store_server_connection_full(&self, mut stream: UdpGwClientStream, reader: OwnedReadHalf, writer: OwnedWriteHalf) { - if self.server_connections.lock().await.len() < self.max_connections as usize { + if self.server_connections.lock().await.len() < self.max_connections { stream.set_reader(Some(reader)); stream.set_writer(Some(writer)); self.server_connections.lock().await.push_back(stream); @@ -465,54 +461,59 @@ impl UdpGwClient { self.udpgw_server } + pub(crate) fn is_in_heartbeat_progress(&self) -> bool { + self.is_in_heartbeat.load(Relaxed) + } + /// Heartbeat task asynchronous function to periodically check and maintain the active state of the server connection. pub(crate) async fn heartbeat_task(&self) -> std::io::Result<()> { loop { + self.is_in_heartbeat.store(false, Relaxed); sleep(self.keepalive_time).await; - let Some(mut stream) = self.pop_server_connection_from_queue().await else { - continue; - }; + self.is_in_heartbeat.store(true, Relaxed); + let mut streams = Vec::new(); - if stream.is_closed() { - // This stream will be dropped - continue; - } - - if stream.last_activity.elapsed() < self.keepalive_time { - self.store_server_connection(stream).await; - continue; - } - - let Some(mut stream_reader) = stream.get_reader() else { - continue; - }; - - let Some(mut stream_writer) = stream.get_writer() else { - continue; - }; - let local_addr = stream_writer.local_addr()?; - let sn = stream.serial_number(); - log::trace!("stream {} {:?} send keepalive", sn, local_addr); - let keepalive_packet: Vec = Packet::build_keepalive_packet(stream.new_packet_id()).into(); - if let Err(e) = stream_writer.write_all(&keepalive_packet).await { - log::warn!("stream {} {:?} send keepalive failed: {}", sn, local_addr, e); - continue; - } - match UdpGwClient::recv_udpgw_packet(self.udp_mtu, 10, &mut stream_reader).await { - Ok(UdpGwResponse::KeepAlive) => { - stream.update_activity(); - self.store_server_connection_full(stream, stream_reader, stream_writer).await; - log::trace!("stream {} {:?} keepalive success", sn, local_addr); + while let Some(stream) = self.pop_server_connection_from_queue().await { + if !stream.is_closed() { + streams.push(stream); + } + } + + for mut stream in streams { + if stream.last_activity.elapsed() < self.keepalive_time { + self.store_server_connection(stream).await; + continue; + } + + let Some(mut stream_reader) = stream.get_reader() else { + continue; + }; + + let Some(mut stream_writer) = stream.get_writer() else { + continue; + }; + let local_addr = stream_writer.local_addr()?; + let sn = stream.serial_number(); + let keepalive_packet: Vec = Packet::build_keepalive_packet(sn).into(); + if let Err(e) = stream_writer.write_all(&keepalive_packet).await { + log::warn!("stream {} {:?} send keepalive failed: {}", sn, local_addr, e); + continue; + } + match UdpGwClient::recv_udpgw_packet(self.udp_mtu, self.udp_timeout, &mut stream_reader).await { + Ok(UdpGwResponse::KeepAlive) => { + stream.update_activity(); + self.store_server_connection_full(stream, stream_reader, stream_writer).await; + log::trace!("stream {sn} {:?} send keepalive and recieve it successfully", local_addr); + } + Ok(v) => log::debug!("stream {sn} {:?} keepalive unexpected response: {v}", local_addr), + Err(e) => log::debug!("stream {sn} {:?} keepalive no response, error \"{e}\"", local_addr), } - Ok(v) => log::warn!("stream {} {:?} keepalive unexpected response: {:?}", sn, local_addr, v), - Err(e) => log::warn!("stream {} {:?} keepalive no response, error \"{}\"", sn, local_addr, e), } } } /// Parses the UDP response data. - pub(crate) fn parse_udp_response(udp_mtu: u16, data: &[u8]) -> Result { - let packet = Packet::try_from(data)?; + pub(crate) fn parse_udp_response(udp_mtu: u16, packet: Packet) -> Result { let flags = packet.header.flags; if flags & UdpFlag::ERR == UdpFlag::ERR { return Ok(UdpGwResponse::Error); @@ -538,14 +539,13 @@ impl UdpGwClient { /// # Returns /// - `Result`: Returns a result type containing the parsed UDP gateway response, or an error if one occurs. pub(crate) async fn recv_udpgw_packet(udp_mtu: u16, udp_timeout: u64, stream: &mut OwnedReadHalf) -> Result { - let mut data = vec![0; udp_mtu.into()]; - let data_len = tokio::time::timeout(tokio::time::Duration::from_secs(udp_timeout + 2), stream.read(&mut data)) - .await - .map_err(std::io::Error::from)??; - if data_len == 0 { - return Ok(UdpGwResponse::TcpClose); - } - UdpGwClient::parse_udp_response(udp_mtu, &data[..data_len]) + let packet = tokio::time::timeout( + tokio::time::Duration::from_secs(udp_timeout + 2), + Packet::retrieve_from_async_stream(stream), + ) + .await + .map_err(std::io::Error::from)??; + UdpGwClient::parse_udp_response(udp_mtu, packet) } /// Sends a UDP gateway packet. From d7e391345009dc41b25f47eb9272cda677781ebc Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:08:49 +0800 Subject: [PATCH 136/196] Bump version 0.6.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 98224b5..a17bdb8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.1" +version = "0.6.2" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 9088cf6fe59f0a03719bdde31812469426cdf189 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 2 Nov 2024 07:25:46 +0800 Subject: [PATCH 137/196] minor changes --- src/bin/udpgw_server.rs | 66 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index a6cb365..550f51f 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -60,7 +60,7 @@ impl UdpGwArgs { } } -async fn send_error(tx: Sender, conn_id: u16) { +async fn send_error_response(tx: Sender, conn_id: u16) { let error_packet = Packet::build_error_packet(conn_id); if let Err(e) = tx.send(error_packet).await { log::error!("send error response error {:?}", e); @@ -97,12 +97,12 @@ async fn process_udp(client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Sen }; // 1. send udp data to destination server socket.send_to(&packet.data, &dst_addr).await?; - packet.data.resize(udp_mtu as usize, 0); // 2. receive response from destination server - let (len, _addr) = tokio::time::timeout(tokio::time::Duration::from_secs(udp_timeout), socket.recv_from(&mut packet.data)) + let mut buf = vec![0u8; udp_mtu as usize]; + let (len, _addr) = tokio::time::timeout(tokio::time::Duration::from_secs(udp_timeout), socket.recv_from(&mut buf)) .await .map_err(std::io::Error::from)??; - packet.data.truncate(len); + packet.data = buf[..len].to_vec(); // 3. send response back to client use std::io::{Error, ErrorKind::BrokenPipe}; tx.send(packet).await.map_err(|e| Error::new(BrokenPipe, e))?; @@ -119,7 +119,7 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client let packet = match res { Ok(Ok(packet)) => packet, Ok(Err(e)) => { - log::error!("client {} retrieve_from_async_stream \"{}\"", client.addr, e); + log::debug!("client {} retrieve_from_async_stream \"{}\"", client.addr, e); break; } Err(e) => { @@ -146,8 +146,8 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client let tx = tx.clone(); tokio::spawn(async move { if let Err(e) = process_udp(client.addr, udp_mtu, udp_timeout, tx.clone(), packet).await { - send_error(tx, conn_id).await; - log::error!("client {} process udp function {}", client.addr, e); + send_error_response(tx, conn_id).await; + log::debug!("client {} process udp function \"{e}\"", client.addr); } }); } @@ -164,32 +164,6 @@ async fn write_to_client(addr: SocketAddr, mut writer: WriteHalf<'_>, mut rx: Re } } -fn main() -> Result<(), BoxError> { - dotenvy::dotenv().ok(); - let args = UdpGwArgs::parse_args(); - - let default = format!("{:?}", args.verbosity); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); - - #[cfg(unix)] - if args.daemonize { - let stdout = std::fs::File::create("/tmp/udpgw.out")?; - let stderr = std::fs::File::create("/tmp/udpgw.err")?; - let daemonize = daemonize::Daemonize::new() - .working_directory("/tmp") - .umask(0o777) - .stdout(stdout) - .stderr(stderr) - .privileged_action(|| "Executed before drop privileges"); - let _ = daemonize - .start() - .map_err(|e| format!("Failed to daemonize process, error:{:?}", e))?; - } - - let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; - rt.block_on(main_async(args)) -} - async fn main_async(args: UdpGwArgs) -> Result<(), BoxError> { log::info!("{} {} starting...", module_path!(), env!("CARGO_PKG_VERSION")); log::info!("UDP Gateway Server running at {}", args.listen_addr); @@ -238,3 +212,29 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation } Ok::<(), Error>(()) } + +fn main() -> Result<(), BoxError> { + dotenvy::dotenv().ok(); + let args = UdpGwArgs::parse_args(); + + let default = format!("{:?}", args.verbosity); + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); + + #[cfg(unix)] + if args.daemonize { + let stdout = std::fs::File::create("/tmp/udpgw.out")?; + let stderr = std::fs::File::create("/tmp/udpgw.err")?; + let daemonize = daemonize::Daemonize::new() + .working_directory("/tmp") + .umask(0o777) + .stdout(stdout) + .stderr(stderr) + .privileged_action(|| "Executed before drop privileges"); + let _ = daemonize + .start() + .map_err(|e| format!("Failed to daemonize process, error:{:?}", e))?; + } + + let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; + rt.block_on(main_async(args)) +} From 53f60ffda67851e19a662d346b40cd3208cf73ab Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 2 Nov 2024 13:55:47 +0800 Subject: [PATCH 138/196] readme on udpgw --- README.md | 55 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index a236c7f..5b4ba04 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and - GFW evasion mechanism for certain use cases (see [issue #35](https://github.com/tun2proxy/tun2proxy/issues/35)) - SOCKS5 UDP support - Native support for proxying DNS over TCP +- UdpGW (UDP gateway) support for UDP over TCP, see the [wiki](https://github.com/tun2proxy/tun2proxy/wiki/UDP-gateway-feature) for more information ## Build Clone the repository and `cd` into the project folder. Then run the following: @@ -129,30 +130,40 @@ Tunnel interface to proxy. Usage: tun2proxy-bin [OPTIONS] --proxy [ADMIN_COMMAND]... Arguments: - [ADMIN_COMMAND]... Specify a command to run with root-like capabilities in the new namespace when using `--unshare`. - This could be useful to start additional daemons, e.g. `openvpn` instance + [ADMIN_COMMAND]... Specify a command to run with root-like capabilities in the new namespace when using `--unshare`. This could be + useful to start additional daemons, e.g. `openvpn` instance Options: - -p, --proxy Proxy URL in the form proto://[username[:password]@]host:port, where proto is one of - socks4, socks5, http. For example: socks5://myname:password@127.0.0.1:1080 - -t, --tun Name of the tun interface, such as tun0, utun4, etc. If this option is not provided, the - OS will generate a random one - --tun-fd File descriptor of the tun interface - --unshare Create a tun interface in a newly created unprivileged namespace while maintaining proxy - connectivity via the global network namespace - -6, --ipv6-enabled IPv6 enabled - -s, --setup Routing and system setup, which decides whether to setup the routing and system - configuration. This option is only available on Linux and requires root-like privileges. - See `capabilities(7)` - -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct] - --dns-addr DNS resolver address [default: 8.8.8.8] - -b, --bypass IPs used in routing setup which should bypass the tunnel, in the form of IP or IP/CIDR. - Multiple IPs can be specified, e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8 - --tcp-timeout TCP timeout in seconds [default: 600] - --udp-timeout UDP timeout in seconds [default: 10] - -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] - -h, --help Print help - -V, --version Print version + -p, --proxy Proxy URL in the form proto://[username[:password]@]host:port, where proto is one of + socks4, socks5, http. Username and password are encoded in percent encoding. For example: + socks5://myname:pass%40word@127.0.0.1:1080 + -t, --tun Name of the tun interface, such as tun0, utun4, etc. If this option is not provided, the + OS will generate a random one + --tun-fd File descriptor of the tun interface + --close-fd-on-drop Set whether to close the received raw file descriptor on drop or not. This setting is + dependent on [tun_fd] [possible values: true, false] + --unshare Create a tun interface in a newly created unprivileged namespace while maintaining proxy + connectivity via the global network namespace + --unshare-pidfile Create a pidfile of `unshare` process when using `--unshare` + -6, --ipv6-enabled IPv6 enabled + -s, --setup Routing and system setup, which decides whether to setup the routing and system + configuration. This option is only available on Linux and requires root-like privileges. + See `capabilities(7)` + -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct] + --dns-addr DNS resolver address [default: 8.8.8.8] + --virtual-dns-pool IP address pool to be used by virtual DNS in CIDR notation [default: 198.18.0.0/15] + -b, --bypass IPs used in routing setup which should bypass the tunnel, in the form of IP or IP/CIDR. + Multiple IPs can be specified, e.g. --bypass 3.4.5.0/24 --bypass 5.6.7.8 + --tcp-timeout TCP timeout in seconds [default: 600] + --udp-timeout UDP timeout in seconds [default: 10] + -v, --verbosity Verbosity level [default: info] [possible values: off, error, warn, info, debug, trace] + --daemonize Daemonize for unix family or run as Windows service + --exit-on-fatal-error Exit immediately when fatal error occurs, useful for running as a service + --max-sessions Maximum number of sessions to be handled concurrently [default: 200] + --udpgw-server UDP gateway server address, similar to badvpn-udpgw + --udpgw-max-connections Max udpgw connections, default value is 5 + -h, --help Print help + -V, --version Print version ``` Currently, tun2proxy supports HTTP, SOCKS4/SOCKS4a and SOCKS5. A proxy is supplied to the `--proxy` argument in the URL format. For example, an HTTP proxy at `1.2.3.4:3128` with a username of `john.doe` and a password of `secret` is From e8143a691b4e2152e62932bf0f5f43a716c38e1c Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:16:54 +0800 Subject: [PATCH 139/196] remove useless is_in_heartbeat in udpgw --- src/lib.rs | 3 --- src/udpgw.rs | 14 +------------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 41b901a..1aa64ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -506,9 +506,6 @@ async fn handle_udp_gateway_session( } } None => { - if !udpgw_client.is_in_heartbeat_progress() && udpgw_client.is_full().await { - return Err("max udpgw connection limit reached".into()); - } let mut tcp_server_stream = create_tcp_stream(&socket_queue, proxy_server_addr).await?; if let Err(e) = handle_proxy_session(&mut tcp_server_stream, proxy_handler).await { return Err(format!("udpgw connection error: {}", e).into()); diff --git a/src/udpgw.rs b/src/udpgw.rs index 9a2f5eb..be791f8 100644 --- a/src/udpgw.rs +++ b/src/udpgw.rs @@ -13,7 +13,7 @@ use tokio::{ pub(crate) const UDPGW_LENGTH_FIELD_SIZE: usize = std::mem::size_of::(); pub(crate) const UDPGW_MAX_CONNECTIONS: usize = 5; -pub(crate) const UDPGW_KEEPALIVE_TIME: tokio::time::Duration = std::time::Duration::from_secs(10); +pub(crate) const UDPGW_KEEPALIVE_TIME: tokio::time::Duration = std::time::Duration::from_secs(30); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct UdpFlag(pub u8); @@ -410,7 +410,6 @@ pub(crate) struct UdpGwClient { keepalive_time: Duration, udpgw_server: SocketAddr, server_connections: Mutex>, - is_in_heartbeat: std::sync::atomic::AtomicBool, } impl UdpGwClient { @@ -423,7 +422,6 @@ impl UdpGwClient { udpgw_server, keepalive_time, server_connections, - is_in_heartbeat: std::sync::atomic::AtomicBool::new(false), } } @@ -435,10 +433,6 @@ impl UdpGwClient { self.udp_timeout } - pub(crate) async fn is_full(&self) -> bool { - self.server_connections.lock().await.len() >= self.max_connections - } - pub(crate) async fn pop_server_connection_from_queue(&self) -> Option { self.server_connections.lock().await.pop_front() } @@ -461,16 +455,10 @@ impl UdpGwClient { self.udpgw_server } - pub(crate) fn is_in_heartbeat_progress(&self) -> bool { - self.is_in_heartbeat.load(Relaxed) - } - /// Heartbeat task asynchronous function to periodically check and maintain the active state of the server connection. pub(crate) async fn heartbeat_task(&self) -> std::io::Result<()> { loop { - self.is_in_heartbeat.store(false, Relaxed); sleep(self.keepalive_time).await; - self.is_in_heartbeat.store(true, Relaxed); let mut streams = Vec::new(); while let Some(stream) = self.pop_server_connection_from_queue().await { From 21355e37da43b1832b6c4d144a75302fa13afb25 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 3 Nov 2024 10:36:07 +0800 Subject: [PATCH 140/196] Bump version 0.6.3 --- Cargo.toml | 2 +- README.md | 5 +++-- src/args.rs | 19 +++++++++++++------ src/lib.rs | 11 ++++++----- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a17bdb8..f2d171d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.2" +version = "0.6.3" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" diff --git a/README.md b/README.md index 5b4ba04..06f0601 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,9 @@ Options: --daemonize Daemonize for unix family or run as Windows service --exit-on-fatal-error Exit immediately when fatal error occurs, useful for running as a service --max-sessions Maximum number of sessions to be handled concurrently [default: 200] - --udpgw-server UDP gateway server address, similar to badvpn-udpgw - --udpgw-max-connections Max udpgw connections, default value is 5 + --udpgw-server UDP gateway server address, forwards UDP packets via specified TCP server + --udpgw-connections Max connections for the UDP gateway, default value is 5 + --udpgw-keepalive Keepalive interval in seconds for the UDP gateway, default value is 30 -h, --help Print help -V, --version Print version ``` diff --git a/src/args.rs b/src/args.rs index 919c673..85904db 100644 --- a/src/args.rs +++ b/src/args.rs @@ -111,15 +111,20 @@ pub struct Args { #[arg(long, value_name = "number", default_value = "200")] pub max_sessions: usize, - /// UDP gateway server address, similar to badvpn-udpgw + /// UDP gateway server address, forwards UDP packets via specified TCP server #[cfg(feature = "udpgw")] #[arg(long, value_name = "IP:PORT")] pub udpgw_server: Option, - /// Max udpgw connections, default value is 5 + /// Max connections for the UDP gateway, default value is 5 #[cfg(feature = "udpgw")] #[arg(long, value_name = "number", requires = "udpgw_server")] - pub udpgw_max_connections: Option, + pub udpgw_connections: Option, + + /// Keepalive interval in seconds for the UDP gateway, default value is 30 + #[cfg(feature = "udpgw")] + #[arg(long, value_name = "seconds", requires = "udpgw_server")] + pub udpgw_keepalive: Option, } fn validate_tun(p: &str) -> Result { @@ -166,7 +171,9 @@ impl Default for Args { #[cfg(feature = "udpgw")] udpgw_server: None, #[cfg(feature = "udpgw")] - udpgw_max_connections: None, + udpgw_connections: None, + #[cfg(feature = "udpgw")] + udpgw_keepalive: None, } } } @@ -201,8 +208,8 @@ impl Args { } #[cfg(feature = "udpgw")] - pub fn udpgw_max_connections(&mut self, udpgw_max_connections: usize) -> &mut Self { - self.udpgw_max_connections = Some(udpgw_max_connections); + pub fn udpgw_connections(&mut self, udpgw_connections: usize) -> &mut Self { + self.udpgw_connections = Some(udpgw_connections); self } diff --git a/src/lib.rs b/src/lib.rs index 1aa64ca..932dda8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -240,14 +240,15 @@ where let mut ip_stack = ipstack::IpStack::new(ipstack_config, device); #[cfg(feature = "udpgw")] - let udpgw_client = args.udpgw_server.as_ref().map(|addr| { - log::info!("UDPGW enabled"); + let udpgw_client = args.udpgw_server.map(|addr| { + log::info!("UDP Gateway enabled, server: {}", addr); + use std::time::Duration; let client = Arc::new(UdpGwClient::new( mtu, - args.udpgw_max_connections.unwrap_or(UDPGW_MAX_CONNECTIONS), - UDPGW_KEEPALIVE_TIME, + args.udpgw_connections.unwrap_or(UDPGW_MAX_CONNECTIONS), + args.udpgw_keepalive.map(Duration::from_secs).unwrap_or(UDPGW_KEEPALIVE_TIME), args.udp_timeout, - *addr, + addr, )); let client_keepalive = client.clone(); tokio::spawn(async move { From c2382ee29b4c35669cf8d8debd90a1c364c7f943 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 3 Nov 2024 15:15:03 +0800 Subject: [PATCH 141/196] minor changes --- src/bin/udpgw_server.rs | 25 +++++++++++-------------- src/lib.rs | 6 ++++-- src/udpgw.rs | 16 ++++++++++++---- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index 550f51f..8f475e5 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -1,4 +1,4 @@ -use socks5_impl::protocol::{AddressType, AsyncStreamOperation}; +use socks5_impl::protocol::AsyncStreamOperation; use std::net::SocketAddr; use tokio::{ io::AsyncWriteExt, @@ -44,7 +44,7 @@ pub struct UdpGwArgs { /// Daemonize for unix family or run as Windows service #[cfg(unix)] - #[arg(long)] + #[arg(short, long)] pub daemonize: bool, /// Verbosity level @@ -76,25 +76,22 @@ async fn send_keepalive_response(tx: Sender, conn_id: u16) { /// Send data field of packet from client to destination server and receive response, /// then wrap response data to the packet's data field and send packet back to client. -async fn process_udp(client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Sender, mut packet: Packet) -> Result<()> { +async fn process_udp(_client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Sender, mut packet: Packet) -> Result<()> { let Some(dst_addr) = &packet.address else { - log::error!("client {} udp request address is None", client); - return Ok(()); + return Err(std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "udp request address is None").into()); }; - let std_sock = if dst_addr.get_type() == AddressType::IPv6 { - std::net::UdpSocket::bind("[::]:0")? - } else { - std::net::UdpSocket::bind("0.0.0.0:0")? + use std::net::ToSocketAddrs; + let Some(dst_addr) = dst_addr.to_socket_addrs()?.next() else { + return Err(std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "to_socket_addrs").into()); + }; + let std_sock = match dst_addr { + std::net::SocketAddr::V6(_) => std::net::UdpSocket::bind("[::]:0")?, + std::net::SocketAddr::V4(_) => std::net::UdpSocket::bind("0.0.0.0:0")?, }; std_sock.set_nonblocking(true)?; #[cfg(unix)] nix::sys::socket::setsockopt(&std_sock, nix::sys::socket::sockopt::ReuseAddr, &true)?; let socket = UdpSocket::from_std(std_sock)?; - use std::net::ToSocketAddrs; - let Some(dst_addr) = dst_addr.to_socket_addrs()?.next() else { - log::error!("client {} udp request address to_socket_addrs", client); - return Ok(()); - }; // 1. send udp data to destination server socket.send_to(&packet.data, &dst_addr).await?; // 2. receive response from destination server diff --git a/src/lib.rs b/src/lib.rs index 932dda8..1972b9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -555,13 +555,16 @@ async fn handle_udp_gateway_session( stream.update_activity(); } ret = UdpGwClient::recv_udpgw_packet(udp_mtu, udp_timeout, &mut reader) => { + if let Ok((len, _)) = ret { + crate::traffic_status::traffic_status_update(0, len)?; + } match ret { Err(e) => { log::warn!("[UdpGw] Ending stream {} {} <> {} with recv_udpgw_packet {}", sn, &tcp_local_addr, udp_dst, e); stream.close(); break; } - Ok(packet) => match packet { + Ok((_, packet)) => match packet { //should not received keepalive UdpGwResponse::KeepAlive => { log::error!("[UdpGw] Ending stream {} {} <> {} with recv keepalive", sn, &tcp_local_addr, udp_dst); @@ -588,7 +591,6 @@ async fn handle_udp_gateway_session( log::error!("[UdpGw] Ending stream {} {} <> {} with send_udp_packet {}", sn, &tcp_local_addr, udp_dst, e); break; } - crate::traffic_status::traffic_status_update(0, len)?; } } } diff --git a/src/udpgw.rs b/src/udpgw.rs index be791f8..ae2cb87 100644 --- a/src/udpgw.rs +++ b/src/udpgw.rs @@ -467,6 +467,8 @@ impl UdpGwClient { } } + let (mut tx, mut rx) = (0, 0); + for mut stream in streams { if stream.last_activity.elapsed() < self.keepalive_time { self.store_server_connection(stream).await; @@ -483,20 +485,26 @@ impl UdpGwClient { let local_addr = stream_writer.local_addr()?; let sn = stream.serial_number(); let keepalive_packet: Vec = Packet::build_keepalive_packet(sn).into(); + tx += keepalive_packet.len(); if let Err(e) = stream_writer.write_all(&keepalive_packet).await { log::warn!("stream {} {:?} send keepalive failed: {}", sn, local_addr, e); continue; } match UdpGwClient::recv_udpgw_packet(self.udp_mtu, self.udp_timeout, &mut stream_reader).await { - Ok(UdpGwResponse::KeepAlive) => { + Ok((len, UdpGwResponse::KeepAlive)) => { stream.update_activity(); self.store_server_connection_full(stream, stream_reader, stream_writer).await; log::trace!("stream {sn} {:?} send keepalive and recieve it successfully", local_addr); + rx += len; + } + Ok((len, v)) => { + log::debug!("stream {sn} {:?} keepalive unexpected response: {v}", local_addr); + rx += len; } - Ok(v) => log::debug!("stream {sn} {:?} keepalive unexpected response: {v}", local_addr), Err(e) => log::debug!("stream {sn} {:?} keepalive no response, error \"{e}\"", local_addr), } } + crate::traffic_status::traffic_status_update(tx, rx)?; } } @@ -526,14 +534,14 @@ impl UdpGwClient { /// /// # Returns /// - `Result`: Returns a result type containing the parsed UDP gateway response, or an error if one occurs. - pub(crate) async fn recv_udpgw_packet(udp_mtu: u16, udp_timeout: u64, stream: &mut OwnedReadHalf) -> Result { + pub(crate) async fn recv_udpgw_packet(udp_mtu: u16, udp_timeout: u64, stream: &mut OwnedReadHalf) -> Result<(usize, UdpGwResponse)> { let packet = tokio::time::timeout( tokio::time::Duration::from_secs(udp_timeout + 2), Packet::retrieve_from_async_stream(stream), ) .await .map_err(std::io::Error::from)??; - UdpGwClient::parse_udp_response(udp_mtu, packet) + Ok((packet.len(), UdpGwClient::parse_udp_response(udp_mtu, packet)?)) } /// Sends a UDP gateway packet. From 1a508918a2df4c650b51862dc06a67ea8e55de93 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:31:33 +0800 Subject: [PATCH 142/196] Auto merge script --- .github/workflows/auto-merge.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/auto-merge.yaml diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml new file mode 100644 index 0000000..c39e8a6 --- /dev/null +++ b/.github/workflows/auto-merge.yaml @@ -0,0 +1,20 @@ +name: Dependabot Auto Merge + +on: + pull_request_target: + types: [labeled] + +jobs: + auto: + if: github.actor == 'dependabot[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Auto approve pull request, then squash and merge + uses: ahmadnassri/action-dependabot-auto-merge@v2 + with: + target: minor + # here `PAT_REPO_ADMIN` is a user's passkey provided by github. + github-token: ${{ secrets.PAT_REPO_ADMIN }} From 8c98d1dc7404cdff9125ab0fc46f3864a0979050 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2024 10:37:46 +0800 Subject: [PATCH 143/196] Update thiserror requirement from 1 to 2 (#162) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f2d171d..a227dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ log = { version = "0.4", features = ["std"] } mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" socks5-impl = { version = "0.5" } -thiserror = "1" +thiserror = "2" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" tproxy-config = { version = "6", default-features = false } From 28d54be6388fe6d698deb0c7d9171d4c26e378ae Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:30:56 +0800 Subject: [PATCH 144/196] Bump version 0.6.4 --- Cargo.toml | 2 +- src/udpgw.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a227dcb..e9351ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.3" +version = "0.6.4" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" diff --git a/src/udpgw.rs b/src/udpgw.rs index ae2cb87..baa7d36 100644 --- a/src/udpgw.rs +++ b/src/udpgw.rs @@ -218,7 +218,7 @@ impl StreamOperation for Packet { impl AsyncStreamOperation for Packet { async fn retrieve_from_async_stream(r: &mut R) -> std::io::Result where - R: tokio::io::AsyncRead + Unpin + Send, + R: tokio::io::AsyncRead + Unpin + Send + ?Sized, Self: Sized, { let mut buf = [0; UDPGW_LENGTH_FIELD_SIZE]; @@ -277,7 +277,7 @@ impl StreamOperation for UdpgwHeader { impl AsyncStreamOperation for UdpgwHeader { async fn retrieve_from_async_stream(r: &mut R) -> std::io::Result where - R: tokio::io::AsyncRead + Unpin + Send, + R: tokio::io::AsyncRead + Unpin + Send + ?Sized, Self: Sized, { let mut buf = [0; UdpgwHeader::static_len()]; From 23d4e5936761e90383edadea00e62ba172fdba4a Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:51:28 +0800 Subject: [PATCH 145/196] minor changes --- src/bin/udpgw_server.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index 8f475e5..d02d6c4 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -29,6 +29,7 @@ impl Client { } #[derive(Debug, Clone, clap::Parser)] +#[command(author, version, about = "UDP Gateway Server for tun2proxy", long_about = None)] pub struct UdpGwArgs { /// UDP gateway listen address #[arg(short, long, value_name = "IP:PORT", default_value = "127.0.0.1:7300")] From 731490684175cb6f1a34de1c6a228e5331b6d33f Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:48:02 +0800 Subject: [PATCH 146/196] mask_socket_addr function --- src/bin/udpgw_server.rs | 53 +++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index d02d6c4..e78fbb9 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -77,7 +77,7 @@ async fn send_keepalive_response(tx: Sender, conn_id: u16) { /// Send data field of packet from client to destination server and receive response, /// then wrap response data to the packet's data field and send packet back to client. -async fn process_udp(_client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Sender, mut packet: Packet) -> Result<()> { +async fn process_udp(udp_mtu: u16, udp_timeout: u64, tx: Sender, mut packet: Packet) -> Result<()> { let Some(dst_addr) = &packet.address else { return Err(std::io::Error::new(std::io::ErrorKind::AddrNotAvailable, "udp request address is None").into()); }; @@ -107,22 +107,53 @@ async fn process_udp(_client: SocketAddr, udp_mtu: u16, udp_timeout: u64, tx: Se Ok(()) } +fn mask_ip(ip: &str) -> String { + if ip.len() <= 2 { + return ip.to_string(); + } + let mut masked_ip = String::new(); + for (i, c) in ip.chars().enumerate() { + if i == 0 || i == ip.len() - 1 || c == '.' || c == ':' { + masked_ip.push(c); + } else { + masked_ip.push('*'); + } + } + masked_ip +} + +fn mask_socket_addr(socket_addr: std::net::SocketAddr) -> String { + match socket_addr { + std::net::SocketAddr::V4(addr) => { + let masked_ip = mask_ip(&addr.ip().to_string()); + format!("{}:{}", masked_ip, addr.port()) + } + std::net::SocketAddr::V6(addr) => { + let masked_ip = mask_ip(&addr.ip().to_string()); + format!("[{}]:{}", masked_ip, addr.port()) + } + } +} + async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client: Client, mut reader: ReadHalf<'_>) -> std::io::Result<()> { let udp_timeout = args.udp_timeout; let udp_mtu = args.udp_mtu; + let masked_addr = mask_socket_addr(client.addr); + loop { + let masked_addr = masked_addr.clone(); // 1. read udpgw packet from client let res = tokio::time::timeout(tokio::time::Duration::from_secs(2), Packet::retrieve_from_async_stream(&mut reader)).await; let packet = match res { Ok(Ok(packet)) => packet, Ok(Err(e)) => { - log::debug!("client {} retrieve_from_async_stream \"{}\"", client.addr, e); + log::debug!("client {} retrieve_from_async_stream \"{}\"", masked_addr, e); break; } Err(e) => { if client.last_activity.elapsed() >= CLIENT_DISCONNECT_TIMEOUT { - log::debug!("client {} last_activity elapsed \"{e}\"", client.addr); + log::debug!("client {} last_activity elapsed \"{e}\"", masked_addr); break; } continue; @@ -133,19 +164,19 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client let flags = packet.header.flags; let conn_id = packet.header.conn_id; if flags & UdpFlag::KEEPALIVE == UdpFlag::KEEPALIVE { - log::trace!("client {} send keepalive", client.addr); + log::trace!("client {} send keepalive", masked_addr); // 2. if keepalive packet, do nothing, send keepalive response to client send_keepalive_response(tx.clone(), conn_id).await; continue; } - log::trace!("client {} received udp data {}", client.addr, packet); + log::trace!("client {} received udp data {}", masked_addr, packet); // 3. process client udpgw packet in a new task let tx = tx.clone(); tokio::spawn(async move { - if let Err(e) = process_udp(client.addr, udp_mtu, udp_timeout, tx.clone(), packet).await { + if let Err(e) = process_udp(udp_mtu, udp_timeout, tx.clone(), packet).await { send_error_response(tx, conn_id).await; - log::debug!("client {} process udp function \"{e}\"", client.addr); + log::debug!("client {} process udp function \"{e}\"", masked_addr); } }); } @@ -153,10 +184,11 @@ async fn process_client_udp_req(args: &UdpGwArgs, tx: Sender, mut client } async fn write_to_client(addr: SocketAddr, mut writer: WriteHalf<'_>, mut rx: Receiver) -> std::io::Result<()> { + let masked_addr = mask_socket_addr(addr); loop { use std::io::{Error, ErrorKind::BrokenPipe}; let packet = rx.recv().await.ok_or(Error::new(BrokenPipe, "recv error"))?; - log::trace!("send response to client {} with {}", addr, packet); + log::trace!("send response to client {} with {}", masked_addr, packet); let data: Vec = packet.into(); let _r = writer.write(&data).await?; } @@ -196,7 +228,8 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation _ = shutdown_token.cancelled() => break, }; let client = Client::new(addr); - log::info!("client {} connected", addr); + let masked_addr = mask_socket_addr(addr); + log::info!("client {} connected", masked_addr); let params = args.clone(); tokio::spawn(async move { let (tx, rx) = tokio::sync::mpsc::channel::(100); @@ -205,7 +238,7 @@ pub async fn run(args: UdpGwArgs, shutdown_token: tokio_util::sync::Cancellation v = process_client_udp_req(¶ms, tx, client, tcp_read_stream) => v, v = write_to_client(addr, tcp_write_stream, rx) => v, }; - log::info!("client {} disconnected with {:?}", addr, res); + log::info!("client {} disconnected with {:?}", masked_addr, res); }); } Ok::<(), Error>(()) From ee4df8f97bc4dc0aea76ca33c778d9a4fcabe0a8 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 16 Nov 2024 23:52:17 +0800 Subject: [PATCH 147/196] cbindgen issues --- .github/workflows/publish-exe.yml | 8 ++++---- build-aarch64-apple-ios-debug.sh | 2 +- build-aarch64-apple-ios.sh | 2 +- build-android.sh | 2 +- build-apple.sh | 2 +- cbindgen.toml | 4 ++++ 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index f9c20a0..628baf3 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -90,13 +90,13 @@ jobs: cargo build --all-features --release --target ${{ matrix.target }} fi fi - cbindgen --config cbindgen.toml -l C --cpp-compat -o target/tun2proxy-ffi.h + cbindgen --config cbindgen.toml -o target/tun2proxy.h if [[ "${{ matrix.host_os }}" == "windows-latest" ]]; then powershell -Command "(Get-Item README.md).LastWriteTime = Get-Date" powershell -Command "(Get-Item target/${{ matrix.target }}/release/wintun.dll).LastWriteTime = Get-Date" - powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy-bin.exe, target/${{ matrix.target }}/release/udpgw-server.exe, README.md, target/tun2proxy-ffi.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip + powershell Compress-Archive -Path target/${{ matrix.target }}/release/tun2proxy-bin.exe, target/${{ matrix.target }}/release/udpgw-server.exe, README.md, target/tun2proxy.h, target/${{ matrix.target }}/release/tun2proxy.dll, target/${{ matrix.target }}/release/wintun.dll -DestinationPath mypubdir4/tun2proxy-${{ matrix.target }}.zip elif [[ "${{ matrix.host_os }}" == "macos-latest" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.dylib + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy.h target/${{ matrix.target }}/release/libtun2proxy.dylib if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then ./build-aarch64-apple-ios.sh zip -r mypubdir4/tun2proxy-aarch64-apple-ios-xcframework.zip ./tun2proxy.xcframework/ @@ -104,7 +104,7 @@ jobs: zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/ fi elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then - zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy-ffi.h target/${{ matrix.target }}/release/libtun2proxy.so + zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy.h target/${{ matrix.target }}/release/libtun2proxy.so if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then ./build-android.sh cp ./tun2proxy-android-libs.zip ./mypubdir4/ diff --git a/build-aarch64-apple-ios-debug.sh b/build-aarch64-apple-ios-debug.sh index 7642758..6c97921 100755 --- a/build-aarch64-apple-ios-debug.sh +++ b/build-aarch64-apple-ios-debug.sh @@ -10,7 +10,7 @@ cargo build --target aarch64-apple-ios --features mimalloc echo "Generating includes..." mkdir -p target/include/ rm -rf target/include/* -cbindgen --config cbindgen.toml -l C --cpp-compat -o target/include/tun2proxy.h +cbindgen --config cbindgen.toml -o target/include/tun2proxy.h cat > target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < target/include/tun2proxy.modulemap < Date: Thu, 21 Nov 2024 14:15:37 +0800 Subject: [PATCH 148/196] Bump version 0.6.5 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e9351ca..c426865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.4" +version = "0.6.5" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 987635d3dc201d9b2a279cde710e5dc6558a1b87 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 23 Nov 2024 22:10:53 +0800 Subject: [PATCH 149/196] Contributors in README --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 06f0601..f737745 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ +[![tun2proxy](https://socialify.git.ci/tun2proxy/tun2proxy/image?description=1&language=1&name=1&stargazers=1&theme=Light)](https://github.com/tun2proxy/tun2proxy) + # tun2proxy A tunnel interface for HTTP and SOCKS proxies on Linux, Android, macOS, iOS and Windows. [![Crates.io](https://img.shields.io/crates/v/tun2proxy.svg)](https://crates.io/crates/tun2proxy) -![tun2proxy](https://docs.rs/tun2proxy/badge.svg) +[![tun2proxy](https://docs.rs/tun2proxy/badge.svg)](https://docs.rs/tun2proxy) [![Documentation](https://img.shields.io/badge/docs-release-brightgreen.svg?style=flat)](https://docs.rs/tun2proxy) [![Download](https://img.shields.io/crates/d/tun2proxy.svg)](https://crates.io/crates/tun2proxy) [![License](https://img.shields.io/crates/l/tun2proxy.svg?style=flat)](https://github.com/tun2proxy/tun2proxy/blob/master/LICENSE) @@ -216,3 +218,10 @@ asked to open connections to IPv6 destinations. In such a case, you can disable either through `sysctl -w net.ipv6.conf.all.disable_ipv6=1` and `sysctl -w net.ipv6.conf.default.disable_ipv6=1` or through `ip -6 route del default`, which causes the `libc` resolver (and other software) to not issue DNS AAAA requests for IPv6 addresses. + +## Contributors ✨ +Thanks goes to these wonderful people: + + + + From d37cb44b6221c0b1b5e3841ead6eb98c4a8d920f Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:17:16 +0800 Subject: [PATCH 150/196] Fix #165 --- src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/args.rs b/src/args.rs index 85904db..33f160c 100644 --- a/src/args.rs +++ b/src/args.rs @@ -397,7 +397,7 @@ impl TryFrom<&str> for ArgProxy { let mut url_host = String::from(host); let e = format!("`{s}` does not contain a port"); - let port = url.port().ok_or(Error::from(&e))?; + let port = url.port_or_known_default().ok_or(Error::from(&e))?; url_host.push(':'); url_host.push_str(port.to_string().as_str()); From 46bf4434ef4dc6eb29950aacbdeff0c16bc733c2 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:28:03 +0800 Subject: [PATCH 151/196] Bump version 0.6.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index c426865..a2285cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.5" +version = "0.6.6" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 7a7293effd893dcf9e94eeb1e10bdede4a499000 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 26 Nov 2024 12:50:07 +0800 Subject: [PATCH 152/196] Refine code --- src/args.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/args.rs b/src/args.rs index 33f160c..38c2a09 100644 --- a/src/args.rs +++ b/src/args.rs @@ -395,17 +395,11 @@ impl TryFrom<&str> for ArgProxy { let e = format!("`{s}` does not contain a host"); let host = url.host_str().ok_or(Error::from(e))?; - let mut url_host = String::from(host); let e = format!("`{s}` does not contain a port"); let port = url.port_or_known_default().ok_or(Error::from(&e))?; - url_host.push(':'); - url_host.push_str(port.to_string().as_str()); - let e = format!("`{host}` could not be resolved"); - let mut addr_iter = url_host.to_socket_addrs().map_err(|_| Error::from(&e))?; - - let e = format!("`{host}` does not resolve to a usable IP address"); - let addr = addr_iter.next().ok_or(Error::from(&e))?; + let e2 = format!("`{host}` does not resolve to a usable IP address"); + let addr = (host, port).to_socket_addrs()?.next().ok_or(Error::from(&e2))?; let credentials = if url.username() == "" && url.password().is_none() { None From 724557b30e5199febf3ff96f667836856da6d726 Mon Sep 17 00:00:00 2001 From: Paper-Dragon <2678885646@qq.com> Date: Tue, 26 Nov 2024 13:12:04 +0800 Subject: [PATCH 153/196] docker-compose.yaml support (#166) --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f737745..3f320e2 100644 --- a/README.md +++ b/README.md @@ -172,7 +172,8 @@ Currently, tun2proxy supports HTTP, SOCKS4/SOCKS4a and SOCKS5. A proxy is suppli URL format. For example, an HTTP proxy at `1.2.3.4:3128` with a username of `john.doe` and a password of `secret` is supplied as `--proxy http://john.doe:secret@1.2.3.4:3128`. This works analogously to curl's `--proxy` argument. -## Docker Support +## Container Support +### Docker Tun2proxy can serve as a proxy for other Docker containers. To make use of that feature, first build the image: ```bash @@ -197,6 +198,36 @@ docker run -it \ --network "container:tun2proxy" \ ubuntu:latest ``` +### Docker Compose + +The above docker command is written into a `docker-compose.yaml` file. + +```yaml +services: + tun2proxy: + volumes: + - /dev/net/tun:/dev/net/tun + sysctls: + - net.ipv6.conf.default.disable_ipv6=0 + cap_add: + - NET_ADMIN + container_name: tun2proxy + image: ghcr.io/tun2proxy/tun2proxy:latest + command: --proxy proto://[username[:password]@]host:port + alpine: + stdin_open: true + tty: true + network_mode: container:tun2proxy + image: alpine:latest + command: apk add curl && curl ifconfig.icu && sleep 10 +``` + +run compose file + +```bash +docker compose up -d tun2proxy +docker compose up alpine +``` ## Configuration Tips ### DNS From a01de17b365bc6343dbff4051490f0b3b974e463 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:32:39 +0800 Subject: [PATCH 154/196] minor changes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3f320e2..5c9b591 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ docker run -it \ ``` ### Docker Compose -The above docker command is written into a `docker-compose.yaml` file. +Write a `docker-compose.yaml` file with the following content: ```yaml services: @@ -222,7 +222,7 @@ services: command: apk add curl && curl ifconfig.icu && sleep 10 ``` -run compose file +Then run the compose file ```bash docker compose up -d tun2proxy From 258637a52ea663e832a8cb380a78d5774140c8ab Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:17:51 +0800 Subject: [PATCH 155/196] upgrade dependencies --- Cargo.toml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a2285cb..1f46a49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,14 +26,16 @@ ctrlc2 = { version = "3", features = ["tokio", "termination"] } digest_auth = "0.3" dotenvy = "0.15" env_logger = "0.11" -hashlink = "0.9" +hashlink = "0.10" hickory-proto = "0.24" httparse = "1" ipstack = { version = "0.1" } log = { version = "0.4", features = ["std"] } mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" -socks5-impl = { version = "0.5" } +socks5-impl = { version = "0.6", default-features = false, features = [ + "tokio", +] } thiserror = "2" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" @@ -43,6 +45,9 @@ udp-stream = { version = "0.0.12", default-features = false } unicase = "2" url = "2" +[build-dependencies] +serde_json = "1" + [target.'cfg(target_os="linux")'.dependencies] serde = { version = "1", features = ["derive"] } bincode = "1" @@ -62,9 +67,6 @@ nix = { version = "0.29", default-features = false, features = [ [target.'cfg(target_os = "windows")'.dependencies] windows-service = "0.7" -[build-dependencies] -serde_json = "1" - [[bin]] name = "tun2proxy-bin" path = "src/bin/main.rs" From 4d4a0ce85cc3222d34f9dd7cf38ec05548cb189b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 26 Dec 2024 20:38:13 +0800 Subject: [PATCH 156/196] minor changes --- Cargo.toml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f46a49..f203a2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,15 @@ rust-version = "1.80" [lib] crate-type = ["staticlib", "cdylib", "lib"] +[[bin]] +name = "tun2proxy-bin" +path = "src/bin/main.rs" + +[[bin]] +name = "udpgw-server" +path = "src/bin/udpgw_server.rs" +required-features = ["udpgw"] + [features] default = ["udpgw"] udpgw = [] @@ -67,14 +76,5 @@ nix = { version = "0.29", default-features = false, features = [ [target.'cfg(target_os = "windows")'.dependencies] windows-service = "0.7" -[[bin]] -name = "tun2proxy-bin" -path = "src/bin/main.rs" - -[[bin]] -name = "udpgw-server" -path = "src/bin/udpgw_server.rs" -required-features = ["udpgw"] - [profile.release] strip = "symbols" From ea5ee834dbbfa6cf373b14fd77caee9946e22403 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 27 Dec 2024 16:59:11 +0800 Subject: [PATCH 157/196] Bump version 0.6.7 --- .github/workflows/publish-exe.yml | 8 ++++++++ Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 628baf3..c9730b9 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -15,6 +15,7 @@ jobs: attestations: write strategy: + fail-fast: false matrix: target: - x86_64-unknown-linux-gnu @@ -77,6 +78,7 @@ jobs: fi - name: Build + if: ${{ !cancelled() }} shell: bash run: | if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then @@ -112,20 +114,26 @@ jobs: fi - name: Upload artifacts + if: ${{ !cancelled() }} uses: actions/upload-artifact@v4 with: name: bin-${{ matrix.target }} path: mypubdir4/* - name: Generate artifact attestation + if: ${{ !cancelled() }} uses: actions/attest-build-provenance@v1 with: subject-path: mypubdir4/* - name: Publish + if: ${{ !cancelled() }} uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: mypubdir4/* + - name: Abort on error + if: ${{ failure() }} + run: echo "Some of jobs failed" && false diff --git a/Cargo.toml b/Cargo.toml index f203a2e..171b742 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.6" +version = "0.6.7" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 2a8e31225c8369b4a0ccd346c45288c04fb0019d Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 2 Jan 2025 17:05:32 +0800 Subject: [PATCH 158/196] refine clap::Parser --- src/args.rs | 3 +-- src/bin/udpgw_server.rs | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/args.rs b/src/args.rs index 38c2a09..71a85f2 100644 --- a/src/args.rs +++ b/src/args.rs @@ -181,8 +181,7 @@ impl Default for Args { impl Args { #[allow(clippy::let_and_return)] pub fn parse_args() -> Self { - use clap::Parser; - let args = Self::parse(); + let args = ::parse(); #[cfg(target_os = "linux")] if !args.setup && args.tun.is_none() { eprintln!("Missing required argument, '--tun' must present when '--setup' is not used."); diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index e78fbb9..9cf144a 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -54,10 +54,8 @@ pub struct UdpGwArgs { } impl UdpGwArgs { - #[allow(clippy::let_and_return)] pub fn parse_args() -> Self { - use clap::Parser; - Self::parse() + ::parse() } } From 7136e2a20c63526f217c1ce9ca05f4ced26b0bba Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 2 Jan 2025 23:18:40 +0800 Subject: [PATCH 159/196] refactor desktop_run_async --- Cargo.toml | 2 +- cbindgen.toml | 1 + src/bin/main.rs | 3 +- src/desktop_api.rs | 116 ++++++++++++++++++++++++++++----------------- src/mobile_api.rs | 101 +++++++++++++++++++-------------------- src/win_svc.rs | 2 +- 6 files changed, 127 insertions(+), 98 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 171b742..e93e99b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.6.7" +version = "0.7.0" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" diff --git a/cbindgen.toml b/cbindgen.toml index 8354c6c..10e4235 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -3,6 +3,7 @@ cpp_compat = true [export] include = [ + "tun2proxy_run_with_cli", "tun2proxy_with_fd_run", "tun2proxy_with_name_run", "tun2proxy_with_name_stop", diff --git a/src/bin/main.rs b/src/bin/main.rs index e39b7b4..d5b1ebe 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,3 +1,4 @@ +use tun::DEFAULT_MTU as MTU; use tun2proxy::{Args, BoxError}; fn main() -> Result<(), BoxError> { @@ -49,7 +50,7 @@ async fn main_async(args: Args) -> Result<(), BoxError> { } unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - if let Err(err) = tun2proxy::desktop_run_async(args, shutdown_token).await { + if let Err(err) = tun2proxy::desktop_run_async(args, MTU, false, shutdown_token).await { log::error!("main loop error: {}", err); } } diff --git a/src/desktop_api.rs b/src/desktop_api.rs index 17428fb..ed8dbb5 100644 --- a/src/desktop_api.rs +++ b/src/desktop_api.rs @@ -5,7 +5,6 @@ use crate::{ ArgVerbosity, Args, }; use std::os::raw::{c_char, c_int}; -use tproxy_config::{TproxyArgs, TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; use tun::{AbstractDevice, DEFAULT_MTU as MTU}; static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); @@ -29,6 +28,42 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( _root_privilege: bool, verbosity: ArgVerbosity, ) -> c_int { + let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy = ArgProxy::try_from(proxy_url).unwrap(); + let tun = std::ffi::CStr::from_ptr(tun).to_str().unwrap().to_string(); + + let mut args = Args::default(); + if let Ok(bypass) = std::ffi::CStr::from_ptr(bypass).to_str() { + args.bypass(bypass.parse().unwrap()); + } + args.proxy(proxy).tun(tun).dns(dns_strategy).verbosity(verbosity); + + #[cfg(target_os = "linux")] + args.setup(_root_privilege); + + desktop_run(args) +} + +/// # Safety +/// Run the tun2proxy component with command line arguments +/// Parameters: +/// - cli_args: The command line arguments, +/// e.g. `tun2proxy-bin --setup --proxy socks5://127.0.0.1:1080 --bypass 98.76.54.0/24 --dns over-tcp --verbosity trace` +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char) -> c_int { + let Ok(cli_args) = std::ffi::CStr::from_ptr(cli_args).to_str() else { + return -5; + }; + let args = ::parse_from(cli_args.split_whitespace()); + desktop_run(args) +} + +pub fn desktop_run(args: Args) -> c_int { + log::set_max_level(args.verbosity.into()); + if let Err(err) = log::set_boxed_logger(Box::::default()) { + log::warn!("set logger error: {}", err); + } + let shutdown_token = tokio_util::sync::CancellationToken::new(); { if let Ok(mut lock) = TUN_QUIT.lock() { @@ -41,51 +76,38 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( } } - log::set_max_level(verbosity.into()); - if let Err(err) = log::set_boxed_logger(Box::::default()) { - log::warn!("set logger error: {}", err); - } - - let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); - let proxy = ArgProxy::try_from(proxy_url).unwrap(); - let tun = std::ffi::CStr::from_ptr(tun).to_str().unwrap().to_string(); - - let mut args = Args::default(); - args.proxy(proxy).tun(tun).dns(dns_strategy).verbosity(verbosity); - - #[cfg(target_os = "linux")] - args.setup(_root_privilege); - - if let Ok(bypass) = std::ffi::CStr::from_ptr(bypass).to_str() { - args.bypass(bypass.parse().unwrap()); - } - - let main_loop = async move { - if let Err(err) = desktop_run_async(args, shutdown_token).await { + let Ok(rt) = tokio::runtime::Builder::new_multi_thread().enable_all().build() else { + return -3; + }; + let res = rt.block_on(async move { + if let Err(err) = desktop_run_async(args, MTU, false, shutdown_token).await { log::error!("main loop error: {}", err); return Err(err); } Ok(()) - }; - - let exit_code = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { - Err(_e) => -3, - Ok(rt) => match rt.block_on(main_loop) { - Ok(_) => 0, - Err(_e) => -4, - }, - }; - - exit_code + }); + match res { + Ok(_) => 0, + Err(_) => -4, + } } /// Run the tun2proxy component with some arguments. -pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::CancellationToken) -> std::io::Result<()> { - let bypass_ips = args.bypass.clone(); - +pub async fn desktop_run_async( + args: Args, + tun_mtu: u16, + _packet_information: bool, + shutdown_token: tokio_util::sync::CancellationToken, +) -> std::io::Result<()> { let mut tun_config = tun::Configuration::default(); - tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(MTU).up(); - tun_config.destination(TUN_GATEWAY); + + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] + { + use tproxy_config::{TUN_GATEWAY, TUN_IPV4, TUN_NETMASK}; + tun_config.address(TUN_IPV4).netmask(TUN_NETMASK).mtu(tun_mtu).up(); + tun_config.destination(TUN_GATEWAY); + } + #[cfg(unix)] if let Some(fd) = args.tun_fd { tun_config.raw_fd(fd); @@ -112,11 +134,17 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can cfg.device_guid(12324323423423434234_u128); }); + #[cfg(any(target_os = "ios", target_os = "macos"))] + tun_config.platform_config(|cfg| { + cfg.packet_information(_packet_information); + }); + + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] #[allow(unused_variables)] - let mut tproxy_args = TproxyArgs::new() + let mut tproxy_args = tproxy_config::TproxyArgs::new() .tun_dns(args.dns_addr) .proxy_addr(args.proxy.addr) - .bypass_ips(&bypass_ips) + .bypass_ips(&args.bypass) .ipv6_default_route(args.ipv6_enabled); #[allow(unused_mut, unused_assignments, unused_variables)] @@ -124,12 +152,14 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can let device = tun::create_as_async(&tun_config)?; + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] if let Ok(tun_name) = device.tun_name() { tproxy_args = tproxy_args.tun_name(&tun_name); } // TproxyState implements the Drop trait to restore network configuration, // so we need to assign it to a variable, even if it is not used. + #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] let mut _restore: Option = None; #[cfg(target_os = "linux")] @@ -166,10 +196,8 @@ pub async fn desktop_run_async(args: Args, shutdown_token: tokio_util::sync::Can } } - let join_handle = tokio::spawn(crate::run(device, MTU, args, shutdown_token)); - join_handle.await.map_err(std::io::Error::from)??; - - Ok::<(), std::io::Error>(()) + let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); + Ok(join_handle.await.map_err(std::io::Error::from)??) } /// # Safety diff --git a/src/mobile_api.rs b/src/mobile_api.rs index a32f68d..3f885d5 100644 --- a/src/mobile_api.rs +++ b/src/mobile_api.rs @@ -8,68 +8,67 @@ static TUN_QUIT: std::sync::Mutex> = /// Dummy function to make the build pass. #[doc(hidden)] #[cfg(not(target_os = "macos"))] -pub async fn desktop_run_async(_: Args, _: tokio_util::sync::CancellationToken) -> std::io::Result<()> { +pub async fn desktop_run_async(_: Args, _: u16, _: bool, _: tokio_util::sync::CancellationToken) -> std::io::Result<()> { Ok(()) } +pub async fn mobile_run_async( + args: Args, + tun_mtu: u16, + _packet_information: bool, + shutdown_token: tokio_util::sync::CancellationToken, +) -> std::io::Result<()> { + let mut config = tun::Configuration::default(); + + #[cfg(unix)] + if let Some(fd) = args.tun_fd { + config.raw_fd(fd); + if let Some(v) = args.close_fd_on_drop { + config.close_fd_on_drop(v); + }; + } else if let Some(ref tun) = args.tun { + config.tun_name(tun); + } + #[cfg(windows)] + if let Some(ref tun) = args.tun { + config.tun_name(tun); + } + + #[cfg(any(target_os = "ios", target_os = "macos"))] + config.platform_config(|config| { + config.packet_information(_packet_information); + }); + + let device = tun::create_as_async(&config).map_err(std::io::Error::from)?; + let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); + + Ok(join_handle.await.map_err(std::io::Error::from)??) +} + pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int { let shutdown_token = tokio_util::sync::CancellationToken::new(); - { - if let Ok(mut lock) = TUN_QUIT.lock() { - if lock.is_some() { - log::error!("tun2proxy already started"); - return -1; - } - *lock = Some(shutdown_token.clone()); - } else { - log::error!("failed to lock tun2proxy quit token"); - return -2; + if let Ok(mut lock) = TUN_QUIT.lock() { + if lock.is_some() { + log::error!("tun2proxy already started"); + return -1; } + *lock = Some(shutdown_token.clone()); + } else { + log::error!("failed to lock tun2proxy quit token"); + return -2; } - let block = async move { - let mut config = tun::Configuration::default(); - - #[cfg(unix)] - if let Some(fd) = args.tun_fd { - config.raw_fd(fd); - if let Some(v) = args.close_fd_on_drop { - config.close_fd_on_drop(v); - }; - } else if let Some(ref tun) = args.tun { - config.tun_name(tun); - } - #[cfg(windows)] - if let Some(ref tun) = args.tun { - config.tun_name(tun); - } - - #[cfg(any(target_os = "ios", target_os = "macos"))] - config.platform_config(|config| { - config.packet_information(_packet_information); - }); - - let device = tun::create_as_async(&config).map_err(std::io::Error::from)?; - let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); - - join_handle.await.map_err(std::io::Error::from)? + let Ok(rt) = tokio::runtime::Builder::new_multi_thread().enable_all().build() else { + log::error!("failed to create tokio runtime with"); + return -1; }; - - let exit_code = match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + match rt.block_on(mobile_run_async(args, tun_mtu, _packet_information, shutdown_token)) { + Ok(_) => 0, Err(e) => { - log::error!("failed to create tokio runtime with error: {:?}", e); - -1 + log::error!("failed to run tun2proxy with error: {:?}", e); + -2 } - Ok(rt) => match rt.block_on(block) { - Ok(_) => 0, - Err(e) => { - log::error!("failed to run tun2proxy with error: {:?}", e); - -2 - } - }, - }; - - exit_code + } } pub fn mobile_stop() -> c_int { diff --git a/src/win_svc.rs b/src/win_svc.rs index afca2f1..e775049 100644 --- a/src/win_svc.rs +++ b/src/win_svc.rs @@ -78,7 +78,7 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro } unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - if let Err(err) = crate::desktop_run_async(args, shutdown_token).await { + if let Err(err) = crate::desktop_run_async(args, tun::DEFAULT_MTU, false, shutdown_token).await { log::error!("main loop error: {}", err); } Ok::<(), crate::Error>(()) From e933e5d4c032b1319f111b263e3091c7d7d7d485 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 3 Jan 2025 00:41:29 +0800 Subject: [PATCH 160/196] iOS & Android testing suits --- .github/workflows/rust.yml | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 13992eb..693d55c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -47,6 +47,52 @@ jobs: if: ${{ failure() }} run: echo "Some of jobs failed" && false + build_n_test_android: + strategy: + fail-fast: false + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Install cargo ndk and rust compiler for android target + if: ${{ !cancelled() }} + run: | + cargo install --locked cargo-ndk + rustup target add x86_64-linux-android + - name: clippy + if: ${{ !cancelled() }} + run: cargo ndk -t x86_64 clippy --all-features -- -D warnings + - name: Build + if: ${{ !cancelled() }} + run: | + cargo ndk -t x86_64 rustc --verbose --all-features --lib --crate-type=cdylib + - name: Abort on error + if: ${{ failure() }} + run: echo "Android build job failed" && false + + build_n_test_ios: + strategy: + fail-fast: false + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + - name: Install cargo lipo and rust compiler for ios target + if: ${{ !cancelled() }} + run: | + cargo install --locked cargo-lipo + rustup target add x86_64-apple-ios aarch64-apple-ios + - name: clippy + if: ${{ !cancelled() }} + run: cargo clippy --target x86_64-apple-ios --all-features -- -D warnings + - name: Build + if: ${{ !cancelled() }} + run: | + cargo lipo --verbose --all-features + - name: Abort on error + if: ${{ failure() }} + run: echo "iOS build job failed" && false + semver: name: Check semver strategy: From 60348702649f0ec4a896e33f9f02fe02cbca9956 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 3 Jan 2025 01:37:40 +0800 Subject: [PATCH 161/196] rename desktop_run_async to general_run_async --- src/android.rs | 4 +- src/bin/main.rs | 2 +- src/{desktop_api.rs => general_api.rs} | 50 +++++++++------- src/lib.rs | 14 +---- src/{apple.rs => mobile.rs} | 9 +-- src/mobile_api.rs | 82 -------------------------- src/win_svc.rs | 2 +- 7 files changed, 37 insertions(+), 126 deletions(-) rename src/{desktop_api.rs => general_api.rs} (84%) rename src/{apple.rs => mobile.rs} (82%) delete mode 100644 src/mobile_api.rs diff --git a/src/android.rs b/src/android.rs index cca174c..21dd5eb 100644 --- a/src/android.rs +++ b/src/android.rs @@ -52,7 +52,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( .close_fd_on_drop(close_fd_on_drop) .dns(dns) .verbosity(verbosity); - crate::mobile_api::mobile_run(args, tun_mtu, false) + crate::general_api::general_run_for_api(args, tun_mtu, false) } /// # Safety @@ -60,7 +60,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( /// Shutdown tun2proxy #[no_mangle] pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNIEnv, _: JClass) -> jint { - crate::mobile_api::mobile_stop() + crate::general_api::tun2proxy_stop_internal() } fn get_java_string(env: &mut JNIEnv, string: &JString) -> Result { diff --git a/src/bin/main.rs b/src/bin/main.rs index d5b1ebe..e690f6c 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -50,7 +50,7 @@ async fn main_async(args: Args) -> Result<(), BoxError> { } unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - if let Err(err) = tun2proxy::desktop_run_async(args, MTU, false, shutdown_token).await { + if let Err(err) = tun2proxy::general_run_async(args, MTU, false, shutdown_token).await { log::error!("main loop error: {}", err); } } diff --git a/src/desktop_api.rs b/src/general_api.rs similarity index 84% rename from src/desktop_api.rs rename to src/general_api.rs index ed8dbb5..9385f48 100644 --- a/src/desktop_api.rs +++ b/src/general_api.rs @@ -1,11 +1,9 @@ -#![cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] - use crate::{ args::{ArgDns, ArgProxy}, ArgVerbosity, Args, }; use std::os::raw::{c_char, c_int}; -use tun::{AbstractDevice, DEFAULT_MTU as MTU}; +use tun::DEFAULT_MTU as MTU; static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); @@ -41,7 +39,7 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( #[cfg(target_os = "linux")] args.setup(_root_privilege); - desktop_run(args) + general_run_for_api(args, MTU, false) } /// # Safety @@ -55,45 +53,48 @@ pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char) -> return -5; }; let args = ::parse_from(cli_args.split_whitespace()); - desktop_run(args) + general_run_for_api(args, MTU, false) } -pub fn desktop_run(args: Args) -> c_int { +pub fn general_run_for_api(args: Args, tun_mtu: u16, packet_information: bool) -> c_int { log::set_max_level(args.verbosity.into()); if let Err(err) = log::set_boxed_logger(Box::::default()) { - log::warn!("set logger error: {}", err); + log::debug!("set logger error: {}", err); } let shutdown_token = tokio_util::sync::CancellationToken::new(); - { - if let Ok(mut lock) = TUN_QUIT.lock() { - if lock.is_some() { - return -1; - } - *lock = Some(shutdown_token.clone()); - } else { - return -2; + if let Ok(mut lock) = TUN_QUIT.lock() { + if lock.is_some() { + log::error!("tun2proxy already started"); + return -1; } + *lock = Some(shutdown_token.clone()); + } else { + log::error!("failed to lock tun2proxy quit token"); + return -2; } let Ok(rt) = tokio::runtime::Builder::new_multi_thread().enable_all().build() else { + log::error!("failed to create tokio runtime with"); return -3; }; - let res = rt.block_on(async move { - if let Err(err) = desktop_run_async(args, MTU, false, shutdown_token).await { + match rt.block_on(async move { + if let Err(err) = general_run_async(args, tun_mtu, packet_information, shutdown_token).await { log::error!("main loop error: {}", err); return Err(err); } Ok(()) - }); - match res { + }) { Ok(_) => 0, - Err(_) => -4, + Err(e) => { + log::error!("failed to run tun2proxy with error: {:?}", e); + -4 + } } } /// Run the tun2proxy component with some arguments. -pub async fn desktop_run_async( +pub async fn general_run_async( args: Args, tun_mtu: u16, _packet_information: bool, @@ -153,7 +154,8 @@ pub async fn desktop_run_async( let device = tun::create_as_async(&tun_config)?; #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - if let Ok(tun_name) = device.tun_name() { + if let Ok(tun_name) = tun::AbstractDevice::tun_name(&*device) { + // Above line is equivalent to: `use tun::AbstractDevice; if let Ok(tun_name) = device.tun_name() {` tproxy_args = tproxy_args.tun_name(&tun_name); } @@ -205,6 +207,10 @@ pub async fn desktop_run_async( /// Shutdown the tun2proxy component. #[no_mangle] pub unsafe extern "C" fn tun2proxy_with_name_stop() -> c_int { + tun2proxy_stop_internal() +} + +pub(crate) fn tun2proxy_stop_internal() -> c_int { if let Ok(mut lock) = TUN_QUIT.lock() { if let Some(shutdown_token) = lock.take() { shutdown_token.cancel(); diff --git a/src/lib.rs b/src/lib.rs index 1972b9c..f211110 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,25 +40,17 @@ pub use { #[global_allocator] static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; -#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))] -pub use desktop_api::desktop_run_async; - -#[cfg(any(target_os = "ios", target_os = "android"))] -pub use mobile_api::{desktop_run_async, mobile_run, mobile_stop}; - -#[cfg(target_os = "macos")] -pub use mobile_api::{mobile_run, mobile_stop}; +pub use general_api::general_run_async; mod android; -mod apple; mod args; -mod desktop_api; mod directions; mod dns; mod dump_logger; mod error; +mod general_api; mod http; -mod mobile_api; +mod mobile; mod no_proxy; mod proxy_handler; mod session_info; diff --git a/src/apple.rs b/src/mobile.rs similarity index 82% rename from src/apple.rs rename to src/mobile.rs index 155b101..3747d67 100644 --- a/src/apple.rs +++ b/src/mobile.rs @@ -27,11 +27,6 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( dns_strategy: ArgDns, verbosity: ArgVerbosity, ) -> c_int { - log::set_max_level(verbosity.into()); - if let Err(err) = log::set_boxed_logger(Box::::default()) { - log::warn!("failed to set logger: {:?}", err); - } - let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); let proxy = ArgProxy::try_from(proxy_url).unwrap(); @@ -42,7 +37,7 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( .dns(dns_strategy) .verbosity(verbosity); - crate::mobile_api::mobile_run(args, tun_mtu, packet_information) + crate::general_api::general_run_for_api(args, tun_mtu, packet_information) } /// # Safety @@ -50,5 +45,5 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( /// Shutdown the tun2proxy component. #[no_mangle] pub unsafe extern "C" fn tun2proxy_with_fd_stop() -> c_int { - crate::mobile_api::mobile_stop() + crate::general_api::tun2proxy_stop_internal() } diff --git a/src/mobile_api.rs b/src/mobile_api.rs deleted file mode 100644 index 3f885d5..0000000 --- a/src/mobile_api.rs +++ /dev/null @@ -1,82 +0,0 @@ -#![cfg(any(target_os = "ios", target_os = "android", target_os = "macos"))] - -use crate::Args; -use std::os::raw::c_int; - -static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); - -/// Dummy function to make the build pass. -#[doc(hidden)] -#[cfg(not(target_os = "macos"))] -pub async fn desktop_run_async(_: Args, _: u16, _: bool, _: tokio_util::sync::CancellationToken) -> std::io::Result<()> { - Ok(()) -} - -pub async fn mobile_run_async( - args: Args, - tun_mtu: u16, - _packet_information: bool, - shutdown_token: tokio_util::sync::CancellationToken, -) -> std::io::Result<()> { - let mut config = tun::Configuration::default(); - - #[cfg(unix)] - if let Some(fd) = args.tun_fd { - config.raw_fd(fd); - if let Some(v) = args.close_fd_on_drop { - config.close_fd_on_drop(v); - }; - } else if let Some(ref tun) = args.tun { - config.tun_name(tun); - } - #[cfg(windows)] - if let Some(ref tun) = args.tun { - config.tun_name(tun); - } - - #[cfg(any(target_os = "ios", target_os = "macos"))] - config.platform_config(|config| { - config.packet_information(_packet_information); - }); - - let device = tun::create_as_async(&config).map_err(std::io::Error::from)?; - let join_handle = tokio::spawn(crate::run(device, tun_mtu, args, shutdown_token)); - - Ok(join_handle.await.map_err(std::io::Error::from)??) -} - -pub fn mobile_run(args: Args, tun_mtu: u16, _packet_information: bool) -> c_int { - let shutdown_token = tokio_util::sync::CancellationToken::new(); - if let Ok(mut lock) = TUN_QUIT.lock() { - if lock.is_some() { - log::error!("tun2proxy already started"); - return -1; - } - *lock = Some(shutdown_token.clone()); - } else { - log::error!("failed to lock tun2proxy quit token"); - return -2; - } - - let Ok(rt) = tokio::runtime::Builder::new_multi_thread().enable_all().build() else { - log::error!("failed to create tokio runtime with"); - return -1; - }; - match rt.block_on(mobile_run_async(args, tun_mtu, _packet_information, shutdown_token)) { - Ok(_) => 0, - Err(e) => { - log::error!("failed to run tun2proxy with error: {:?}", e); - -2 - } - } -} - -pub fn mobile_stop() -> c_int { - if let Ok(mut lock) = TUN_QUIT.lock() { - if let Some(shutdown_token) = lock.take() { - shutdown_token.cancel(); - return 0; - } - } - -1 -} diff --git a/src/win_svc.rs b/src/win_svc.rs index e775049..ac87417 100644 --- a/src/win_svc.rs +++ b/src/win_svc.rs @@ -78,7 +78,7 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro } unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - if let Err(err) = crate::desktop_run_async(args, tun::DEFAULT_MTU, false, shutdown_token).await { + if let Err(err) = crate::general_run_async(args, tun::DEFAULT_MTU, false, shutdown_token).await { log::error!("main loop error: {}", err); } Ok::<(), crate::Error>(()) From bac54ec56c844267768dd257252c5e622a3f10bd Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 3 Jan 2025 02:26:51 +0800 Subject: [PATCH 162/196] Bump version 0.7.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e93e99b..c2a9df3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.0" +version = "0.7.1" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 51de01854bf9429ba420d91479753fa7ad3a64a3 Mon Sep 17 00:00:00 2001 From: Mostafa Kazemi <32548292+mkay1375@users.noreply.github.com> Date: Fri, 3 Jan 2025 06:30:19 +0330 Subject: [PATCH 163/196] Fix typo in comment (#178) --- src/http.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/http.rs b/src/http.rs index 2b5f491..6c5f0d6 100644 --- a/src/http.rs +++ b/src/http.rs @@ -252,7 +252,7 @@ impl HttpConnection { } // The HTTP/1.1 expected to be keep alive waiting for the next frame so, we must - // compute the lenght of the response in order to detect the next frame (response) + // compute the length of the response in order to detect the next frame (response) // [RFC-9112](https://datatracker.ietf.org/doc/html/rfc9112#body.content-length) // Transfer-Encoding isn't supported yet From ecd1ab80bfa0f94c743e423f5e7cbf062c2a5478 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:10:14 +0800 Subject: [PATCH 164/196] base64 removed --- Cargo.toml | 2 +- src/http.rs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c2a9df3..e7edc93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ udpgw = [] [dependencies] async-trait = "0.1" -base64 = { version = "0.22" } +base64easy = "0.1" chrono = "0.4" clap = { version = "4", features = ["derive", "wrap_help", "color"] } ctrlc2 = { version = "3", features = ["tokio", "termination"] } diff --git a/src/http.rs b/src/http.rs index 6c5f0d6..0b6adf5 100644 --- a/src/http.rs +++ b/src/http.rs @@ -4,7 +4,6 @@ use crate::{ proxy_handler::{ProxyHandler, ProxyHandlerManager}, session_info::{IpProtocol, SessionInfo}, }; -use base64::Engine; use httparse::Response; use socks5_impl::protocol::UserKey; use std::{ @@ -141,8 +140,7 @@ impl HttpConnection { .extend(format!("{}: {}\r\n", PROXY_AUTHORIZATION, response.to_header_string()).as_bytes()); } AuthenticationScheme::Basic => { - let cred = format!("{}:{}", credentials.username, credentials.password); - let auth_b64 = base64::engine::general_purpose::STANDARD.encode(cred); + let auth_b64 = base64easy::encode(credentials.to_string(), base64easy::EngineKind::Standard); self.server_outbuf .extend(format!("{}: Basic {}\r\n", PROXY_AUTHORIZATION, auth_b64).as_bytes()); } From e939f5f3dcfe4c2671fbb1ba8bf39c9d23c5fb45 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:15:05 +0800 Subject: [PATCH 165/196] remove mod mobile --- cbindgen.toml | 3 +-- src/general_api.rs | 48 +++++++++++++++++++++++++++++++++++++++------ src/lib.rs | 1 - src/mobile.rs | 49 ---------------------------------------------- 4 files changed, 43 insertions(+), 58 deletions(-) delete mode 100644 src/mobile.rs diff --git a/cbindgen.toml b/cbindgen.toml index 10e4235..50ea65f 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -6,8 +6,7 @@ include = [ "tun2proxy_run_with_cli", "tun2proxy_with_fd_run", "tun2proxy_with_name_run", - "tun2proxy_with_name_stop", - "tun2proxy_with_fd_stop", + "tun2proxy_stop", "tun2proxy_set_log_callback", "tun2proxy_set_traffic_status_callback", ] diff --git a/src/general_api.rs b/src/general_api.rs index 9385f48..d4bc0ce 100644 --- a/src/general_api.rs +++ b/src/general_api.rs @@ -2,8 +2,7 @@ use crate::{ args::{ArgDns, ArgProxy}, ArgVerbosity, Args, }; -use std::os::raw::{c_char, c_int}; -use tun::DEFAULT_MTU as MTU; +use std::os::raw::{c_char, c_int, c_ushort}; static TUN_QUIT: std::sync::Mutex> = std::sync::Mutex::new(None); @@ -39,7 +38,42 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( #[cfg(target_os = "linux")] args.setup(_root_privilege); - general_run_for_api(args, MTU, false) + general_run_for_api(args, tun::DEFAULT_MTU, false) +} + +/// # Safety +/// +/// Run the tun2proxy component with some arguments. +/// Parameters: +/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" +/// - tun_fd: the tun file descriptor, it will be owned by tun2proxy +/// - close_fd_on_drop: whether close the tun_fd on drop +/// - packet_information: indicates whether exists packet information in packet from TUN device +/// - tun_mtu: the tun mtu +/// - dns_strategy: the dns strategy, see ArgDns enum +/// - verbosity: the verbosity level, see ArgVerbosity enum +#[cfg(unix)] +#[no_mangle] +pub unsafe extern "C" fn tun2proxy_with_fd_run( + proxy_url: *const c_char, + tun_fd: c_int, + close_fd_on_drop: bool, + packet_information: bool, + tun_mtu: c_ushort, + dns_strategy: ArgDns, + verbosity: ArgVerbosity, +) -> c_int { + let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy = ArgProxy::try_from(proxy_url).unwrap(); + + let mut args = Args::default(); + args.proxy(proxy) + .tun_fd(Some(tun_fd)) + .close_fd_on_drop(close_fd_on_drop) + .dns(dns_strategy) + .verbosity(verbosity); + + general_run_for_api(args, tun_mtu, packet_information) } /// # Safety @@ -47,13 +81,15 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( /// Parameters: /// - cli_args: The command line arguments, /// e.g. `tun2proxy-bin --setup --proxy socks5://127.0.0.1:1080 --bypass 98.76.54.0/24 --dns over-tcp --verbosity trace` +/// - tun_mtu: The MTU of the TUN device, e.g. 1500 +/// - packet_information: Whether exists packet information in packet from TUN device #[no_mangle] -pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char) -> c_int { +pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char, tun_mtu: c_ushort, packet_information: bool) -> c_int { let Ok(cli_args) = std::ffi::CStr::from_ptr(cli_args).to_str() else { return -5; }; let args = ::parse_from(cli_args.split_whitespace()); - general_run_for_api(args, MTU, false) + general_run_for_api(args, tun_mtu, packet_information) } pub fn general_run_for_api(args: Args, tun_mtu: u16, packet_information: bool) -> c_int { @@ -206,7 +242,7 @@ pub async fn general_run_async( /// /// Shutdown the tun2proxy component. #[no_mangle] -pub unsafe extern "C" fn tun2proxy_with_name_stop() -> c_int { +pub unsafe extern "C" fn tun2proxy_stop() -> c_int { tun2proxy_stop_internal() } diff --git a/src/lib.rs b/src/lib.rs index f211110..40c6774 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,6 @@ mod dump_logger; mod error; mod general_api; mod http; -mod mobile; mod no_proxy; mod proxy_handler; mod session_info; diff --git a/src/mobile.rs b/src/mobile.rs deleted file mode 100644 index 3747d67..0000000 --- a/src/mobile.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![cfg(any(target_os = "android", target_os = "ios", target_os = "macos"))] - -use crate::{ - args::{ArgDns, ArgProxy}, - ArgVerbosity, Args, -}; -use std::os::raw::{c_char, c_int, c_ushort}; - -/// # Safety -/// -/// Run the tun2proxy component with some arguments. -/// Parameters: -/// - proxy_url: the proxy url, e.g. "socks5://127.0.0.1:1080" -/// - tun_fd: the tun file descriptor, it will be owned by tun2proxy -/// - close_fd_on_drop: whether close the tun_fd on drop -/// - packet_information: whether exists packet information in tun_fd -/// - tun_mtu: the tun mtu -/// - dns_strategy: the dns strategy, see ArgDns enum -/// - verbosity: the verbosity level, see ArgVerbosity enum -#[no_mangle] -pub unsafe extern "C" fn tun2proxy_with_fd_run( - proxy_url: *const c_char, - tun_fd: c_int, - close_fd_on_drop: bool, - packet_information: bool, - tun_mtu: c_ushort, - dns_strategy: ArgDns, - verbosity: ArgVerbosity, -) -> c_int { - let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); - let proxy = ArgProxy::try_from(proxy_url).unwrap(); - - let mut args = Args::default(); - args.proxy(proxy) - .tun_fd(Some(tun_fd)) - .close_fd_on_drop(close_fd_on_drop) - .dns(dns_strategy) - .verbosity(verbosity); - - crate::general_api::general_run_for_api(args, tun_mtu, packet_information) -} - -/// # Safety -/// -/// Shutdown the tun2proxy component. -#[no_mangle] -pub unsafe extern "C" fn tun2proxy_with_fd_stop() -> c_int { - crate::general_api::tun2proxy_stop_internal() -} From 8ba2c1a2b73e934c5033b3e638895f3f51f5cc5e Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:30:41 +0800 Subject: [PATCH 166/196] Bump version 0.7.2 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e7edc93..0bc479f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.1" +version = "0.7.2" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From f8c902b61c8667bbd5b8c962014c1133f615f7a7 Mon Sep 17 00:00:00 2001 From: Ahmed Elsayed Date: Tue, 7 Jan 2025 15:03:25 +0200 Subject: [PATCH 167/196] use shlex instead of split whitespaces. (#179) --- Cargo.toml | 1 + src/general_api.rs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 0bc479f..58ca7ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ ipstack = { version = "0.1" } log = { version = "0.4", features = ["std"] } mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" +shlex = "1.3.0" socks5-impl = { version = "0.6", default-features = false, features = [ "tokio", ] } diff --git a/src/general_api.rs b/src/general_api.rs index d4bc0ce..665026e 100644 --- a/src/general_api.rs +++ b/src/general_api.rs @@ -88,7 +88,11 @@ pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char, tu let Ok(cli_args) = std::ffi::CStr::from_ptr(cli_args).to_str() else { return -5; }; - let args = ::parse_from(cli_args.split_whitespace()); + let Some(args) = shlex::split(cli_args) else { + log::error!("Failed to split CLI arguments"); + return -6; + }; + let args = ::parse_from(args); general_run_for_api(args, tun_mtu, packet_information) } From 04db15f5532ad53d88091f82994a2da8fb9450a1 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:15:44 +0800 Subject: [PATCH 168/196] Bump version 0.7.3 --- Cargo.toml | 2 +- src/general_api.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 58ca7ac..8b71fb7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.2" +version = "0.7.3" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" diff --git a/src/general_api.rs b/src/general_api.rs index 665026e..c877fd4 100644 --- a/src/general_api.rs +++ b/src/general_api.rs @@ -86,6 +86,7 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( #[no_mangle] pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char, tun_mtu: c_ushort, packet_information: bool) -> c_int { let Ok(cli_args) = std::ffi::CStr::from_ptr(cli_args).to_str() else { + log::error!("Failed to convert CLI arguments to string"); return -5; }; let Some(args) = shlex::split(cli_args) else { From 5287bef3c0b1c7d14af6c242a5a5392e3697cd8d Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 10 Jan 2025 18:43:55 +0800 Subject: [PATCH 169/196] PI issues for macOS --- src/bin/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index e690f6c..0ed98c7 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,4 +1,3 @@ -use tun::DEFAULT_MTU as MTU; use tun2proxy::{Args, BoxError}; fn main() -> Result<(), BoxError> { @@ -50,7 +49,7 @@ async fn main_async(args: Args) -> Result<(), BoxError> { } unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - if let Err(err) = tun2proxy::general_run_async(args, MTU, false, shutdown_token).await { + if let Err(err) = tun2proxy::general_run_async(args, tun::DEFAULT_MTU, cfg!(target_os = "macos"), shutdown_token).await { log::error!("main loop error: {}", err); } } From 6b038c2a80a06b493a4d0ebab74899e5fc0407ed Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:09:37 +0800 Subject: [PATCH 170/196] Bump version 0.7.4 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8b71fb7..993b959 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.3" +version = "0.7.4" edition = "2021" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From c5d907551ba1765ae32ec8176e42e1935ef15eba Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 12 Feb 2025 20:37:49 +0800 Subject: [PATCH 171/196] ubuntu-20.04 used in publish script --- .github/workflows/publish-exe.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index c9730b9..31312e6 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -34,7 +34,7 @@ jobs: include: - target: x86_64-unknown-linux-gnu - host_os: ubuntu-latest + host_os: ubuntu-20.04 - target: x86_64-unknown-linux-musl host_os: ubuntu-latest - target: i686-unknown-linux-musl @@ -73,7 +73,7 @@ jobs: rustup target add ${{ matrix.target }} fi cargo install cbindgen - if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then + if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-20.04" ]]; then sudo .github/workflows/install-cross.sh fi @@ -81,7 +81,7 @@ jobs: if: ${{ !cancelled() }} shell: bash run: | - if [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then + if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-20.04" ]]; then cross build --all-features --release --target ${{ matrix.target }} else if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then @@ -105,7 +105,7 @@ jobs: ./build-apple.sh zip -r mypubdir4/tun2proxy-apple-xcframework.zip ./tun2proxy.xcframework/ fi - elif [[ "${{ matrix.host_os }}" == "ubuntu-latest" ]]; then + elif [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" ]]; then zip -j mypubdir4/tun2proxy-${{ matrix.target }}.zip target/${{ matrix.target }}/release/tun2proxy-bin target/${{ matrix.target }}/release/udpgw-server README.md target/tun2proxy.h target/${{ matrix.target }}/release/libtun2proxy.so if [[ "${{ matrix.target }}" == "x86_64-unknown-linux-gnu" ]]; then ./build-android.sh From 9a018f2393b727ffd52720cc7ae61b06a72f65d7 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 18 Feb 2025 13:23:04 +0800 Subject: [PATCH 172/196] update ipstack --- Cargo.toml | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 993b959..691c15d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ env_logger = "0.11" hashlink = "0.10" hickory-proto = "0.24" httparse = "1" -ipstack = { version = "0.1" } +ipstack = { version = "0.2" } log = { version = "0.4", features = ["std"] } mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" @@ -75,7 +75,7 @@ nix = { version = "0.29", default-features = false, features = [ ] } [target.'cfg(target_os = "windows")'.dependencies] -windows-service = "0.7" +windows-service = "0.8" [profile.release] strip = "symbols" diff --git a/src/lib.rs b/src/lib.rs index 40c6774..fac9095 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -375,7 +375,7 @@ where } IpStackStream::UnknownTransport(u) => { let len = u.payload().len(); - log::info!("#0 unhandled transport - Ip Protocol 0x{:02X}, length {}", u.ip_protocol(), len); + log::info!("#0 unhandled transport - Ip Protocol {:?}, length {}", u.ip_protocol(), len); continue; } IpStackStream::UnknownNetwork(pkt) => { From fd7dca998897dddc3130b6d539674e70585784aa Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:53:39 +0800 Subject: [PATCH 173/196] unsafe_in_unsafe issues --- .github/workflows/rust.yml | 1 + Cargo.toml | 4 ++-- src/android.rs | 8 ++++---- src/bin/main.rs | 4 ++-- src/bin/udpgw_server.rs | 4 ++-- src/dns.rs | 2 +- src/dump_logger.rs | 4 ++-- src/general_api.rs | 20 ++++++++++---------- src/http.rs | 2 +- src/lib.rs | 6 +++--- src/socket_transfer.rs | 4 ++-- src/socks.rs | 2 +- src/traffic_status.rs | 4 ++-- src/udpgw.rs | 4 ++-- src/virtual_dns.rs | 2 +- src/win_svc.rs | 2 +- 16 files changed, 37 insertions(+), 36 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 693d55c..75d5c93 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -77,6 +77,7 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - name: Install cargo lipo and rust compiler for ios target if: ${{ !cancelled() }} run: | diff --git a/Cargo.toml b/Cargo.toml index 691c15d..36a5784 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,14 @@ [package] name = "tun2proxy" version = "0.7.4" -edition = "2021" +edition = "2024" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" homepage = "https://github.com/tun2proxy/tun2proxy" authors = ["B. Blechschmidt", "ssrlive"] description = "Tunnel interface to proxy" readme = "README.md" -rust-version = "1.80" +rust-version = "1.85" [lib] crate-type = ["staticlib", "cdylib", "lib"] diff --git a/src/android.rs b/src/android.rs index 21dd5eb..030e921 100644 --- a/src/android.rs +++ b/src/android.rs @@ -1,14 +1,14 @@ #![cfg(target_os = "android")] use crate::{ + Args, args::ArgProxy, error::{Error, Result}, - Args, }; use jni::{ + JNIEnv, objects::{JClass, JString}, sys::{jboolean, jchar, jint}, - JNIEnv, }; /// # Safety @@ -21,7 +21,7 @@ use jni::{ /// - tun_mtu: the tun mtu /// - dns_strategy: the dns strategy, see ArgDns enum /// - verbosity: the verbosity level, see ArgVerbosity enum -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( mut env: JNIEnv, _clazz: JClass, @@ -58,7 +58,7 @@ pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_run( /// # Safety /// /// Shutdown tun2proxy -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn Java_com_github_shadowsocks_bg_Tun2proxy_stop(_env: JNIEnv, _: JClass) -> jint { crate::general_api::tun2proxy_stop_internal() } diff --git a/src/bin/main.rs b/src/bin/main.rs index 0ed98c7..b8ad59e 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -44,7 +44,7 @@ async fn main_async(args: Args) -> Result<(), BoxError> { } unsafe extern "C" fn traffic_cb(status: *const tun2proxy::TrafficStatus, _: *mut std::ffi::c_void) { - let status = &*status; + let status = unsafe { &*status }; log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); } unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; @@ -79,7 +79,7 @@ async fn namespace_proxy_main( _args: Args, _shutdown_token: tokio_util::sync::CancellationToken, ) -> Result { - use nix::fcntl::{open, OFlag}; + use nix::fcntl::{OFlag, open}; use nix::sys::stat::Mode; use std::os::fd::AsRawFd; diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index 9cf144a..370897a 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -3,14 +3,14 @@ use std::net::SocketAddr; use tokio::{ io::AsyncWriteExt, net::{ - tcp::{ReadHalf, WriteHalf}, UdpSocket, + tcp::{ReadHalf, WriteHalf}, }, sync::mpsc::{Receiver, Sender}, }; use tun2proxy::{ - udpgw::{Packet, UdpFlag}, ArgVerbosity, BoxError, Error, Result, + udpgw::{Packet, UdpFlag}, }; pub(crate) const CLIENT_DISCONNECT_TIMEOUT: tokio::time::Duration = std::time::Duration::from_secs(60); diff --git a/src/dns.rs b/src/dns.rs index e18d218..6f9470d 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -1,6 +1,6 @@ use hickory_proto::{ op::{Message, MessageType, ResponseCode}, - rr::{record_type::RecordType, Name, RData, Record}, + rr::{Name, RData, Record, record_type::RecordType}, }; use std::{net::IpAddr, str::FromStr}; diff --git a/src/dump_logger.rs b/src/dump_logger.rs index b04ff09..aba05dd 100644 --- a/src/dump_logger.rs +++ b/src/dump_logger.rs @@ -9,7 +9,7 @@ pub(crate) static DUMP_CALLBACK: Mutex> = Mutex::new(None); /// # Safety /// /// set dump log info callback. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tun2proxy_set_log_callback( callback: Option, ctx: *mut c_void, @@ -23,7 +23,7 @@ pub struct DumpCallback(Option> = /// - dns_strategy: the dns strategy, see ArgDns enum /// - root_privilege: whether to run with root privilege /// - verbosity: the verbosity level, see ArgVerbosity enum -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tun2proxy_with_name_run( proxy_url: *const c_char, tun: *const c_char, @@ -25,12 +25,12 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( _root_privilege: bool, verbosity: ArgVerbosity, ) -> c_int { - let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy_url = unsafe { std::ffi::CStr::from_ptr(proxy_url) }.to_str().unwrap(); let proxy = ArgProxy::try_from(proxy_url).unwrap(); - let tun = std::ffi::CStr::from_ptr(tun).to_str().unwrap().to_string(); + let tun = unsafe { std::ffi::CStr::from_ptr(tun) }.to_str().unwrap().to_string(); let mut args = Args::default(); - if let Ok(bypass) = std::ffi::CStr::from_ptr(bypass).to_str() { + if let Ok(bypass) = unsafe { std::ffi::CStr::from_ptr(bypass) }.to_str() { args.bypass(bypass.parse().unwrap()); } args.proxy(proxy).tun(tun).dns(dns_strategy).verbosity(verbosity); @@ -53,7 +53,7 @@ pub unsafe extern "C" fn tun2proxy_with_name_run( /// - dns_strategy: the dns strategy, see ArgDns enum /// - verbosity: the verbosity level, see ArgVerbosity enum #[cfg(unix)] -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tun2proxy_with_fd_run( proxy_url: *const c_char, tun_fd: c_int, @@ -63,7 +63,7 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( dns_strategy: ArgDns, verbosity: ArgVerbosity, ) -> c_int { - let proxy_url = std::ffi::CStr::from_ptr(proxy_url).to_str().unwrap(); + let proxy_url = unsafe { std::ffi::CStr::from_ptr(proxy_url) }.to_str().unwrap(); let proxy = ArgProxy::try_from(proxy_url).unwrap(); let mut args = Args::default(); @@ -83,9 +83,9 @@ pub unsafe extern "C" fn tun2proxy_with_fd_run( /// e.g. `tun2proxy-bin --setup --proxy socks5://127.0.0.1:1080 --bypass 98.76.54.0/24 --dns over-tcp --verbosity trace` /// - tun_mtu: The MTU of the TUN device, e.g. 1500 /// - packet_information: Whether exists packet information in packet from TUN device -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tun2proxy_run_with_cli_args(cli_args: *const c_char, tun_mtu: c_ushort, packet_information: bool) -> c_int { - let Ok(cli_args) = std::ffi::CStr::from_ptr(cli_args).to_str() else { + let Ok(cli_args) = unsafe { std::ffi::CStr::from_ptr(cli_args) }.to_str() else { log::error!("Failed to convert CLI arguments to string"); return -5; }; @@ -246,7 +246,7 @@ pub async fn general_run_async( /// # Safety /// /// Shutdown the tun2proxy component. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tun2proxy_stop() -> c_int { tun2proxy_stop_internal() } diff --git a/src/http.rs b/src/http.rs index 0b6adf5..caf79e9 100644 --- a/src/http.rs +++ b/src/http.rs @@ -7,7 +7,7 @@ use crate::{ use httparse::Response; use socks5_impl::protocol::UserKey; use std::{ - collections::{hash_map::RandomState, HashMap, VecDeque}, + collections::{HashMap, VecDeque, hash_map::RandomState}, iter::FromIterator, net::SocketAddr, str, diff --git a/src/lib.rs b/src/lib.rs index fac9095..e9b2bfb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,18 +22,18 @@ use std::{ use tokio::{ io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}, net::{TcpSocket, TcpStream, UdpSocket}, - sync::{mpsc::Receiver, Mutex}, + sync::{Mutex, mpsc::Receiver}, }; pub use tokio_util::sync::CancellationToken; use tproxy_config::is_private_ip; use udp_stream::UdpStream; #[cfg(feature = "udpgw")] -use udpgw::{UdpGwClientStream, UdpGwResponse, UDPGW_KEEPALIVE_TIME, UDPGW_MAX_CONNECTIONS}; +use udpgw::{UDPGW_KEEPALIVE_TIME, UDPGW_MAX_CONNECTIONS, UdpGwClientStream, UdpGwResponse}; pub use { args::{ArgDns, ArgProxy, ArgVerbosity, Args, ProxyType}, error::{BoxError, Error, Result}, - traffic_status::{tun2proxy_set_traffic_status_callback, TrafficStatus}, + traffic_status::{TrafficStatus, tun2proxy_set_traffic_status_callback}, }; #[cfg(feature = "mimalloc")] diff --git a/src/socket_transfer.rs b/src/socket_transfer.rs index f10b3f2..fcc2932 100644 --- a/src/socket_transfer.rs +++ b/src/socket_transfer.rs @@ -1,10 +1,10 @@ #![cfg(target_os = "linux")] -use crate::{error, SocketDomain, SocketProtocol}; +use crate::{SocketDomain, SocketProtocol, error}; use nix::{ errno::Errno, fcntl::{self, FdFlag}, - sys::socket::{cmsg_space, getsockopt, recvmsg, sendmsg, sockopt, ControlMessage, ControlMessageOwned, MsgFlags, SockType}, + sys::socket::{ControlMessage, ControlMessageOwned, MsgFlags, SockType, cmsg_space, getsockopt, recvmsg, sendmsg, sockopt}, }; use serde::{Deserialize, Serialize}; use std::{ diff --git a/src/socks.rs b/src/socks.rs index 3800c6a..9c8ad2d 100644 --- a/src/socks.rs +++ b/src/socks.rs @@ -4,7 +4,7 @@ use crate::{ proxy_handler::{ProxyHandler, ProxyHandlerManager}, session_info::SessionInfo, }; -use socks5_impl::protocol::{self, handshake, password_method, Address, AuthMethod, StreamOperation, UserKey, Version}; +use socks5_impl::protocol::{self, Address, AuthMethod, StreamOperation, UserKey, Version, handshake, password_method}; use std::{collections::VecDeque, net::SocketAddr, sync::Arc}; use tokio::sync::Mutex; diff --git a/src/traffic_status.rs b/src/traffic_status.rs index 3117a22..1922401 100644 --- a/src/traffic_status.rs +++ b/src/traffic_status.rs @@ -5,7 +5,7 @@ use std::sync::{LazyLock, Mutex}; /// # Safety /// /// set traffic status callback. -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe extern "C" fn tun2proxy_set_traffic_status_callback( send_interval_secs: u32, callback: Option, @@ -34,7 +34,7 @@ struct TrafficStatusCallback(Option(); diff --git a/src/virtual_dns.rs b/src/virtual_dns.rs index 8dae2a7..7794d37 100644 --- a/src/virtual_dns.rs +++ b/src/virtual_dns.rs @@ -1,5 +1,5 @@ use crate::error::Result; -use hashlink::{linked_hash_map::RawEntryMut, LruCache}; +use hashlink::{LruCache, linked_hash_map::RawEntryMut}; use std::{ collections::HashMap, convert::TryInto, diff --git a/src/win_svc.rs b/src/win_svc.rs index ac87417..a4090e3 100644 --- a/src/win_svc.rs +++ b/src/win_svc.rs @@ -73,7 +73,7 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro let rt = tokio::runtime::Builder::new_multi_thread().enable_all().build()?; rt.block_on(async { unsafe extern "C" fn traffic_cb(status: *const crate::TrafficStatus, _: *mut std::ffi::c_void) { - let status = &*status; + let status = unsafe { &*status }; log::debug!("Traffic: ▲ {} : ▼ {}", status.tx, status.rx); } unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; From e556f7657b92b8e20a67220eca41ee51a84de184 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:56:14 +0800 Subject: [PATCH 174/196] Bump version 0.7.5 --- .github/workflows/publish-exe.yml | 6 +++--- Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index 31312e6..dc2f007 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -34,7 +34,7 @@ jobs: include: - target: x86_64-unknown-linux-gnu - host_os: ubuntu-20.04 + host_os: ubuntu-22.04 - target: x86_64-unknown-linux-musl host_os: ubuntu-latest - target: i686-unknown-linux-musl @@ -73,7 +73,7 @@ jobs: rustup target add ${{ matrix.target }} fi cargo install cbindgen - if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-20.04" ]]; then + if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-22.04" ]]; then sudo .github/workflows/install-cross.sh fi @@ -81,7 +81,7 @@ jobs: if: ${{ !cancelled() }} shell: bash run: | - if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-20.04" ]]; then + if [[ "${{ contains(matrix.host_os, 'ubuntu') }}" == "true" && "${{ matrix.host_os }}" != "ubuntu-22.04" ]]; then cross build --all-features --release --target ${{ matrix.target }} else if [[ "${{ matrix.target }}" == "x86_64-win7-windows-msvc" || "${{ matrix.target }}" == "i686-win7-windows-msvc" ]]; then diff --git a/Cargo.toml b/Cargo.toml index 36a5784..b1d96ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.4" +version = "0.7.5" edition = "2024" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 68716bdc9fa23fd52e0abd11a9ef0c311f979a82 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:48:39 +0800 Subject: [PATCH 175/196] update deps --- .github/workflows/auto-merge.yaml | 2 +- Cargo.toml | 4 ++-- src/error.rs | 4 ---- src/lib.rs | 10 ++++++-- src/socket_transfer.rs | 39 ++++++++++++++++++++----------- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/.github/workflows/auto-merge.yaml b/.github/workflows/auto-merge.yaml index c39e8a6..1280f57 100644 --- a/.github/workflows/auto-merge.yaml +++ b/.github/workflows/auto-merge.yaml @@ -15,6 +15,6 @@ jobs: - name: Auto approve pull request, then squash and merge uses: ahmadnassri/action-dependabot-auto-merge@v2 with: - target: minor + # target: minor # here `PAT_REPO_ADMIN` is a user's passkey provided by github. github-token: ${{ secrets.PAT_REPO_ADMIN }} diff --git a/Cargo.toml b/Cargo.toml index b1d96ad..750989c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,10 +60,10 @@ serde_json = "1" [target.'cfg(target_os="linux")'.dependencies] serde = { version = "1", features = ["derive"] } -bincode = "1" +bincode = "2" [target.'cfg(target_os="android")'.dependencies] -android_logger = "0.14" +android_logger = "0.15" jni = { version = "0.21", default-features = false } [target.'cfg(unix)'.dependencies] diff --git a/src/error.rs b/src/error.rs index 755ee0c..c93ecb5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -43,10 +43,6 @@ pub enum Error { #[error("std::num::ParseIntError {0:?}")] IntParseError(#[from] std::num::ParseIntError), - - #[cfg(target_os = "linux")] - #[error("bincode::Error {0:?}")] - BincodeError(#[from] bincode::Error), } impl From<&str> for Error { diff --git a/src/lib.rs b/src/lib.rs index e9b2bfb..c62e08b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,7 +69,10 @@ use std::sync::atomic::Ordering::Relaxed; #[allow(unused)] #[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + target_os = "linux", + derive(bincode::Encode, bincode::Decode, serde::Serialize, serde::Deserialize) +)] pub enum SocketProtocol { Tcp, Udp, @@ -77,7 +80,10 @@ pub enum SocketProtocol { #[allow(unused)] #[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(target_os = "linux", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + target_os = "linux", + derive(bincode::Encode, bincode::Decode, serde::Serialize, serde::Deserialize) +)] pub enum SocketDomain { IpV4, IpV6, diff --git a/src/socket_transfer.rs b/src/socket_transfer.rs index fcc2932..e069b1d 100644 --- a/src/socket_transfer.rs +++ b/src/socket_transfer.rs @@ -16,14 +16,14 @@ use tokio::net::{TcpSocket, UdpSocket, UnixDatagram}; const REQUEST_BUFFER_SIZE: usize = 64; -#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[derive(bincode::Encode, bincode::Decode, Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] struct Request { protocol: SocketProtocol, domain: SocketDomain, number: u32, } -#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug, Serialize, Deserialize)] +#[derive(bincode::Encode, bincode::Decode, PartialEq, Debug, Hash, Copy, Clone, Eq, Serialize, Deserialize)] enum Response { Ok, } @@ -135,14 +135,21 @@ where // Borrow socket as mut to prevent multiple simultaneous requests let socket = socket.deref_mut(); - // Send request - let request = bincode::serialize(&Request { - protocol: T::domain(), - domain, - number, - })?; + let mut request = [0u8; 1000]; - socket.send(&request[..]).await?; + // Send request + let size = bincode::encode_into_slice( + Request { + protocol: T::domain(), + domain, + number, + }, + &mut request, + bincode::config::standard(), + ) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; + + socket.send(&request[..size]).await?; // Receive response loop { @@ -161,7 +168,9 @@ where // Parse response let response = &msg.iovs().next().unwrap()[..msg.bytes]; - let response: Response = bincode::deserialize(response)?; + let response: Response = bincode::decode_from_slice(response, bincode::config::standard()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))? + .0; if !matches!(response, Response::Ok) { return Err("Request for new sockets failed".into()); } @@ -194,10 +203,14 @@ pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()> let len = socket.recv(&mut buf[..]).await?; - let request: Request = bincode::deserialize(&buf[..len])?; + let request: Request = bincode::decode_from_slice(&buf[..len], bincode::config::standard()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))? + .0; let response = Response::Ok; - let buf = bincode::serialize(&response)?; + let mut buf = [0u8; 1000]; + let size = bincode::encode_into_slice(response, &mut buf, bincode::config::standard()) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e))?; let mut owned_fd_buf: Vec = Vec::with_capacity(request.number as usize); for _ in 0..request.number { @@ -223,7 +236,7 @@ pub async fn process_socket_requests(socket: &UnixDatagram) -> error::Result<()> let raw_fd_buf: Vec = owned_fd_buf.iter().map(|fd| fd.as_raw_fd()).collect(); let cmsg = ControlMessage::ScmRights(&raw_fd_buf[..]); - let iov = [IoSlice::new(&buf[..])]; + let iov = [IoSlice::new(&buf[..size])]; sendmsg::<()>(socket.as_raw_fd(), &iov, &[cmsg], MsgFlags::empty(), None)?; } From ca7cd25c4e6ea041cb1113fc6836e63b2d2edb81 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 7 Mar 2025 14:15:14 +0800 Subject: [PATCH 176/196] Bump version 0.7.6 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 750989c..2d48a4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.5" +version = "0.7.6" edition = "2024" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 61bbafcf82761e0bc540f9a3672c6d4a1cafe6c9 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 11 Mar 2025 12:37:22 +0800 Subject: [PATCH 177/196] version_info method --- Cargo.toml | 5 +++-- build.rs | 16 ++++++++++++++++ src/args.rs | 6 +++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2d48a4d..f23360b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,6 +56,7 @@ unicase = "2" url = "2" [build-dependencies] +chrono = "0.4" serde_json = "1" [target.'cfg(target_os="linux")'.dependencies] @@ -77,5 +78,5 @@ nix = { version = "0.29", default-features = false, features = [ [target.'cfg(target_os = "windows")'.dependencies] windows-service = "0.8" -[profile.release] -strip = "symbols" +# [profile.release] +# strip = "symbols" diff --git a/build.rs b/build.rs index a7dc782..ccf40ea 100644 --- a/build.rs +++ b/build.rs @@ -1,4 +1,13 @@ fn main() -> Result<(), Box> { + if let Ok(git_hash) = get_git_hash() { + // Set the environment variables + println!("cargo:rustc-env=GIT_HASH={}", git_hash.trim()); + } + + // Get the build time + let build_time = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string(); + println!("cargo:rustc-env=BUILD_TIME={}", build_time); + #[cfg(target_os = "windows")] if let Ok(cargo_target_dir) = get_cargo_target_dir() { let mut f = std::fs::File::create(cargo_target_dir.join("build.log"))?; @@ -85,3 +94,10 @@ fn get_crate_dir(crate_name: &str) -> Result std::io::Result { + use std::process::Command; + let git_hash = Command::new("git").args(["rev-parse", "--short", "HEAD"]).output()?.stdout; + let git_hash = String::from_utf8(git_hash).map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?; + Ok(git_hash) +} diff --git a/src/args.rs b/src/args.rs index 71a85f2..ee1ab58 100644 --- a/src/args.rs +++ b/src/args.rs @@ -9,7 +9,7 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::str::FromStr; #[derive(Debug, Clone, clap::Parser)] -#[command(author, version, about = "Tunnel interface to proxy.", long_about = None)] +#[command(author, version = version_info(), about = "Tunnel interface to proxy.", long_about = None)] pub struct Args { /// Proxy URL in the form proto://[username[:password]@]host:port, /// where proto is one of socks4, socks5, http. @@ -127,6 +127,10 @@ pub struct Args { pub udpgw_keepalive: Option, } +fn version_info() -> &'static str { + concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")") +} + fn validate_tun(p: &str) -> Result { #[cfg(target_os = "macos")] if p.len() <= 4 || &p[..4] != "utun" { From a2399c8b28a7a9759d3a22b24ff175ff280d70e7 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 12 Mar 2025 11:18:47 +0800 Subject: [PATCH 178/196] log ipstack info adjusted --- src/bin/main.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index b8ad59e..0c82309 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,4 +1,4 @@ -use tun2proxy::{Args, BoxError}; +use tun2proxy::{ArgVerbosity, Args, BoxError}; fn main() -> Result<(), BoxError> { dotenvy::dotenv().ok(); @@ -28,7 +28,11 @@ fn main() -> Result<(), BoxError> { } async fn main_async(args: Args) -> Result<(), BoxError> { - let default = format!("{:?},hickory_proto=warn", args.verbosity); + let ipstack = match args.verbosity { + ArgVerbosity::Trace => ArgVerbosity::Debug, + _ => args.verbosity, + }; + let default = format!("{:?},hickory_proto=warn,ipstack={:?}", args.verbosity, ipstack); env_logger::Builder::from_env(env_logger::Env::default().default_filter_or(default)).init(); let shutdown_token = tokio_util::sync::CancellationToken::new(); From a380817951e8c3be166b73eebae52b4d86d09d85 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 19 Mar 2025 08:36:29 +0800 Subject: [PATCH 179/196] update hickory-proto (DNS parser) --- Cargo.toml | 2 +- src/dns.rs | 23 ++++++++--------------- src/error.rs | 2 +- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f23360b..3614e90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ digest_auth = "0.3" dotenvy = "0.15" env_logger = "0.11" hashlink = "0.10" -hickory-proto = "0.24" +hickory-proto = "0.25" httparse = "1" ipstack = { version = "0.2" } log = { version = "0.4", features = ["std"] } diff --git a/src/dns.rs b/src/dns.rs index 6f9470d..ed36f5f 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -1,21 +1,16 @@ use hickory_proto::{ op::{Message, MessageType, ResponseCode}, - rr::{Name, RData, Record, record_type::RecordType}, + rr::{ + Name, RData, Record, + rdata::{A, AAAA}, + }, }; use std::{net::IpAddr, str::FromStr}; pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u32) -> Result { let record = match ip { - IpAddr::V4(ip) => { - let mut record = Record::with(Name::from_str(domain)?, RecordType::A, ttl); - record.set_data(Some(RData::A(ip.into()))); - record - } - IpAddr::V6(ip) => { - let mut record = Record::with(Name::from_str(domain)?, RecordType::AAAA, ttl); - record.set_data(Some(RData::AAAA(ip.into()))); - record - } + IpAddr::V4(ip) => Record::from_rdata(Name::from_str(domain)?, ttl, RData::A(A(ip))), + IpAddr::V6(ip) => Record::from_rdata(Name::from_str(domain)?, ttl, RData::AAAA(AAAA(ip))), }; // We must indicate that this message is a response. Otherwise, implementations may not @@ -27,9 +22,7 @@ pub fn build_dns_response(mut request: Message, domain: &str, ip: IpAddr, ttl: u } pub fn remove_ipv6_entries(message: &mut Message) { - message - .answers_mut() - .retain(|answer| !matches!(answer.data(), Some(RData::AAAA(_)))); + message.answers_mut().retain(|answer| !matches!(answer.data(), RData::AAAA(_))); } pub fn extract_ipaddr_from_dns_message(message: &Message) -> Result { @@ -38,7 +31,7 @@ pub fn extract_ipaddr_from_dns_message(message: &Message) -> Result { return Ok(IpAddr::V4((*addr).into())); } diff --git a/src/error.rs b/src/error.rs index c93ecb5..99107c2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,7 +26,7 @@ pub enum Error { IpStack(#[from] ipstack::IpStackError), #[error("DnsProtoError {0:?}")] - DnsProto(#[from] hickory_proto::error::ProtoError), + DnsProto(#[from] hickory_proto::ProtoError), #[error("httparse::Error {0:?}")] Httparse(#[from] httparse::Error), From 7657f1603f001b9a0bbafbb2b86a1a3c85f81be3 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 28 Mar 2025 20:23:47 +0800 Subject: [PATCH 180/196] Bump version 0.7.7 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3614e90..b730860 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.6" +version = "0.7.7" edition = "2024" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From 9e75475a2370bdceef0bf3778be557912d50890b Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 18 Apr 2025 11:03:05 +0800 Subject: [PATCH 181/196] force exit process while fatal error --- src/bin/main.rs | 17 +++++++++++++---- src/general_api.rs | 17 ++++++++++++----- src/lib.rs | 28 +++++++++++++++------------- src/win_svc.rs | 12 ++++++++++-- 4 files changed, 50 insertions(+), 24 deletions(-) diff --git a/src/bin/main.rs b/src/bin/main.rs index 0c82309..d7174d9 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -37,6 +37,7 @@ async fn main_async(args: Args) -> Result<(), BoxError> { let shutdown_token = tokio_util::sync::CancellationToken::new(); let main_loop_handle = tokio::spawn({ + let args = args.clone(); let shutdown_token = shutdown_token.clone(); async move { #[cfg(target_os = "linux")] @@ -44,7 +45,7 @@ async fn main_async(args: Args) -> Result<(), BoxError> { if let Err(err) = namespace_proxy_main(args, shutdown_token).await { log::error!("namespace proxy error: {}", err); } - return; + return Ok(0); } unsafe extern "C" fn traffic_cb(status: *const tun2proxy::TrafficStatus, _: *mut std::ffi::c_void) { @@ -53,9 +54,11 @@ async fn main_async(args: Args) -> Result<(), BoxError> { } unsafe { tun2proxy::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - if let Err(err) = tun2proxy::general_run_async(args, tun::DEFAULT_MTU, cfg!(target_os = "macos"), shutdown_token).await { - log::error!("main loop error: {}", err); + let ret = tun2proxy::general_run_async(args, tun::DEFAULT_MTU, cfg!(target_os = "macos"), shutdown_token).await; + if let Err(err) = &ret { + log::error!("main loop error: {err}"); } + ret } }); @@ -68,13 +71,19 @@ async fn main_async(args: Args) -> Result<(), BoxError> { }) .await; - main_loop_handle.await?; + let tasks = main_loop_handle.await??; if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) { log::info!("Ctrl-C fired, waiting the handler to finish..."); ctrlc_handel.await.map_err(|err| err.to_string())?; } + if args.exit_on_fatal_error && tasks >= args.max_sessions { + // Because `main_async` function perhaps stuck in `await` state, so we need to exit the process forcefully + log::info!("Internal fatal error, max sessions reached ({tasks}/{})", args.max_sessions); + std::process::exit(-1); + } + Ok(()) } diff --git a/src/general_api.rs b/src/general_api.rs index 2838271..9e7dd3c 100644 --- a/src/general_api.rs +++ b/src/general_api.rs @@ -120,11 +120,18 @@ pub fn general_run_for_api(args: Args, tun_mtu: u16, packet_information: bool) - return -3; }; match rt.block_on(async move { - if let Err(err) = general_run_async(args, tun_mtu, packet_information, shutdown_token).await { - log::error!("main loop error: {}", err); - return Err(err); + let ret = general_run_async(args.clone(), tun_mtu, packet_information, shutdown_token).await; + match &ret { + Ok(sessions) => { + if args.exit_on_fatal_error && *sessions >= args.max_sessions { + log::error!("Forced exit due to max sessions reached ({sessions}/{})", args.max_sessions); + std::process::exit(-1); + } + log::debug!("tun2proxy exited normally, current sessions: {sessions}"); + } + Err(err) => log::error!("main loop error: {err}"), } - Ok(()) + ret }) { Ok(_) => 0, Err(e) => { @@ -140,7 +147,7 @@ pub async fn general_run_async( tun_mtu: u16, _packet_information: bool, shutdown_token: tokio_util::sync::CancellationToken, -) -> std::io::Result<()> { +) -> std::io::Result { let mut tun_config = tun::Configuration::default(); #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] diff --git a/src/lib.rs b/src/lib.rs index c62e08b..06e3697 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,7 +64,7 @@ pub mod win_svc; const DNS_PORT: u16 = 53; -static TASK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); +static TASK_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); use std::sync::atomic::Ordering::Relaxed; #[allow(unused)] @@ -154,7 +154,9 @@ async fn create_udp_stream(socket_queue: &Option>, peer: Socket /// * `mtu` - The MTU of the network device /// * `args` - The arguments to use /// * `shutdown_token` - The token to exit the server -pub async fn run(device: D, mtu: u16, args: Args, shutdown_token: CancellationToken) -> crate::Result<()> +/// # Returns +/// * The number of sessions while exiting +pub async fn run(device: D, mtu: u16, args: Args, shutdown_token: CancellationToken) -> crate::Result where D: AsyncRead + AsyncWrite + Unpin + Send + 'static, { @@ -265,10 +267,10 @@ where ip_stack_stream? } }; - let max_sessions = args.max_sessions as u64; + let max_sessions = args.max_sessions; match ip_stack_stream { IpStackStream::Tcp(tcp) => { - if TASK_COUNT.load(Relaxed) > max_sessions { + if TASK_COUNT.load(Relaxed) >= max_sessions { if args.exit_on_fatal_error { log::info!("Too many sessions that over {max_sessions}, exiting..."); break; @@ -276,7 +278,7 @@ where log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } - log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); + log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed).saturating_add(1)); let info = SessionInfo::new(tcp.local_addr(), tcp.peer_addr(), IpProtocol::Tcp); let domain_name = if let Some(virtual_dns) = &virtual_dns { let mut virtual_dns = virtual_dns.lock().await; @@ -291,11 +293,11 @@ where if let Err(err) = handle_tcp_session(tcp, proxy_handler, socket_queue).await { log::error!("{} error \"{}\"", info, err); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); }); } IpStackStream::Udp(udp) => { - if TASK_COUNT.load(Relaxed) > max_sessions { + if TASK_COUNT.load(Relaxed) >= max_sessions { if args.exit_on_fatal_error { log::info!("Too many sessions that over {max_sessions}, exiting..."); break; @@ -303,7 +305,7 @@ where log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } - log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed) + 1); + log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed).saturating_add(1)); let mut info = SessionInfo::new(udp.local_addr(), udp.peer_addr(), IpProtocol::Udp); if info.dst.port() == DNS_PORT { if is_private_ip(info.dst.ip()) { @@ -317,7 +319,7 @@ where if let Err(err) = handle_dns_over_tcp_session(udp, proxy_handler, socket_queue, ipv6_enabled).await { log::error!("{} error \"{}\"", info, err); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); }); continue; } @@ -328,7 +330,7 @@ where log::error!("{} error \"{}\"", info, err); } } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); }); continue; } @@ -359,7 +361,7 @@ where if let Err(e) = handle_udp_gateway_session(udp, udpgw, &dst_addr, proxy_handler, queue, ipv6_enabled).await { log::info!("Ending {} with \"{}\"", info, e); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); }); continue; } @@ -371,7 +373,7 @@ where if let Err(err) = handle_udp_associate_session(udp, ty, proxy_handler, socket_queue, ipv6_enabled).await { log::info!("Ending {} with \"{}\"", info, err); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed) - 1); + log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); }); } Err(e) => { @@ -390,7 +392,7 @@ where } } } - Ok(()) + Ok(TASK_COUNT.load(Relaxed)) } async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc>) -> crate::Result<()> { diff --git a/src/win_svc.rs b/src/win_svc.rs index a4090e3..5ee416e 100644 --- a/src/win_svc.rs +++ b/src/win_svc.rs @@ -78,8 +78,16 @@ fn run_service(_arguments: Vec) -> Result<(), crate::BoxErro } unsafe { crate::tun2proxy_set_traffic_status_callback(1, Some(traffic_cb), std::ptr::null_mut()) }; - if let Err(err) = crate::general_run_async(args, tun::DEFAULT_MTU, false, shutdown_token).await { - log::error!("main loop error: {}", err); + let ret = crate::general_run_async(args.clone(), tun::DEFAULT_MTU, false, shutdown_token).await; + match &ret { + Ok(sessions) => { + if args.exit_on_fatal_error && *sessions >= args.max_sessions { + log::error!("Forced exit due to max sessions reached ({sessions}/{})", args.max_sessions); + std::process::exit(-1); + } + log::debug!("tun2proxy exited normally, current sessions: {sessions}"); + } + Err(err) => log::error!("main loop error: {err}"), } Ok::<(), crate::Error>(()) })?; From 7121a80300359644e2b0719181845157c40e02b3 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:50:56 +0800 Subject: [PATCH 182/196] Bump version 0.7.8 --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b730860..dc4fec7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.7" +version = "0.7.8" edition = "2024" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" @@ -38,7 +38,7 @@ env_logger = "0.11" hashlink = "0.10" hickory-proto = "0.25" httparse = "1" -ipstack = { version = "0.2" } +ipstack = { version = "0.3", git = "https://github.com/ssrlive/ipstack.git", rev = "53c648e" } log = { version = "0.4", features = ["std"] } mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" From 88423039c643d010ef36723aa251e53932624397 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sun, 20 Apr 2025 19:56:36 +0800 Subject: [PATCH 183/196] make TASK_COUNT as local task_count variable --- src/lib.rs | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 06e3697..5f77672 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,9 +64,6 @@ pub mod win_svc; const DNS_PORT: u16 = 53; -static TASK_COUNT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0); -use std::sync::atomic::Ordering::Relaxed; - #[allow(unused)] #[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr( @@ -224,11 +221,11 @@ where let socket_queue = None; use socks5_impl::protocol::Version::{V4, V5}; - let mgr = match args.proxy.proxy_type { - ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)) as Arc, - ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)) as Arc, - ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)) as Arc, - ProxyType::None => Arc::new(NoProxyManager::new()) as Arc, + let mgr: Arc = match args.proxy.proxy_type { + ProxyType::Socks5 => Arc::new(SocksProxyManager::new(server_addr, V5, key)), + ProxyType::Socks4 => Arc::new(SocksProxyManager::new(server_addr, V4, key)), + ProxyType::Http => Arc::new(HttpManager::new(server_addr, key)), + ProxyType::None => Arc::new(NoProxyManager::new()), }; let mut ipstack_config = ipstack::IpStackConfig::default(); @@ -256,7 +253,11 @@ where client }); + let task_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); + use std::sync::atomic::Ordering::Relaxed; + loop { + let task_count = task_count.clone(); let virtual_dns = virtual_dns.clone(); let ip_stack_stream = tokio::select! { _ = shutdown_token.cancelled() => { @@ -270,7 +271,7 @@ where let max_sessions = args.max_sessions; match ip_stack_stream { IpStackStream::Tcp(tcp) => { - if TASK_COUNT.load(Relaxed) >= max_sessions { + if task_count.load(Relaxed) >= max_sessions { if args.exit_on_fatal_error { log::info!("Too many sessions that over {max_sessions}, exiting..."); break; @@ -278,7 +279,7 @@ where log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } - log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed).saturating_add(1)); + log::trace!("Session count {}", task_count.fetch_add(1, Relaxed).saturating_add(1)); let info = SessionInfo::new(tcp.local_addr(), tcp.peer_addr(), IpProtocol::Tcp); let domain_name = if let Some(virtual_dns) = &virtual_dns { let mut virtual_dns = virtual_dns.lock().await; @@ -293,11 +294,11 @@ where if let Err(err) = handle_tcp_session(tcp, proxy_handler, socket_queue).await { log::error!("{} error \"{}\"", info, err); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); }); } IpStackStream::Udp(udp) => { - if TASK_COUNT.load(Relaxed) >= max_sessions { + if task_count.load(Relaxed) >= max_sessions { if args.exit_on_fatal_error { log::info!("Too many sessions that over {max_sessions}, exiting..."); break; @@ -305,11 +306,11 @@ where log::warn!("Too many sessions that over {max_sessions}, dropping new session"); continue; } - log::trace!("Session count {}", TASK_COUNT.fetch_add(1, Relaxed).saturating_add(1)); + log::trace!("Session count {}", task_count.fetch_add(1, Relaxed).saturating_add(1)); let mut info = SessionInfo::new(udp.local_addr(), udp.peer_addr(), IpProtocol::Udp); if info.dst.port() == DNS_PORT { if is_private_ip(info.dst.ip()) { - info.dst.set_ip(dns_addr); + info.dst.set_ip(dns_addr); // !!! Here we change the destination address to remote DNS server!!! } if args.dns == ArgDns::OverTcp { info.protocol = IpProtocol::Tcp; @@ -319,7 +320,7 @@ where if let Err(err) = handle_dns_over_tcp_session(udp, proxy_handler, socket_queue, ipv6_enabled).await { log::error!("{} error \"{}\"", info, err); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); }); continue; } @@ -330,7 +331,7 @@ where log::error!("{} error \"{}\"", info, err); } } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); }); continue; } @@ -361,7 +362,7 @@ where if let Err(e) = handle_udp_gateway_session(udp, udpgw, &dst_addr, proxy_handler, queue, ipv6_enabled).await { log::info!("Ending {} with \"{}\"", info, e); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); }); continue; } @@ -373,7 +374,7 @@ where if let Err(err) = handle_udp_associate_session(udp, ty, proxy_handler, socket_queue, ipv6_enabled).await { log::info!("Ending {} with \"{}\"", info, err); } - log::trace!("Session count {}", TASK_COUNT.fetch_sub(1, Relaxed).saturating_sub(1)); + log::trace!("Session count {}", task_count.fetch_sub(1, Relaxed).saturating_sub(1)); }); } Err(e) => { @@ -392,7 +393,7 @@ where } } } - Ok(TASK_COUNT.load(Relaxed)) + Ok(task_count.load(Relaxed)) } async fn handle_virtual_dns_session(mut udp: IpStackUdpStream, dns: Arc>) -> crate::Result<()> { From 2ead13a3f4081d0fd72312666883fd4a4e7b073c Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 22 Apr 2025 14:58:52 +0800 Subject: [PATCH 184/196] version_info & about_info --- src/args.rs | 17 ++++++++++++----- src/bin/udpgw_server.rs | 8 ++++++-- src/lib.rs | 2 +- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/args.rs b/src/args.rs index ee1ab58..02f5ec4 100644 --- a/src/args.rs +++ b/src/args.rs @@ -8,8 +8,19 @@ use std::ffi::OsString; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use std::str::FromStr; +#[macro_export] +macro_rules! version_info { + () => { + concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")") + }; +} + +fn about_info() -> &'static str { + concat!("Tunnel interface to proxy.\nVersion: ", version_info!()) +} + #[derive(Debug, Clone, clap::Parser)] -#[command(author, version = version_info(), about = "Tunnel interface to proxy.", long_about = None)] +#[command(author, version = version_info!(), about = about_info(), long_about = None)] pub struct Args { /// Proxy URL in the form proto://[username[:password]@]host:port, /// where proto is one of socks4, socks5, http. @@ -127,10 +138,6 @@ pub struct Args { pub udpgw_keepalive: Option, } -fn version_info() -> &'static str { - concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")") -} - fn validate_tun(p: &str) -> Result { #[cfg(target_os = "macos")] if p.len() <= 4 || &p[..4] != "utun" { diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index 370897a..05f39a6 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -28,8 +28,12 @@ impl Client { } } +fn about_info() -> &'static str { + concat!("UDP Gateway Server for tun2proxy\nVersion: ", tun2proxy::version_info!()) +} + #[derive(Debug, Clone, clap::Parser)] -#[command(author, version, about = "UDP Gateway Server for tun2proxy", long_about = None)] +#[command(author, version = tun2proxy::version_info!(), about = about_info(), long_about = None)] pub struct UdpGwArgs { /// UDP gateway listen address #[arg(short, long, value_name = "IP:PORT", default_value = "127.0.0.1:7300")] @@ -193,7 +197,7 @@ async fn write_to_client(addr: SocketAddr, mut writer: WriteHalf<'_>, mut rx: Re } async fn main_async(args: UdpGwArgs) -> Result<(), BoxError> { - log::info!("{} {} starting...", module_path!(), env!("CARGO_PKG_VERSION")); + log::info!("{} {} starting...", module_path!(), tun2proxy::version_info!()); log::info!("UDP Gateway Server running at {}", args.listen_addr); let shutdown_token = tokio_util::sync::CancellationToken::new(); diff --git a/src/lib.rs b/src/lib.rs index 5f77672..942c509 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -157,7 +157,7 @@ pub async fn run(device: D, mtu: u16, args: Args, shutdown_token: Cancellatio where D: AsyncRead + AsyncWrite + Unpin + Send + 'static, { - log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")); + log::info!("{} {} starting...", env!("CARGO_PKG_NAME"), version_info!()); log::info!("Proxy {} server: {}", args.proxy.proxy_type, args.proxy.addr); let server_addr = args.proxy.addr; From b71f479bf3dce0aba316abbd94a3951e1fdea73d Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 23 Apr 2025 09:36:38 +0800 Subject: [PATCH 185/196] close-stale-issues.yml --- .github/workflows/close-stale-issues.yml | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/close-stale-issues.yml diff --git a/.github/workflows/close-stale-issues.yml b/.github/workflows/close-stale-issues.yml new file mode 100644 index 0000000..d06ff8a --- /dev/null +++ b/.github/workflows/close-stale-issues.yml @@ -0,0 +1,26 @@ +name: Close stale issues and PRs + +on: + schedule: + - cron: "0 0 * * *" # run a cron job every day at midnight + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - name: Close stale issues and PRs + uses: actions/stale@v9 + with: + stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.' + stale-pr-message: 'This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days.' + close-issue-message: 'This issue was closed because it has been stalled for 5 days with no activity.' + close-pr-message: 'This PR was closed because it has been stalled for 10 days with no activity.' + days-before-issue-stale: 30 + days-before-pr-stale: 45 + days-before-issue-close: 5 + days-before-pr-close: 10 + stale-issue-label: 'no-issue-activity' + exempt-issue-labels: 'keep-open,awaiting-approval,work-in-progress' + stale-pr-label: 'no-pr-activity' + exempt-pr-labels: 'awaiting-approval,work-in-progress' + # only-labels: 'awaiting-feedback,awaiting-answers' From 54f7dbc81b3ec65be0d2f0190fccd9a2dbe53695 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 30 Apr 2025 10:55:43 +0800 Subject: [PATCH 186/196] update nix deps --- Cargo.toml | 2 +- src/bin/main.rs | 2 +- src/socket_transfer.rs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dc4fec7..a4d94dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -69,7 +69,7 @@ jni = { version = "0.21", default-features = false } [target.'cfg(unix)'.dependencies] daemonize = "0.5" -nix = { version = "0.29", default-features = false, features = [ +nix = { version = "0.30", default-features = false, features = [ "fs", "socket", "uio", diff --git a/src/bin/main.rs b/src/bin/main.rs index d7174d9..102b6a3 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -102,7 +102,7 @@ async fn namespace_proxy_main( let child = tokio::process::Command::new("unshare") .args("--user --map-current-user --net --mount --keep-caps --kill-child --fork".split(' ')) - .arg(format!("/proc/self/fd/{}", fd)) + .arg(format!("/proc/self/fd/{}", fd.as_raw_fd())) .arg("--socket-transfer-fd") .arg(remote_fd.as_raw_fd().to_string()) .args(std::env::args().skip(1)) diff --git a/src/socket_transfer.rs b/src/socket_transfer.rs index e069b1d..e68bef2 100644 --- a/src/socket_transfer.rs +++ b/src/socket_transfer.rs @@ -30,17 +30,17 @@ enum Response { /// Reconstruct socket from raw `fd` pub fn reconstruct_socket(fd: RawFd) -> Result { - // Check if `fd` is valid - let fd_flags = fcntl::fcntl(fd, fcntl::F_GETFD)?; - // `fd` is confirmed to be valid so it should be closed let socket = unsafe { OwnedFd::from_raw_fd(fd) }; + // Check if `fd` is valid + let fd_flags = fcntl::fcntl(socket.as_fd(), fcntl::F_GETFD)?; + // Insert CLOEXEC flag to the `fd` to prevent further propagation across `execve(2)` calls let mut fd_flags = FdFlag::from_bits(fd_flags).ok_or(ErrorKind::Unsupported)?; if !fd_flags.contains(FdFlag::FD_CLOEXEC) { fd_flags.insert(FdFlag::FD_CLOEXEC); - fcntl::fcntl(fd, fcntl::F_SETFD(fd_flags))?; + fcntl::fcntl(socket.as_fd(), fcntl::F_SETFD(fd_flags))?; } Ok(socket) @@ -70,12 +70,12 @@ pub async fn create_transfer_socket_pair() -> std::io::Result<(UnixDatagram, Own let remote_fd: OwnedFd = remote.into_std().unwrap().into(); // Get `remote_fd` flags - let fd_flags = fcntl::fcntl(remote_fd.as_raw_fd(), fcntl::F_GETFD)?; + let fd_flags = fcntl::fcntl(remote_fd.as_fd(), fcntl::F_GETFD)?; // Remove CLOEXEC flag from the `remote_fd` to allow propagating across `execve(2)` let mut fd_flags = FdFlag::from_bits(fd_flags).ok_or(ErrorKind::Unsupported)?; fd_flags.remove(FdFlag::FD_CLOEXEC); - fcntl::fcntl(remote_fd.as_raw_fd(), fcntl::F_SETFD(fd_flags))?; + fcntl::fcntl(remote_fd.as_fd(), fcntl::F_SETFD(fd_flags))?; Ok((local, remote_fd)) } From cf4a565f93f5438587f926c0595ce18a7203d544 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 08:30:43 +0800 Subject: [PATCH 187/196] Update socks5-impl requirement from 0.6 to 0.7 (#201) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a4d94dc..041288f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ log = { version = "0.4", features = ["std"] } mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" shlex = "1.3.0" -socks5-impl = { version = "0.6", default-features = false, features = [ +socks5-impl = { version = "0.7", default-features = false, features = [ "tokio", ] } thiserror = "2" From 7c32b6272744ff1c4d16d06600b508682ae4b392 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Fri, 2 May 2025 16:10:17 +0800 Subject: [PATCH 188/196] Exclude dependabot[bot] in Integration Tests --- .github/workflows/publish-exe.yml | 1 + .github/workflows/rust.yml | 1 + .github/workflows/tests.yml | 10 +++------- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/publish-exe.yml b/.github/workflows/publish-exe.yml index dc2f007..697f352 100644 --- a/.github/workflows/publish-exe.yml +++ b/.github/workflows/publish-exe.yml @@ -1,4 +1,5 @@ on: + workflow_dispatch: push: tags: - "v*.*.*" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 75d5c93..82fa836 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,6 +1,7 @@ name: Push or PR on: + workflow_dispatch: push: branches: - '**' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c274674..51f65cf 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,14 +12,10 @@ jobs: proxy_tests: name: Proxy Tests runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'safe to test') + if: (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'safe to test')) && github.actor != 'dependabot[bot]' && github.actor != 'github-actions[bot]' steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: stable - override: true + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - name: Populate .env env: DOTENV: ${{ secrets.DOTENV }} From 3dc8f222cb32b77d0a2c45d896364537287888f3 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Thu, 8 May 2025 10:26:00 +0800 Subject: [PATCH 189/196] Bump version 0.7.9 --- Cargo.toml | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 041288f..083a8d8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.8" +version = "0.7.9" edition = "2024" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" @@ -38,7 +38,7 @@ env_logger = "0.11" hashlink = "0.10" hickory-proto = "0.25" httparse = "1" -ipstack = { version = "0.3", git = "https://github.com/ssrlive/ipstack.git", rev = "53c648e" } +ipstack = { version = "0.4" } log = { version = "0.4", features = ["std"] } mimalloc = { version = "0.1", default-features = false, optional = true } percent-encoding = "2" diff --git a/src/lib.rs b/src/lib.rs index 942c509..ceddcf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ use crate::{ session_info::{IpProtocol, SessionInfo}, virtual_dns::VirtualDns, }; -use ipstack::stream::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; +use ipstack::{IpStackStream, IpStackTcpStream, IpStackUdpStream}; use proxy_handler::{ProxyHandler, ProxyHandlerManager}; use socks::SocksProxyManager; pub use socks5_impl::protocol::UserKey; From 6a5692cea0285afbb2841c79ef0fdf3b873ccef8 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Wed, 21 May 2025 15:19:18 +0800 Subject: [PATCH 190/196] refine code --- Cargo.toml | 20 ++++++++++---------- src/error.rs | 10 ++++++++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 083a8d8..3fcc22d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,18 +55,17 @@ udp-stream = { version = "0.0.12", default-features = false } unicase = "2" url = "2" -[build-dependencies] -chrono = "0.4" -serde_json = "1" - -[target.'cfg(target_os="linux")'.dependencies] -serde = { version = "1", features = ["derive"] } -bincode = "2" - [target.'cfg(target_os="android")'.dependencies] android_logger = "0.15" jni = { version = "0.21", default-features = false } +[target.'cfg(target_os="linux")'.dependencies] +bincode = "2" +serde = { version = "1", features = ["derive"] } + +[target.'cfg(target_os="windows")'.dependencies] +windows-service = "0.8" + [target.'cfg(unix)'.dependencies] daemonize = "0.5" nix = { version = "0.30", default-features = false, features = [ @@ -75,8 +74,9 @@ nix = { version = "0.30", default-features = false, features = [ "uio", ] } -[target.'cfg(target_os = "windows")'.dependencies] -windows-service = "0.8" +[build-dependencies] +chrono = "0.4" +serde_json = "1" # [profile.release] # strip = "symbols" diff --git a/src/error.rs b/src/error.rs index 99107c2..b6a6b92 100644 --- a/src/error.rs +++ b/src/error.rs @@ -23,7 +23,7 @@ pub enum Error { TryFromSlice(#[from] std::array::TryFromSliceError), #[error("IpStackError {0:?}")] - IpStack(#[from] ipstack::IpStackError), + IpStack(#[from] Box), #[error("DnsProtoError {0:?}")] DnsProto(#[from] hickory_proto::ProtoError), @@ -45,6 +45,12 @@ pub enum Error { IntParseError(#[from] std::num::ParseIntError), } +impl From for Error { + fn from(err: ipstack::IpStackError) -> Self { + Self::IpStack(Box::new(err)) + } +} + impl From<&str> for Error { fn from(err: &str) -> Self { Self::String(err.to_string()) @@ -67,7 +73,7 @@ impl From for std::io::Error { fn from(err: Error) -> Self { match err { Error::Io(err) => err, - _ => std::io::Error::new(std::io::ErrorKind::Other, err), + _ => std::io::Error::other(err), } } } From 8cdb4f535d30c1f3a5e553ae2834daaebb3e914f Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Mon, 2 Jun 2025 10:31:44 +0800 Subject: [PATCH 191/196] Significant change in --setup parameter (#207) --- README.md | 4 ++-- src/args.rs | 5 +++-- src/general_api.rs | 10 +--------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 5c9b591..00537af 100644 --- a/README.md +++ b/README.md @@ -149,8 +149,8 @@ Options: --unshare-pidfile Create a pidfile of `unshare` process when using `--unshare` -6, --ipv6-enabled IPv6 enabled -s, --setup Routing and system setup, which decides whether to setup the routing and system - configuration. This option is only available on Linux and requires root-like privileges. - See `capabilities(7)` + configuration. This option requires root-like privileges on every platform. + It is very important on Linux, see `capabilities(7)` -d, --dns DNS handling strategy [default: direct] [possible values: virtual, over-tcp, direct] --dns-addr DNS resolver address [default: 8.8.8.8] --virtual-dns-pool IP address pool to be used by virtual DNS in CIDR notation [default: 198.18.0.0/15] diff --git a/src/args.rs b/src/args.rs index 02f5ec4..2b74748 100644 --- a/src/args.rs +++ b/src/args.rs @@ -76,8 +76,9 @@ pub struct Args { pub ipv6_enabled: bool, /// Routing and system setup, which decides whether to setup the routing and system configuration. - /// This option is only available on Linux and requires root-like privileges. See `capabilities(7)`. - #[arg(short, long, default_value = if cfg!(target_os = "linux") { "false" } else { "true" })] + /// This option requires root-like privileges on every platform. + /// It is very important on Linux, see `capabilities(7)`. + #[arg(short, long)] pub setup: bool, /// DNS handling strategy diff --git a/src/general_api.rs b/src/general_api.rs index 9e7dd3c..f359b73 100644 --- a/src/general_api.rs +++ b/src/general_api.rs @@ -196,9 +196,6 @@ pub async fn general_run_async( .bypass_ips(&args.bypass) .ipv6_default_route(args.ipv6_enabled); - #[allow(unused_mut, unused_assignments, unused_variables)] - let mut setup = true; - let device = tun::create_as_async(&tun_config)?; #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] @@ -212,13 +209,8 @@ pub async fn general_run_async( #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] let mut _restore: Option = None; - #[cfg(target_os = "linux")] - { - setup = args.setup; - } - #[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))] - if setup { + if args.setup { _restore = Some(tproxy_config::tproxy_setup(&tproxy_args)?); } From ddebf5ee50a5df1b96101d62cd8bd6ca13feedb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 12:02:31 +0800 Subject: [PATCH 192/196] Update tun requirement from 0.7 to 0.8 (#209) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 3fcc22d..abad68b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ thiserror = "2" tokio = { version = "1", features = ["full"] } tokio-util = "0.7" tproxy-config = { version = "6", default-features = false } -tun = { version = "0.7", features = ["async"] } +tun = { version = "0.8", features = ["async"] } udp-stream = { version = "0.0.12", default-features = false } unicase = "2" url = "2" From 88d31ce16868cc0dff228aa6171a9aa481c4d5a8 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Tue, 3 Jun 2025 14:05:28 +0800 Subject: [PATCH 193/196] Bump version 0.7.10 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index abad68b..10f3488 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tun2proxy" -version = "0.7.9" +version = "0.7.10" edition = "2024" license = "MIT" repository = "https://github.com/tun2proxy/tun2proxy" From fbc47a3001148d0f9f069cfdd4a8a2b274e99505 Mon Sep 17 00:00:00 2001 From: "B. Blechschmidt" Date: Tue, 10 Jun 2025 23:00:36 +0200 Subject: [PATCH 194/196] fix(ci): account for change in load_dotenv --- .github/workflows/tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 51f65cf..32d02f5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -19,7 +19,9 @@ jobs: - name: Populate .env env: DOTENV: ${{ secrets.DOTENV }} - run: echo "$DOTENV" > .env + run: | + echo "$DOTENV" > tests/.env + ln -s tests/.env - name: Set up Python uses: actions/setup-python@v2 @@ -36,7 +38,7 @@ jobs: - name: Build project run: cargo build --release - + - name: Run tests run: | source venv/bin/activate From 8b4ecabd8f831a83a09d973bcefb2f784831642e Mon Sep 17 00:00:00 2001 From: Paper-Dragon <2678885646@qq.com> Date: Wed, 11 Jun 2025 14:12:02 +0800 Subject: [PATCH 195/196] build image based on alpine/musl (#212) --- .github/workflows/publish-docker.yml | 15 +++++++++++++-- Dockerfile.alpine | 18 ++++++++++++++++++ Dockerfile => Dockerfile.ubuntu | 0 README.md | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 Dockerfile.alpine rename Dockerfile => Dockerfile.ubuntu (100%) diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml index 10db660..a848c80 100644 --- a/.github/workflows/publish-docker.yml +++ b/.github/workflows/publish-docker.yml @@ -50,11 +50,22 @@ jobs: # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - - name: Build and push Docker image + - name: Build gnu and push Docker image uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 with: platforms: linux/amd64,linux/arm64 context: . + file: Dockerfile.ubuntu push: true - tags: ${{ steps.meta.outputs.tags }} + tags: ${{ steps.meta.outputs.tags }}-ubuntu labels: ${{ steps.meta.outputs.labels }} + + - name: Build musl and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + platforms: linux/amd64 + context: . + file: Dockerfile.alpine + push: true + tags: ${{ steps.meta.outputs.tags }}-alpine + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/Dockerfile.alpine b/Dockerfile.alpine new file mode 100644 index 0000000..570b819 --- /dev/null +++ b/Dockerfile.alpine @@ -0,0 +1,18 @@ +#################################################################################################### +## Builder +#################################################################################################### +FROM rust:latest AS builder +WORKDIR /worker +COPY ./ . +RUN rustup target add x86_64-unknown-linux-musl +RUN cargo build --release --target x86_64-unknown-linux-musl + +#################################################################################################### +## Final image +#################################################################################################### +FROM alpine:latest +RUN apk add --no-cache iproute2 + +COPY --from=builder /worker/target/x86_64-unknown-linux-musl/release/tun2proxy-bin /usr/bin/tun2proxy-bin + +ENTRYPOINT ["/usr/bin/tun2proxy-bin", "--setup"] diff --git a/Dockerfile b/Dockerfile.ubuntu similarity index 100% rename from Dockerfile rename to Dockerfile.ubuntu diff --git a/README.md b/README.md index 00537af..18be7ba 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ services: cap_add: - NET_ADMIN container_name: tun2proxy - image: ghcr.io/tun2proxy/tun2proxy:latest + image: ghcr.io/tun2proxy/tun2proxy:latest-ubuntu command: --proxy proto://[username[:password]@]host:port alpine: stdin_open: true From 1880396822866fcf753d85f1b56881f6e8bf70e9 Mon Sep 17 00:00:00 2001 From: ssrlive <30760636+ssrlive@users.noreply.github.com> Date: Sat, 14 Jun 2025 08:30:45 +0800 Subject: [PATCH 196/196] use ctrlc2 async feature --- Cargo.toml | 2 +- src/bin/main.rs | 8 ++++---- src/bin/udpgw_server.rs | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 10f3488..f3b06b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ async-trait = "0.1" base64easy = "0.1" chrono = "0.4" clap = { version = "4", features = ["derive", "wrap_help", "color"] } -ctrlc2 = { version = "3", features = ["tokio", "termination"] } +ctrlc2 = { version = "3.6.5", features = ["async", "termination"] } digest_auth = "0.3" dotenvy = "0.15" env_logger = "0.11" diff --git a/src/bin/main.rs b/src/bin/main.rs index 102b6a3..7bf00ef 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -64,18 +64,18 @@ async fn main_async(args: Args) -> Result<(), BoxError> { let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let ctrlc_fired_clone = ctrlc_fired.clone(); - let ctrlc_handel = ctrlc2::set_async_handler(async move { + let ctrlc_handel = ctrlc2::AsyncCtrlC::new(move || { log::info!("Ctrl-C received, exiting..."); ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst); shutdown_token.cancel(); - }) - .await; + true + })?; let tasks = main_loop_handle.await??; if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) { log::info!("Ctrl-C fired, waiting the handler to finish..."); - ctrlc_handel.await.map_err(|err| err.to_string())?; + ctrlc_handel.await?; } if args.exit_on_fatal_error && tasks >= args.max_sessions { diff --git a/src/bin/udpgw_server.rs b/src/bin/udpgw_server.rs index 05f39a6..2a8f91d 100644 --- a/src/bin/udpgw_server.rs +++ b/src/bin/udpgw_server.rs @@ -205,18 +205,18 @@ async fn main_async(args: UdpGwArgs) -> Result<(), BoxError> { let ctrlc_fired = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)); let ctrlc_fired_clone = ctrlc_fired.clone(); - let ctrlc_handel = ctrlc2::set_async_handler(async move { + let ctrlc_handel = ctrlc2::AsyncCtrlC::new(move || { log::info!("Ctrl-C received, exiting..."); ctrlc_fired_clone.store(true, std::sync::atomic::Ordering::SeqCst); shutdown_token.cancel(); - }) - .await; + true + })?; let _ = main_loop_handle.await?; if ctrlc_fired.load(std::sync::atomic::Ordering::SeqCst) { log::info!("Ctrl-C fired, waiting the handler to finish..."); - ctrlc_handel.await.map_err(|err| err.to_string())?; + ctrlc_handel.await?; } Ok(())