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 001/111] 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 002/111] 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 003/111] 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 004/111] 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 005/111] 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 006/111] 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 007/111] 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 008/111] 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 009/111] 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 010/111] 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 011/111] 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 012/111] 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 013/111] 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 014/111] 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 015/111] 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 016/111] 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 017/111] 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 018/111] 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 019/111] 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 020/111] 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 021/111] 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 022/111] 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 023/111] 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 024/111] 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 025/111] 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 026/111] 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 027/111] 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 028/111] 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 029/111] 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 030/111] 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 031/111] 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 032/111] 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 033/111] 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 034/111] 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 035/111] 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 036/111] --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 037/111] 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 038/111] --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 039/111] 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 040/111] 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 041/111] 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 042/111] 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 043/111] 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 044/111] 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 045/111] 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 046/111] 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 047/111] 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 048/111] 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 049/111] 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 050/111] 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 051/111] 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 052/111] 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 053/111] 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 054/111] 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 055/111] 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 056/111] 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 057/111] 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 058/111] 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 059/111] 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 060/111] 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 061/111] 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 062/111] 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 063/111] 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 064/111] 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 065/111] 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 066/111] 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 067/111] 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 068/111] 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 069/111] 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 070/111] 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 071/111] 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 072/111] 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 073/111] 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 074/111] 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 075/111] 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 076/111] 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 077/111] 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 078/111] 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 079/111] 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 080/111] 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 081/111] 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 082/111] 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 083/111] 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 084/111] 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 085/111] 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 086/111] 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 087/111] 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 088/111] 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 089/111] 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 090/111] 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 091/111] 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 092/111] 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 093/111] 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 094/111] 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 095/111] 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 096/111] 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 097/111] 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 098/111] 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 099/111] 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 100/111] 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 101/111] 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 102/111] 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 103/111] 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 104/111] 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 105/111] 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 106/111] 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 107/111] 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 108/111] 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 109/111] 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 110/111] 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 111/111] 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(())