From fe265d1649f156a5e38ba84a14e2b096ce211a43 Mon Sep 17 00:00:00 2001 From: Yuwei Ba Date: Thu, 19 Sep 2024 18:53:15 +1000 Subject: [PATCH 1/2] feat(arm): support windows arm (#595) --- .github/workflows/ci.yml | 19 ++++++++++++++++--- .../tests/data/docker}/docker-compose.yml | 0 .../tests/data/docker}/nginx/nginx.conf | 0 .../tests/data/docker}/ss/Dockerfile | 0 .../tests/data/docker}/v2ray/cert.pem | 0 .../tests/data/docker}/v2ray/config.json | 0 .../tests/data/docker}/v2ray/key.pem | 0 .../tests/data/docker}/wg/wg0.conf | 0 clash_lib/Cargo.toml | 6 +++--- clash_lib/src/app/outbound/manager.rs | 5 ++++- .../proxy_provider/proxy_set_provider.rs | 5 ++++- clash_lib/src/config/internal/proxy.rs | 3 +++ clash_lib/src/proxy/converters/mod.rs | 1 + clash_lib/src/proxy/mod.rs | 1 + 14 files changed, 32 insertions(+), 8 deletions(-) rename {docker => clash/tests/data/docker}/docker-compose.yml (100%) rename {docker => clash/tests/data/docker}/nginx/nginx.conf (100%) rename {docker => clash/tests/data/docker}/ss/Dockerfile (100%) rename {docker => clash/tests/data/docker}/v2ray/cert.pem (100%) rename {docker => clash/tests/data/docker}/v2ray/config.json (100%) rename {docker => clash/tests/data/docker}/v2ray/key.pem (100%) rename {docker => clash/tests/data/docker}/wg/wg0.conf (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da5060d7f..72fd75a8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ env: IMAGE_NAME: "clash-rs" +# Arm builder https://github.blog/changelog/2024-09-03-github-actions-arm64-linux-and-windows-runners-are-now-generally-available/ jobs: compile: name: ${{ matrix.release-name || matrix.target || 'Unknown' }} @@ -114,6 +115,11 @@ jobs: cross: false postfix: ".exe" extra-args: "--all-features" + - os: windows-latest + target: aarch64-pc-windows-msvc + cross: false + postfix: ".exe" + extra-args: --features "shadowsocks tuic" # Windows static-crt - os: windows-latest target: x86_64-pc-windows-msvc @@ -129,6 +135,13 @@ jobs: postfix: ".exe" extra-args: "--all-features" rustflags: "-Ctarget-feature=+crt-static --cfg tokio_unstable" + - os: windows-latest + target: aarch64-pc-windows-msvc + release-name: aarch64-pc-windows-msvc-static-crt + cross: false + postfix: ".exe" + extra-args: --features "shadowsocks tuic" + rustflags: "-Ctarget-feature=+crt-static --cfg tokio_unstable" # MacOSX - os: macos-12 target: x86_64-apple-darwin @@ -195,7 +208,7 @@ jobs: env: RUSTFLAGS: ${{ matrix.rustflags || '--cfg tokio_unstable' }} - - name: Cargo test + - name: Cargo test (docker test on linux) uses: clechasseur/rs-cargo@v2 if: startsWith(matrix.os, 'ubuntu') with: @@ -206,9 +219,9 @@ jobs: CROSS_CONTAINER_OPTS: "--network host" RUSTFLAGS: ${{ matrix.rustflags || '--cfg tokio_unstable' }} - - name: Cargo test (no docker test) + - name: Cargo test (no docker test on windows-non-arm and macos) uses: clechasseur/rs-cargo@v2 - if: ${{ !startsWith(matrix.os, 'ubuntu') }} + if: ${{ !startsWith(matrix.os, 'ubuntu') && matrix.target != 'aarch64-pc-windows-msvc' }} with: use-cross: ${{ matrix.cross }} command: test diff --git a/docker/docker-compose.yml b/clash/tests/data/docker/docker-compose.yml similarity index 100% rename from docker/docker-compose.yml rename to clash/tests/data/docker/docker-compose.yml diff --git a/docker/nginx/nginx.conf b/clash/tests/data/docker/nginx/nginx.conf similarity index 100% rename from docker/nginx/nginx.conf rename to clash/tests/data/docker/nginx/nginx.conf diff --git a/docker/ss/Dockerfile b/clash/tests/data/docker/ss/Dockerfile similarity index 100% rename from docker/ss/Dockerfile rename to clash/tests/data/docker/ss/Dockerfile diff --git a/docker/v2ray/cert.pem b/clash/tests/data/docker/v2ray/cert.pem similarity index 100% rename from docker/v2ray/cert.pem rename to clash/tests/data/docker/v2ray/cert.pem diff --git a/docker/v2ray/config.json b/clash/tests/data/docker/v2ray/config.json similarity index 100% rename from docker/v2ray/config.json rename to clash/tests/data/docker/v2ray/config.json diff --git a/docker/v2ray/key.pem b/clash/tests/data/docker/v2ray/key.pem similarity index 100% rename from docker/v2ray/key.pem rename to clash/tests/data/docker/v2ray/key.pem diff --git a/docker/wg/wg0.conf b/clash/tests/data/docker/wg/wg0.conf similarity index 100% rename from docker/wg/wg0.conf rename to clash/tests/data/docker/wg/wg0.conf diff --git a/clash_lib/Cargo.toml b/clash_lib/Cargo.toml index e8d301ae7..76a303576 100644 --- a/clash_lib/Cargo.toml +++ b/clash_lib/Cargo.toml @@ -10,7 +10,7 @@ shadowsocks = ["dep:shadowsocks"] tuic = ["dep:tuic", "dep:tuic-quinn", "dep:quinn", "dep:register-count"] tracing = [] bench = ["dep:criterion"] -onion = ["arti-client/onion-service-client"] +onion = ["dep:arti-client", "dep:tor-rtcompat", "arti-client/onion-service-client"] [dependencies] # Async @@ -117,8 +117,8 @@ maxminddb = "0.24" public-suffix = "0.1" murmur3 = "0.5" -arti-client = { version = "0.22", default-features = false, features = ["tokio", "rustls", "static-sqlite"] } -tor-rtcompat = { version = "0.22", default-features = false } +arti-client = { version = "0.22", optional = true, default-features = false, features = ["tokio", "rustls", "static-sqlite"] } +tor-rtcompat = { version = "0.22", optional = true, default-features = false } # tuic tuic = { tag = "v1.2.0", optional = true, git = "https://github.com/Itsusinn/tuic.git" } diff --git a/clash_lib/src/app/outbound/manager.rs b/clash_lib/src/app/outbound/manager.rs index 05dffccb0..a979654b8 100644 --- a/clash_lib/src/app/outbound/manager.rs +++ b/clash_lib/src/app/outbound/manager.rs @@ -25,7 +25,7 @@ use crate::{ OutboundProxyProviderDef, PROXY_DIRECT, PROXY_GLOBAL, PROXY_REJECT, }, proxy::{ - fallback, loadbalance, selector, socks, tor, trojan, + fallback, loadbalance, selector, socks, trojan, utils::{DirectConnector, ProxyConnector}, vmess, wg, OutboundType, }, @@ -44,6 +44,8 @@ use super::utils::proxy_groups_dag_sort; #[cfg(feature = "shadowsocks")] use crate::proxy::shadowsocks; +#[cfg(feature = "onion")] +use crate::proxy::tor; #[cfg(feature = "tuic")] use crate::proxy::tuic; @@ -275,6 +277,7 @@ impl OutboundManager { }); } + #[cfg(feature = "onion")] OutboundProxyProtocol::Tor(tor) => { handlers.insert(tor.name.clone(), { let h: tor::Handler = tor.try_into()?; diff --git a/clash_lib/src/app/remote_content_manager/providers/proxy_provider/proxy_set_provider.rs b/clash_lib/src/app/remote_content_manager/providers/proxy_provider/proxy_set_provider.rs index ba9c24bc7..927746e6e 100644 --- a/clash_lib/src/app/remote_content_manager/providers/proxy_provider/proxy_set_provider.rs +++ b/clash_lib/src/app/remote_content_manager/providers/proxy_provider/proxy_set_provider.rs @@ -18,12 +18,14 @@ use crate::{ }, common::errors::map_io_error, config::internal::proxy::OutboundProxyProtocol, - proxy::{direct, reject, socks, tor, trojan, vmess, wg, AnyOutboundHandler}, + proxy::{direct, reject, socks, trojan, vmess, wg, AnyOutboundHandler}, Error, }; #[cfg(feature = "shadowsocks")] use crate::proxy::shadowsocks; +#[cfg(feature = "onion")] +use crate::proxy::tor; #[cfg(feature = "tuic")] use crate::proxy::tuic; @@ -139,6 +141,7 @@ impl ProxySetProvider { let h: wg::Handler = wg.try_into()?; Ok(Arc::new(h) as _) } + #[cfg(feature = "onion")] OutboundProxyProtocol::Tor(tor) => { let h: tor::Handler = tor.try_into()?; Ok(Arc::new(h) as _) diff --git a/clash_lib/src/config/internal/proxy.rs b/clash_lib/src/config/internal/proxy.rs index 9f5a0b705..ea8067650 100644 --- a/clash_lib/src/config/internal/proxy.rs +++ b/clash_lib/src/config/internal/proxy.rs @@ -62,6 +62,7 @@ pub enum OutboundProxyProtocol { Vmess(OutboundVmess), #[serde(rename = "wireguard")] Wireguard(OutboundWireguard), + #[cfg(feature = "onion")] #[serde(rename = "tor")] Tor(OutboundTor), #[cfg(feature = "tuic")] @@ -82,6 +83,7 @@ impl OutboundProxyProtocol { OutboundProxyProtocol::Wireguard(wireguard) => { &wireguard.common_opts.name } + #[cfg(feature = "onion")] OutboundProxyProtocol::Tor(tor) => &tor.name, #[cfg(feature = "tuic")] OutboundProxyProtocol::Tuic(tuic) => &tuic.common_opts.name, @@ -116,6 +118,7 @@ impl Display for OutboundProxyProtocol { OutboundProxyProtocol::Trojan(_) => write!(f, "Trojan"), OutboundProxyProtocol::Vmess(_) => write!(f, "Vmess"), OutboundProxyProtocol::Wireguard(_) => write!(f, "Wireguard"), + #[cfg(feature = "onion")] OutboundProxyProtocol::Tor(_) => write!(f, "Tor"), #[cfg(feature = "tuic")] OutboundProxyProtocol::Tuic(_) => write!(f, "Tuic"), diff --git a/clash_lib/src/proxy/converters/mod.rs b/clash_lib/src/proxy/converters/mod.rs index 25b3ddfb3..45eb25e00 100644 --- a/clash_lib/src/proxy/converters/mod.rs +++ b/clash_lib/src/proxy/converters/mod.rs @@ -1,6 +1,7 @@ #[cfg(feature = "shadowsocks")] pub mod shadowsocks; pub mod socks5; +#[cfg(feature = "onion")] pub mod tor; pub mod trojan; #[cfg(feature = "tuic")] diff --git a/clash_lib/src/proxy/mod.rs b/clash_lib/src/proxy/mod.rs index bcaff489d..f4850f9e2 100644 --- a/clash_lib/src/proxy/mod.rs +++ b/clash_lib/src/proxy/mod.rs @@ -35,6 +35,7 @@ pub mod converters; #[cfg(feature = "shadowsocks")] pub mod shadowsocks; pub mod socks; +#[cfg(feature = "onion")] pub mod tor; pub mod trojan; #[cfg(feature = "tuic")] From 96374d68bedf36cf93993ba779954f6b712cb0fa Mon Sep 17 00:00:00 2001 From: Yuwei Ba Date: Thu, 19 Sep 2024 18:53:31 +1000 Subject: [PATCH 2/2] feat(tun): route all on linux (#594) --- .cargo/config.toml | 2 +- clash/src/main.rs | 5 +- clash/tests/data/config/rules.yaml | 1 + clash_lib/src/app/logging.rs | 32 +--- clash_lib/src/app/outbound/manager.rs | 20 +- clash_lib/src/config/def.rs | 4 + clash_lib/src/config/internal/config.rs | 4 + clash_lib/src/config/internal/proxy.rs | 14 +- clash_lib/src/proxy/converters/shadowsocks.rs | 4 +- clash_lib/src/proxy/converters/socks5.rs | 4 +- clash_lib/src/proxy/converters/trojan.rs | 4 +- clash_lib/src/proxy/converters/tuic.rs | 4 +- clash_lib/src/proxy/converters/vmess.rs | 4 +- clash_lib/src/proxy/converters/wireguard.rs | 4 +- clash_lib/src/proxy/direct/mod.rs | 8 +- clash_lib/src/proxy/fallback/mod.rs | 4 +- clash_lib/src/proxy/loadbalance/mod.rs | 4 +- clash_lib/src/proxy/mod.rs | 13 +- clash_lib/src/proxy/options.rs | 6 +- clash_lib/src/proxy/relay/mod.rs | 4 +- clash_lib/src/proxy/selector/mod.rs | 4 +- clash_lib/src/proxy/shadowsocks/mod.rs | 51 ++---- clash_lib/src/proxy/socks/inbound/stream.rs | 2 +- clash_lib/src/proxy/socks/outbound/mod.rs | 16 +- clash_lib/src/proxy/trojan/mod.rs | 14 +- clash_lib/src/proxy/tuic/mod.rs | 30 +-- clash_lib/src/proxy/tun/inbound.rs | 31 +++- clash_lib/src/proxy/tun/mod.rs | 166 +++++++++++++++++ clash_lib/src/proxy/tun/routes/linux.rs | 171 +++++++++++++++++- clash_lib/src/proxy/tun/routes/macos.rs | 3 +- clash_lib/src/proxy/tun/routes/mod.rs | 46 +++-- clash_lib/src/proxy/tun/routes/windows.rs | 9 +- clash_lib/src/proxy/urltest/mod.rs | 4 +- clash_lib/src/proxy/utils/platform/win.rs | 2 +- clash_lib/src/proxy/utils/proxy_connector.rs | 24 +-- clash_lib/src/proxy/utils/socket_helpers.rs | 12 +- clash_lib/src/proxy/vmess/mod.rs | 14 +- clash_lib/src/proxy/wg/mod.rs | 15 +- clash_lib/src/proxy/wg/wireguard.rs | 7 +- clash_lib/src/session.rs | 8 +- 40 files changed, 568 insertions(+), 206 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 13c84ae1c..20711f1b9 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,4 +3,4 @@ target-dir = "target" rustflags = ["--cfg", "tokio_unstable"] [env] -RUST_LOG = { value = "clash=trace" } +RUST_LOG = { value = "clash=trace" } \ No newline at end of file diff --git a/clash/src/main.rs b/clash/src/main.rs index c95f1f601..f0226bd74 100644 --- a/clash/src/main.rs +++ b/clash/src/main.rs @@ -31,6 +31,9 @@ struct Cli { help = "Test configuration and exit" )] test_config: bool, + + #[clap(short, long, help = "Additinally log to file")] + log_file: Option, } fn main() { @@ -64,7 +67,7 @@ fn main() { config: clash::Config::File(file), cwd: cli.directory.map(|x| x.to_string_lossy().to_string()), rt: Some(TokioRuntime::MultiThread), - log_file: None, + log_file: cli.log_file, }) { Ok(_) => {} Err(_) => { diff --git a/clash/tests/data/config/rules.yaml b/clash/tests/data/config/rules.yaml index 0819cf0de..1ea9cbf46 100644 --- a/clash/tests/data/config/rules.yaml +++ b/clash/tests/data/config/rules.yaml @@ -8,6 +8,7 @@ tun: device-id: "dev://utun1989" route-all: true gateway: "198.19.0.1/32" + so-mark: 3389 # routes: # - 0.0.0.0/1 # - 128.0.0.0/1 diff --git a/clash_lib/src/app/logging.rs b/clash_lib/src/app/logging.rs index f7e732948..9903db2d5 100644 --- a/clash_lib/src/app/logging.rs +++ b/clash_lib/src/app/logging.rs @@ -15,7 +15,7 @@ use serde::Serialize; use tokio::sync::broadcast::Sender; use tracing::{debug, error}; -use tracing_appender::non_blocking::{NonBlocking, WorkerGuard}; +use tracing_appender::non_blocking::WorkerGuard; use tracing_oslog::OsLogger; use tracing_subscriber::{filter, filter::Directive, prelude::*, EnvFilter, Layer}; @@ -76,24 +76,6 @@ where } } -struct W(Option); - -impl std::io::Write for W { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - match self.0 { - Some(ref mut w) => w.write(buf), - None => Ok(buf.len()), - } - } - - fn flush(&mut self) -> std::io::Result<()> { - match self.0 { - Some(ref mut w) => w.flush(), - None => Ok(()), - } - } -} - pub fn setup_logging( level: LogLevel, collector: EventCollector, @@ -165,6 +147,15 @@ pub fn setup_logging( .with(filter) .with(collector) .with(console_layer) + .with(appender.map(|x| { + tracing_subscriber::fmt::Layer::new() + .with_ansi(false) + .compact() + .with_file(true) + .with_line_number(true) + .with_level(true) + .with_writer(x) + })) .with( tracing_subscriber::fmt::Layer::new() .with_ansi(std::io::stdout().is_terminal()) @@ -174,9 +165,6 @@ pub fn setup_logging( .with_line_number(true) .with_level(true) .with_thread_ids(true) - .with_writer(move || -> Box { - Box::new(W(appender.clone())) - }) .with_writer(std::io::stdout), ) .with(ios_os_log); diff --git a/clash_lib/src/app/outbound/manager.rs b/clash_lib/src/app/outbound/manager.rs index a979654b8..230eaa459 100644 --- a/clash_lib/src/app/outbound/manager.rs +++ b/clash_lib/src/app/outbound/manager.rs @@ -393,8 +393,9 @@ impl OutboundManager { let relay = relay::Handler::new( relay::HandlerOptions { name: proto.name.clone(), - shared_opts: crate::proxy::HandlerSharedOptions { + common_opts: crate::proxy::HandlerCommonOptions { icon: proto.icon.clone(), + ..Default::default() }, }, providers, @@ -446,8 +447,9 @@ impl OutboundManager { let url_test = urltest::Handler::new( urltest::HandlerOptions { name: proto.name.clone(), - shared_opts: crate::proxy::HandlerSharedOptions { + common_opts: crate::proxy::HandlerCommonOptions { icon: proto.icon.clone(), + ..Default::default() }, ..Default::default() }, @@ -502,8 +504,9 @@ impl OutboundManager { let fallback = fallback::Handler::new( fallback::HandlerOptions { name: proto.name.clone(), - shared_opts: crate::proxy::HandlerSharedOptions { + common_opts: crate::proxy::HandlerCommonOptions { icon: proto.icon.clone(), + ..Default::default() }, ..Default::default() }, @@ -557,8 +560,9 @@ impl OutboundManager { let load_balance = loadbalance::Handler::new( loadbalance::HandlerOptions { name: proto.name.clone(), - shared_opts: crate::proxy::HandlerSharedOptions { + common_opts: crate::proxy::HandlerCommonOptions { icon: proto.icon.clone(), + ..Default::default() }, ..Default::default() }, @@ -616,8 +620,9 @@ impl OutboundManager { selector::HandlerOptions { name: proto.name.clone(), udp: proto.udp.unwrap_or(true), - shared_opts: crate::proxy::HandlerSharedOptions { + common_opts: crate::proxy::HandlerCommonOptions { icon: proto.icon.clone(), + ..Default::default() }, }, providers, @@ -654,7 +659,10 @@ impl OutboundManager { selector::HandlerOptions { name: PROXY_GLOBAL.to_owned(), udp: true, - shared_opts: crate::proxy::HandlerSharedOptions { icon: None }, + common_opts: crate::proxy::HandlerCommonOptions { + icon: None, + ..Default::default() + }, }, vec![pd.clone()], stored_selection, diff --git a/clash_lib/src/config/def.rs b/clash_lib/src/config/def.rs index 4e6ca7c69..484451760 100644 --- a/clash_lib/src/config/def.rs +++ b/clash_lib/src/config/def.rs @@ -20,6 +20,10 @@ pub struct TunConfig { #[serde(default)] pub route_all: bool, pub mtu: Option, + /// fwmark on Linux only + pub so_mark: Option, + /// policy routing table on Linux only + pub route_table: Option, } #[derive(Serialize, Deserialize, Default, Copy, Clone)] diff --git a/clash_lib/src/config/internal/config.rs b/clash_lib/src/config/internal/config.rs index f8dd70489..1bb484db2 100644 --- a/clash_lib/src/config/internal/config.rs +++ b/clash_lib/src/config/internal/config.rs @@ -118,6 +118,8 @@ impl TryFrom for Config { Error::InvalidConfig(format!("parse tun gateway: {}", x)) })?, mtu: t.mtu, + so_mark: t.so_mark, + route_table: t.route_table, }, None => TunConfig::default(), }, @@ -300,6 +302,8 @@ pub struct TunConfig { pub routes: Vec, pub gateway: IpNet, pub mtu: Option, + pub so_mark: Option, + pub route_table: Option, } #[derive(Clone, Default)] diff --git a/clash_lib/src/config/internal/proxy.rs b/clash_lib/src/config/internal/proxy.rs index ea8067650..a0579ac28 100644 --- a/clash_lib/src/config/internal/proxy.rs +++ b/clash_lib/src/config/internal/proxy.rs @@ -128,7 +128,7 @@ impl Display for OutboundProxyProtocol { #[derive(serde::Serialize, serde::Deserialize, Debug, Default, Clone)] #[serde(rename_all = "kebab-case")] -pub struct CommonOption { +pub struct CommonConfigOptions { pub name: String, pub server: String, pub port: u16, @@ -144,7 +144,7 @@ pub struct CommonOption { #[serde(rename_all = "kebab-case")] pub struct OutboundShadowsocks { #[serde(flatten)] - pub common_opts: CommonOption, + pub common_opts: CommonConfigOptions, pub cipher: String, pub password: String, #[serde(default = "default_bool_true")] @@ -157,7 +157,7 @@ pub struct OutboundShadowsocks { #[serde(rename_all = "kebab-case")] pub struct OutboundSocks5 { #[serde(flatten)] - pub common_opts: CommonOption, + pub common_opts: CommonConfigOptions, pub username: Option, pub password: Option, #[serde(default = "Default::default")] @@ -194,7 +194,7 @@ pub struct GrpcOpt { #[serde(rename_all = "kebab-case")] pub struct OutboundTrojan { #[serde(flatten)] - pub common_opts: CommonOption, + pub common_opts: CommonConfigOptions, pub password: String, pub alpn: Option>, pub sni: Option, @@ -209,7 +209,7 @@ pub struct OutboundTrojan { #[serde(rename_all = "kebab-case")] pub struct OutboundVmess { #[serde(flatten)] - pub common_opts: CommonOption, + pub common_opts: CommonConfigOptions, pub uuid: String, #[serde(alias = "alterId")] pub alter_id: u16, @@ -229,7 +229,7 @@ pub struct OutboundVmess { #[serde(rename_all = "kebab-case")] pub struct OutboundWireguard { #[serde(flatten)] - pub common_opts: CommonOption, + pub common_opts: CommonConfigOptions, pub private_key: String, pub public_key: String, pub preshared_key: Option, @@ -253,7 +253,7 @@ pub struct OutboundTor { #[serde(rename_all = "kebab-case")] pub struct OutboundTuic { #[serde(flatten)] - pub common_opts: CommonOption, + pub common_opts: CommonConfigOptions, pub uuid: Uuid, pub password: String, /// override field 'server' dns record, not used for now diff --git a/clash_lib/src/proxy/converters/shadowsocks.rs b/clash_lib/src/proxy/converters/shadowsocks.rs index 28e448c39..5d6fe5493 100644 --- a/clash_lib/src/proxy/converters/shadowsocks.rs +++ b/clash_lib/src/proxy/converters/shadowsocks.rs @@ -7,7 +7,7 @@ use crate::{ Handler, HandlerOptions, OBFSOption, ShadowTlsOption, SimpleOBFSMode, SimpleOBFSOption, V2RayOBFSOption, }, - CommonOption, + HandlerCommonOptions, }, Error, }; @@ -26,7 +26,7 @@ impl TryFrom<&OutboundShadowsocks> for Handler { fn try_from(s: &OutboundShadowsocks) -> Result { let h = Handler::new(HandlerOptions { name: s.common_opts.name.to_owned(), - common_opts: CommonOption { + common_opts: HandlerCommonOptions { connector: s.common_opts.connect_via.clone(), ..Default::default() }, diff --git a/clash_lib/src/proxy/converters/socks5.rs b/clash_lib/src/proxy/converters/socks5.rs index fe1dd275c..b9600bf47 100644 --- a/clash_lib/src/proxy/converters/socks5.rs +++ b/clash_lib/src/proxy/converters/socks5.rs @@ -2,7 +2,7 @@ use crate::{ config::internal::proxy::OutboundSocks5, proxy::{ socks::{Handler, HandlerOptions}, - CommonOption, + HandlerCommonOptions, }, }; @@ -20,7 +20,7 @@ impl TryFrom<&OutboundSocks5> for Handler { fn try_from(s: &OutboundSocks5) -> Result { let h = Handler::new(HandlerOptions { name: s.common_opts.name.to_owned(), - common_opts: CommonOption { + common_opts: HandlerCommonOptions { connector: s.common_opts.connect_via.clone(), ..Default::default() }, diff --git a/clash_lib/src/proxy/converters/trojan.rs b/clash_lib/src/proxy/converters/trojan.rs index 11152340d..6ff691567 100644 --- a/clash_lib/src/proxy/converters/trojan.rs +++ b/clash_lib/src/proxy/converters/trojan.rs @@ -5,7 +5,7 @@ use crate::{ proxy::{ options::{GrpcOption, WsOption}, trojan::{Handler, HandlerOptions, Transport}, - CommonOption, + HandlerCommonOptions, }, Error, }; @@ -32,7 +32,7 @@ impl TryFrom<&OutboundTrojan> for Handler { let h = Handler::new(HandlerOptions { name: s.common_opts.name.to_owned(), - common_opts: CommonOption { + common_opts: HandlerCommonOptions { connector: s.common_opts.connect_via.clone(), ..Default::default() }, diff --git a/clash_lib/src/proxy/converters/tuic.rs b/clash_lib/src/proxy/converters/tuic.rs index cc06c2bbf..c7015043b 100644 --- a/clash_lib/src/proxy/converters/tuic.rs +++ b/clash_lib/src/proxy/converters/tuic.rs @@ -6,7 +6,7 @@ use crate::{ config::internal::proxy::OutboundTuic, proxy::{ tuic::{types::CongestionControl, Handler, HandlerOptions}, - CommonOption, + HandlerCommonOptions, }, }; @@ -25,7 +25,7 @@ impl TryFrom<&OutboundTuic> for Handler { Ok(Handler::new(HandlerOptions { name: s.common_opts.name.to_owned(), server: s.common_opts.server.to_owned(), - common_opts: CommonOption { + common_opts: HandlerCommonOptions { connector: s.common_opts.connect_via.clone(), ..Default::default() }, diff --git a/clash_lib/src/proxy/converters/vmess.rs b/clash_lib/src/proxy/converters/vmess.rs index 4579060ca..d133e320f 100644 --- a/clash_lib/src/proxy/converters/vmess.rs +++ b/clash_lib/src/proxy/converters/vmess.rs @@ -6,7 +6,7 @@ use crate::{ options::{GrpcOption, Http2Option, WsOption}, transport::TLSOptions, vmess::{Handler, HandlerOptions, VmessTransport}, - CommonOption, + HandlerCommonOptions, }, Error, }; @@ -33,7 +33,7 @@ impl TryFrom<&OutboundVmess> for Handler { let h = Handler::new(HandlerOptions { name: s.common_opts.name.to_owned(), - common_opts: CommonOption { + common_opts: HandlerCommonOptions { connector: s.common_opts.connect_via.clone(), ..Default::default() }, diff --git a/clash_lib/src/proxy/converters/wireguard.rs b/clash_lib/src/proxy/converters/wireguard.rs index c1589d108..bf1b45671 100644 --- a/clash_lib/src/proxy/converters/wireguard.rs +++ b/clash_lib/src/proxy/converters/wireguard.rs @@ -4,7 +4,7 @@ use crate::{ config::internal::proxy::OutboundWireguard, proxy::{ wg::{Handler, HandlerOptions}, - CommonOption, + HandlerCommonOptions, }, Error, }; @@ -23,7 +23,7 @@ impl TryFrom<&OutboundWireguard> for Handler { fn try_from(s: &OutboundWireguard) -> Result { let h = Handler::new(HandlerOptions { name: s.common_opts.name.to_owned(), - common_opts: CommonOption { + common_opts: HandlerCommonOptions { connector: s.common_opts.connect_via.clone(), ..Default::default() }, diff --git a/clash_lib/src/proxy/direct/mod.rs b/clash_lib/src/proxy/direct/mod.rs index f146cf1cb..f3b9ed3fe 100644 --- a/clash_lib/src/proxy/direct/mod.rs +++ b/clash_lib/src/proxy/direct/mod.rs @@ -74,7 +74,7 @@ impl OutboundHandler for Handler { (remote_ip, sess.destination.port()).into(), sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; @@ -92,7 +92,7 @@ impl OutboundHandler for Handler { None, sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await .map(|x| OutboundDatagramImpl::new(x, resolver))?; @@ -119,7 +119,7 @@ impl OutboundHandler for Handler { sess.destination.port(), sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; let s = ChainedStreamWrapper::new(s); @@ -140,7 +140,7 @@ impl OutboundHandler for Handler { sess.destination.clone(), sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; let d = ChainedDatagramWrapper::new(d); diff --git a/clash_lib/src/proxy/fallback/mod.rs b/clash_lib/src/proxy/fallback/mod.rs index ac17d841f..ba31406c9 100644 --- a/clash_lib/src/proxy/fallback/mod.rs +++ b/clash_lib/src/proxy/fallback/mod.rs @@ -22,7 +22,7 @@ use super::{ #[derive(Default, Clone)] pub struct HandlerOptions { - pub shared_opts: super::options::HandlerSharedOptions, + pub common_opts: super::options::HandlerCommonOptions, pub name: String, pub udp: bool, } @@ -150,6 +150,6 @@ impl OutboundHandler for Handler { } fn icon(&self) -> Option { - self.opts.shared_opts.icon.clone() + self.opts.common_opts.icon.clone() } } diff --git a/clash_lib/src/proxy/loadbalance/mod.rs b/clash_lib/src/proxy/loadbalance/mod.rs index 019d255f6..3f4e7d879 100644 --- a/clash_lib/src/proxy/loadbalance/mod.rs +++ b/clash_lib/src/proxy/loadbalance/mod.rs @@ -26,7 +26,7 @@ use super::{ #[derive(Default, Clone)] pub struct HandlerOptions { - pub shared_opts: super::options::HandlerSharedOptions, + pub common_opts: super::options::HandlerCommonOptions, pub name: String, pub udp: bool, pub strategy: LoadBalanceStrategy, @@ -157,6 +157,6 @@ impl OutboundHandler for Handler { } fn icon(&self) -> Option { - self.opts.shared_opts.icon.clone() + self.opts.common_opts.icon.clone() } } diff --git a/clash_lib/src/proxy/mod.rs b/clash_lib/src/proxy/mod.rs index f4850f9e2..22bee2857 100644 --- a/clash_lib/src/proxy/mod.rs +++ b/clash_lib/src/proxy/mod.rs @@ -3,7 +3,7 @@ use crate::{ dispatcher::{BoxedChainedDatagram, BoxedChainedStream}, dns::ThreadSafeDNSResolver, }, - proxy::{datagram::UdpPacket, utils::Interface}, + proxy::datagram::UdpPacket, session::Session, }; use async_trait::async_trait; @@ -53,9 +53,10 @@ pub mod urltest; mod common; mod options; -pub use options::HandlerSharedOptions; mod transport; +pub use options::HandlerCommonOptions; + #[cfg(test)] pub mod mocks; @@ -99,14 +100,6 @@ impl OutboundDatagram for T where pub type AnyOutboundDatagram = Box>; -#[derive(Default, Debug, Clone)] -pub struct CommonOption { - #[allow(dead_code)] - so_mark: Option, - iface: Option, - connector: Option, -} - #[async_trait] pub trait InboundListener: Send + Sync + Unpin { /// support tcp or not diff --git a/clash_lib/src/proxy/options.rs b/clash_lib/src/proxy/options.rs index a16d2149f..525d023f4 100644 --- a/clash_lib/src/proxy/options.rs +++ b/clash_lib/src/proxy/options.rs @@ -24,8 +24,8 @@ pub struct WsOption { pub early_data_header_name: String, } -// TODO: merge this with CommonOptions -#[derive(Clone, Default)] -pub struct HandlerSharedOptions { +#[derive(Default, Debug, Clone)] +pub struct HandlerCommonOptions { + pub connector: Option, pub icon: Option, } diff --git a/clash_lib/src/proxy/relay/mod.rs b/clash_lib/src/proxy/relay/mod.rs index 7ae59ae8a..c54ab1c23 100644 --- a/clash_lib/src/proxy/relay/mod.rs +++ b/clash_lib/src/proxy/relay/mod.rs @@ -29,7 +29,7 @@ use super::{ #[derive(Default)] pub struct HandlerOptions { - pub shared_opts: super::options::HandlerSharedOptions, + pub common_opts: super::options::HandlerCommonOptions, pub name: String, } @@ -194,7 +194,7 @@ impl OutboundHandler for Handler { } fn icon(&self) -> Option { - self.opts.shared_opts.icon.clone() + self.opts.common_opts.icon.clone() } } diff --git a/clash_lib/src/proxy/selector/mod.rs b/clash_lib/src/proxy/selector/mod.rs index 5214945e4..b20f3f476 100644 --- a/clash_lib/src/proxy/selector/mod.rs +++ b/clash_lib/src/proxy/selector/mod.rs @@ -35,7 +35,7 @@ struct HandlerInner { #[derive(Default, Clone)] pub struct HandlerOptions { - pub shared_opts: super::options::HandlerSharedOptions, + pub common_opts: super::options::HandlerCommonOptions, pub name: String, pub udp: bool, } @@ -202,7 +202,7 @@ impl OutboundHandler for Handler { } fn icon(&self) -> Option { - self.opts.shared_opts.icon.clone() + self.opts.common_opts.icon.clone() } } diff --git a/clash_lib/src/proxy/shadowsocks/mod.rs b/clash_lib/src/proxy/shadowsocks/mod.rs index 41c4e4a3c..285b7f716 100644 --- a/clash_lib/src/proxy/shadowsocks/mod.rs +++ b/clash_lib/src/proxy/shadowsocks/mod.rs @@ -6,7 +6,7 @@ mod v2ray; use self::{datagram::OutboundDatagramShadowsocks, stream::ShadowSocksStream}; use super::{ - utils::{new_udp_socket, RemoteConnector, GLOBAL_DIRECT_CONNECTOR}, + utils::{RemoteConnector, GLOBAL_DIRECT_CONNECTOR}, AnyStream, ConnectorType, DialWithConnector, OutboundType, }; use crate::{ @@ -18,7 +18,7 @@ use crate::{ dns::ThreadSafeDNSResolver, }, impl_default_connector, - proxy::{CommonOption, OutboundHandler}, + proxy::{HandlerCommonOptions, OutboundHandler}, session::Session, }; use async_trait::async_trait; @@ -68,7 +68,7 @@ pub enum OBFSOption { pub struct HandlerOptions { pub name: String, - pub common_opts: CommonOption, + pub common_opts: HandlerCommonOptions, pub server: String, pub port: u16, pub password: String, @@ -204,30 +204,24 @@ impl OutboundHandler for Handler { async fn connect_datagram( &self, - #[allow(unused_variables)] sess: &Session, + sess: &Session, resolver: ThreadSafeDNSResolver, ) -> io::Result { - let ctx = Context::new_shared(ServerType::Local); - let cfg = self.server_config()?; + let dialer = self.connector.lock().await; - let socket = new_udp_socket( - None, - self.opts.common_opts.iface.clone().or(sess.iface.clone()), - #[cfg(any(target_os = "linux", target_os = "android"))] - None, - ) - .await?; + if let Some(dialer) = dialer.as_ref() { + debug!("{:?} is connecting via {:?}", self, dialer); + } - let socket: ProxySocket = - ProxySocket::from_socket(UdpSocketType::Client, ctx, &cfg, socket); - let d = OutboundDatagramShadowsocks::new( - socket, - (self.opts.server.to_owned(), self.opts.port), + self.connect_datagram_with_connector( + sess, resolver, - ); - let d = ChainedDatagramWrapper::new(d); - d.append_to_chain(self.name()).await; - Ok(Box::new(d)) + dialer + .as_ref() + .unwrap_or(&GLOBAL_DIRECT_CONNECTOR.clone()) + .as_ref(), + ) + .await } async fn support_connector(&self) -> ConnectorType { @@ -245,9 +239,9 @@ impl OutboundHandler for Handler { resolver.clone(), self.opts.server.as_str(), self.opts.port, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; @@ -271,14 +265,9 @@ impl OutboundHandler for Handler { resolver.clone(), None, (self.opts.server.clone(), self.opts.port).try_into()?, - self.opts - .common_opts - .iface - .as_ref() - .or(sess.iface.as_ref()) - .cloned(), + sess.iface.as_ref().cloned(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; diff --git a/clash_lib/src/proxy/socks/inbound/stream.rs b/clash_lib/src/proxy/socks/inbound/stream.rs index 4a7de423a..fda35158b 100644 --- a/clash_lib/src/proxy/socks/inbound/stream.rs +++ b/clash_lib/src/proxy/socks/inbound/stream.rs @@ -178,7 +178,7 @@ pub async fn handle_tcp<'a>( let sess = Session { network: Network::Udp, typ: Type::Socks5, - packet_mark: None, + so_mark: None, iface: None, ..Default::default() }; diff --git a/clash_lib/src/proxy/socks/outbound/mod.rs b/clash_lib/src/proxy/socks/outbound/mod.rs index f0b2497f0..1857f1491 100644 --- a/clash_lib/src/proxy/socks/outbound/mod.rs +++ b/clash_lib/src/proxy/socks/outbound/mod.rs @@ -15,8 +15,8 @@ use crate::{ proxy::{ transport::{self, TLSOptions}, utils::{new_udp_socket, RemoteConnector, GLOBAL_DIRECT_CONNECTOR}, - AnyStream, CommonOption, ConnectorType, DialWithConnector, OutboundHandler, - OutboundType, + AnyStream, ConnectorType, DialWithConnector, HandlerCommonOptions, + OutboundHandler, OutboundType, }, session::Session, }; @@ -30,7 +30,7 @@ use super::socks5::{client_handshake, socks_command}; #[derive(Default)] pub struct HandlerOptions { pub name: String, - pub common_opts: CommonOption, + pub common_opts: HandlerCommonOptions, pub server: String, pub port: u16, pub user: Option, @@ -149,7 +149,7 @@ impl Handler { let udp_socket = new_udp_socket( None, - self.opts.common_opts.iface.clone().or(sess.iface.clone()), + sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] None, ) @@ -238,9 +238,9 @@ impl OutboundHandler for Handler { resolver, self.opts.server.as_str(), self.opts.port, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; @@ -262,9 +262,9 @@ impl OutboundHandler for Handler { resolver.clone(), self.opts.server.as_str(), self.opts.port, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; diff --git a/clash_lib/src/proxy/trojan/mod.rs b/clash_lib/src/proxy/trojan/mod.rs index 58ffce461..f879b872a 100644 --- a/clash_lib/src/proxy/trojan/mod.rs +++ b/clash_lib/src/proxy/trojan/mod.rs @@ -25,8 +25,8 @@ use super::{ options::{GrpcOption, WsOption}, transport::{self, TLSOptions}, utils::{RemoteConnector, GLOBAL_DIRECT_CONNECTOR}, - AnyStream, CommonOption, ConnectorType, DialWithConnector, OutboundHandler, - OutboundType, + AnyStream, ConnectorType, DialWithConnector, HandlerCommonOptions, + OutboundHandler, OutboundType, }; mod datagram; @@ -40,7 +40,7 @@ pub enum Transport { pub struct HandlerOptions { pub name: String, - pub common_opts: CommonOption, + pub common_opts: HandlerCommonOptions, pub server: String, pub port: u16, pub password: String, @@ -215,9 +215,9 @@ impl OutboundHandler for Handler { resolver, self.opts.server.as_str(), self.opts.port, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; @@ -238,9 +238,9 @@ impl OutboundHandler for Handler { resolver, self.opts.server.as_str(), self.opts.port, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; diff --git a/clash_lib/src/proxy/tuic/mod.rs b/clash_lib/src/proxy/tuic/mod.rs index b532149fe..fa3f8b179 100644 --- a/clash_lib/src/proxy/tuic/mod.rs +++ b/clash_lib/src/proxy/tuic/mod.rs @@ -56,7 +56,7 @@ use self::types::{CongestionControl, TuicConnection, UdpRelayMode, UdpSession}; use super::{ datagram::UdpPacket, utils::{get_outbound_interface, Interface}, - AnyOutboundDatagram, CommonOption, ConnectorType, OutboundHandler, OutboundType, + ConnectorType, HandlerCommonOptions, OutboundHandler, OutboundType, }; #[derive(Debug, Clone)] @@ -81,7 +81,7 @@ pub struct HandlerOptions { pub receive_window: VarInt, #[allow(dead_code)] - pub common_opts: CommonOption, + pub common_opts: HandlerCommonOptions, /// not used #[allow(dead_code)] @@ -165,6 +165,7 @@ impl Handler { async fn init_endpoint( opts: HandlerOptions, resolver: ThreadSafeDNSResolver, + #[cfg(any(target_os = "linux", target_os = "android"))] so_mark: Option, ) -> Result { let mut crypto = TlsConfig::builder_with_protocol_versions(&[&rustls::version::TLS13]) @@ -205,7 +206,7 @@ impl Handler { Some((Ipv6Addr::UNSPECIFIED, 0).into()), iface.map(|x| Interface::Name(x.name.clone())), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + so_mark, ) .await? } else { @@ -213,7 +214,7 @@ impl Handler { Some((Ipv4Addr::UNSPECIFIED, 0).into()), iface.map(|x| Interface::Name(x.name.clone())), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + so_mark, ) .await? } @@ -249,11 +250,17 @@ impl Handler { async fn get_conn( &self, resolver: &ThreadSafeDNSResolver, + #[allow(unused)] sess: &Session, // linux only ) -> Result> { let endpoint = self .ep .get_or_try_init(|| { - Self::init_endpoint(self.opts.clone(), resolver.clone()) + Self::init_endpoint( + self.opts.clone(), + resolver.clone(), + #[cfg(any(target_os = "linux", target_os = "android"))] + sess.so_mark, + ) }) .await?; @@ -282,7 +289,7 @@ impl Handler { sess: &Session, resolver: ThreadSafeDNSResolver, ) -> Result { - let conn = self.get_conn(&resolver).await?; + let conn = self.get_conn(&resolver, sess).await?; let dest = sess.destination.clone().into_tuic(); let tuic_tcp = conn.connect_tcp(dest).await?.compat(); let s = ChainedStreamWrapper::new(tuic_tcp); @@ -295,7 +302,7 @@ impl Handler { sess: &Session, resolver: ThreadSafeDNSResolver, ) -> Result { - let conn = self.get_conn(&resolver).await?; + let conn = self.get_conn(&resolver, sess).await?; let assos_id = self.next_assoc_id.fetch_add(1, Ordering::SeqCst); let quic_udp = TuicDatagramOutbound::new(assos_id, conn, sess.source.into()); let s = ChainedDatagramWrapper::new(quic_udp); @@ -311,12 +318,11 @@ struct TuicDatagramOutbound { } impl TuicDatagramOutbound { - #[allow(clippy::new_ret_no_self)] pub fn new( assoc_id: u16, conn: Arc, local_addr: ClashSocksAddr, - ) -> AnyOutboundDatagram { + ) -> Self { // TODO not sure about the size of buffer let (send_tx, send_rx) = tokio::sync::mpsc::channel::(32); let (recv_tx, recv_rx) = tokio::sync::mpsc::channel::(32); @@ -351,10 +357,10 @@ impl TuicDatagramOutbound { udp_sessions.write().await.remove(&assoc_id); anyhow::Ok(()) }); - let s = Self { + + Self { send_tx: tokio_util::sync::PollSender::new(send_tx), recv_rx, - }; - Box::new(s) + } } } diff --git a/clash_lib/src/proxy/tun/inbound.rs b/clash_lib/src/proxy/tun/inbound.rs index 8af5167d1..4c54cc131 100644 --- a/clash_lib/src/proxy/tun/inbound.rs +++ b/clash_lib/src/proxy/tun/inbound.rs @@ -19,16 +19,17 @@ use crate::{ Error, Runner, }; -#[cfg(target_os = "macos")] -use crate::defer; -#[cfg(target_os = "macos")] -use crate::proxy::tun::routes; +use crate::{defer, proxy::tun::routes}; + +const DEFAULT_SO_MARK: u32 = 3389; +const DEFAULT_ROUTE_TABLE: u32 = 2468; async fn handle_inbound_stream( stream: netstack::TcpStream, local_addr: SocketAddr, remote_addr: SocketAddr, dispatcher: Arc, + so_mark: u32, ) { let sess = Session { network: Network::Tcp, @@ -43,7 +44,7 @@ async fn handle_inbound_stream( x ); }), - ..Default::default() + so_mark: Some(so_mark), }; debug!("new tun TCP session assigned: {}", sess); @@ -54,6 +55,7 @@ async fn handle_inbound_datagram( socket: Box, dispatcher: Arc, resolver: ThreadSafeDNSResolver, + so_mark: u32, ) { let local_addr = socket.local_addr(); // tun i/o @@ -80,7 +82,7 @@ async fn handle_inbound_datagram( .inspect(|x| { debug!("selecting outbound interface: {:?} for tun UDP traffic", x); }), - + so_mark: Some(so_mark), ..Default::default() }; @@ -199,19 +201,29 @@ pub fn get_runner( let tun_name = tun.get_ref().name().map_err(map_io_error)?; info!("tun started at {}", tun_name); + let mut cfg = cfg; + cfg.route_table = cfg.route_table.or(Some(DEFAULT_ROUTE_TABLE)); + cfg.so_mark = cfg.so_mark.or(Some(DEFAULT_SO_MARK)); + maybe_add_routes(&cfg, &tun_name)?; let (stack, mut tcp_listener, udp_socket) = netstack::NetStack::with_buffer_size(512, 256).map_err(map_io_error)?; Ok(Some(Box::pin(async move { - #[cfg(target_os = "macos")] defer! { warn!("cleaning up routes"); - let _ = routes::maybe_routes_clean_up(); + match routes::maybe_routes_clean_up(&cfg) { + Ok(_) => {} + Err(e) => { + error!("failed to clean up routes: {}", e); + } + } } + let so_mark = cfg.so_mark.unwrap(); + let framed = tun.into_framed(); let (mut tun_sink, mut tun_stream) = framed.split(); @@ -273,6 +285,7 @@ pub fn get_runner( local_addr, remote_addr, dsp.clone(), + so_mark, )); } @@ -280,7 +293,7 @@ pub fn get_runner( })); futs.push(Box::pin(async move { - handle_inbound_datagram(udp_socket, dispatcher, resolver).await; + handle_inbound_datagram(udp_socket, dispatcher, resolver, so_mark).await; Err(Error::Operation("tun stopped unexpectedly 3".to_string())) })); diff --git a/clash_lib/src/proxy/tun/mod.rs b/clash_lib/src/proxy/tun/mod.rs index 19bc5b90a..54de9084f 100644 --- a/clash_lib/src/proxy/tun/mod.rs +++ b/clash_lib/src/proxy/tun/mod.rs @@ -3,3 +3,169 @@ pub use netstack_lwip as netstack; mod datagram; pub use inbound::get_runner as get_tun_runner; mod routes; + +#[cfg(test)] +mod tests { + use std::thread; + + use crate::{shutdown, start, Config, Options}; + + fn wait_port_open(port: u16) { + let mut count = 0; + while count < 30 { + if std::net::TcpStream::connect(("127.0.0.1", port)).is_ok() { + break; + } + count += 1; + thread::sleep(std::time::Duration::from_secs(1)); + } + } + + #[test] + #[ignore = "only run this test locally, to not deal with the tun device \ + permission"] + fn test_individual_routes() { + let conf = r#" + socks-port: 7891 + bind-address: 127.0.0.1 + mmdb: "Country.mmdb" + log-level: trace + tun: + enable: true + device-id: "dev://utun1989" + route-all: false + routes: + - 1.1.1.1/32 + gateway: "198.19.0.1/32" + so-mark: 3389 + "#; + + let cwd = tempfile::tempdir() + .unwrap() + .path() + .to_str() + .unwrap() + .to_string(); + + let log_file = uuid::Uuid::new_v4().to_string() + ".log"; + + let cwd_clone = cwd.clone(); + let log_file_clone = log_file.clone(); + + let handle = thread::spawn(|| { + start(Options { + config: Config::Str(conf.to_string()), + cwd: Some(cwd_clone), + rt: None, + log_file: Some(log_file_clone), + }) + .unwrap() + }); + + wait_port_open(7891); + + let udp_socket = std::net::UdpSocket::bind("0.0.0.0:0").unwrap(); + let req = b"\x12\x34\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x05\x62\x61\x69\x64\x75\x03\x63\x6f\x6d\x00\x00\x01\x00\x01"; + udp_socket + .send_to(req, "1.1.1.1:53") + .inspect_err(|x| { + panic!("failed to send udp packet: {x:?}"); + }) + .unwrap(); + + let mut buf = [0u8; 1024]; + udp_socket.recv_from(&mut buf).unwrap(); + + assert_eq!(buf[0], req[0]); + + assert!(shutdown()); + + thread::sleep(std::time::Duration::from_secs(1)); + + let today = chrono::Utc::now().format("%Y-%m-%d"); + + let log_path = cwd + "/" + &log_file + "." + &today.to_string(); + + let logs = std::fs::read_to_string(&log_path) + .expect(format!("failed to read log file: {}", log_path).as_str()); + + assert!(logs.contains("1.1.1.1:53 to MATCH")); + + handle.join().unwrap(); + } + + #[test] + #[ignore = "it's hard to test as altering the routing table can cause ssh \ + connection lost"] + fn test_route_all() { + let conf = r#" + socks-port: 7891 + bind-address: 127.0.0.1 + mmdb: "Country.mmdb" + log-level: trace + tun: + enable: true + device-id: "dev://utun1989" + route-all: true + gateway: "198.19.0.1/32" + so-mark: 3389 + "#; + + let cwd = tempfile::tempdir() + .unwrap() + .path() + .to_str() + .unwrap() + .to_string(); + + let log_file = uuid::Uuid::new_v4().to_string() + ".log"; + + let cwd_clone = cwd.clone(); + let log_file_clone = log_file.clone(); + + let handle = thread::spawn(|| { + start(Options { + config: Config::Str(conf.to_string()), + cwd: Some(cwd_clone), + rt: None, + log_file: Some(log_file_clone), + }) + .unwrap() + }); + + wait_port_open(7891); + + let echo_server = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); + let echo_addr = echo_server.local_addr().unwrap(); + thread::spawn(move || { + let mut buf = [0u8; 1024]; + loop { + let (n, src) = echo_server.recv_from(&mut buf).unwrap(); + echo_server.send_to(&buf[..n], src).unwrap(); + } + }); + + let udp_socket = std::net::UdpSocket::bind("127.0.0.1:0").unwrap(); + udp_socket.send_to(b"hello", echo_addr).unwrap(); + let mut buf = [0u8; 1024]; + udp_socket.recv_from(&mut buf).unwrap(); + + assert_eq!(b"hello", &buf[..5]); + + assert!(shutdown()); + + thread::sleep(std::time::Duration::from_secs(1)); + + let today = chrono::Utc::now().format("%Y-%m-%d"); + + let log_path = cwd + "/" + &log_file + "." + &today.to_string(); + + let logs = std::fs::read_to_string(&log_path) + .expect(format!("failed to read log file: {}", log_path).as_str()); + + assert!(logs.contains("route_all is enabled")); + assert!(logs.contains(format!("{} to MATCH", echo_addr).as_str())); + + handle.join().unwrap(); + } +} diff --git a/clash_lib/src/proxy/tun/routes/linux.rs b/clash_lib/src/proxy/tun/routes/linux.rs index 0ee6e018b..1fb87deb2 100644 --- a/clash_lib/src/proxy/tun/routes/linux.rs +++ b/clash_lib/src/proxy/tun/routes/linux.rs @@ -1,9 +1,174 @@ use ipnet::IpNet; use tracing::warn; -use crate::proxy::utils::OutboundInterface; +use crate::{ + common::errors::new_io_error, config::internal::config::TunConfig, + proxy::utils::OutboundInterface, +}; -pub fn add_route(_: &OutboundInterface, _: &IpNet) -> std::io::Result<()> { - warn!("add_route is not implemented on Linux"); +/// TODO: get rid of command execution +pub fn check_ip_command_installed() -> std::io::Result<()> { + std::process::Command::new("ip") + .arg("route") + .output() + .and_then(|output| { + if output.status.success() { + Ok(()) + } else { + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "ip command not found", + )) + } + }) +} + +pub fn add_route(via: &OutboundInterface, dest: &IpNet) -> std::io::Result<()> { + let cmd = std::process::Command::new("ip") + .arg("route") + .arg("add") + .arg(dest.to_string()) + .arg("dev") + .arg(&via.name) + .output()?; + warn!("executing: ip route add {} dev {}", dest, via.name); + if !cmd.status.success() { + return Err(new_io_error(format!( + "add route failed: {}", + String::from_utf8_lossy(&cmd.stderr) + ))); + } + Ok(()) +} + +/// three rules are added: +/// # ip route add default dev wg0 table 2468 +/// # ip rule add not fwmark 1234 table 2468 +/// # ip rule add table main suppress_prefixlength 0 +pub fn setup_policy_routing( + tun_cfg: &TunConfig, + via: &OutboundInterface, +) -> std::io::Result<()> { + let cmd = std::process::Command::new("ip") + .arg("route") + .arg("add") + .arg("default") + .arg("dev") + .arg(via.name.as_str()) + .arg("table") + .arg( + tun_cfg + .route_table + .expect("route_table not set") + .to_string(), + ) + .output()?; + warn!( + "executing: ip route add default dev {} table {}", + via.name, + tun_cfg.route_table.unwrap() + ); + if !cmd.status.success() { + return Err(new_io_error(format!( + "add default route failed: {}", + String::from_utf8_lossy(&cmd.stderr) + ))); + } + + let cmd = std::process::Command::new("ip") + .arg("rule") + .arg("add") + .arg("not") + .arg("fwmark") + .arg(tun_cfg.so_mark.expect("so_mark not set").to_string()) + .arg("table") + .arg( + tun_cfg + .route_table + .expect("route_table not set") + .to_string(), + ) + .output()?; + warn!( + "executing: ip rule add not fwmark {} table {}", + tun_cfg.so_mark.unwrap(), + tun_cfg.route_table.unwrap() + ); + if !cmd.status.success() { + return Err(new_io_error(format!( + "add rule failed: {}", + String::from_utf8_lossy(&cmd.stderr) + ))); + } + + let cmd = std::process::Command::new("ip") + .arg("rule") + .arg("add") + .arg("table") + .arg("main") + .arg("suppress_prefixlength") + .arg("0") + .output()?; + warn!("executing: ip rule add table main suppress_prefixlength 0"); + if !cmd.status.success() { + return Err(new_io_error(format!( + "add rule failed: {}", + String::from_utf8_lossy(&cmd.stderr) + ))); + } + + Ok(()) +} + +/// three rules to clean up: +/// # ip rule add not fwmark $SO_MARK table $TABLE +/// # ip rule add table main suppress_prefixlength 0 +pub fn maybe_routes_clean_up(tun_cfg: &TunConfig) -> std::io::Result<()> { + if !(tun_cfg.enable && tun_cfg.route_all) { + return Ok(()); + } + + let cmd = std::process::Command::new("ip") + .arg("rule") + .arg("del") + .arg("not") + .arg("fwmark") + .arg(tun_cfg.so_mark.expect("so_mark not set").to_string()) + .arg("table") + .arg( + tun_cfg + .route_table + .expect("route_table not set") + .to_string(), + ) + .output()?; + warn!( + "executing: ip rule del not fwmark {} table {}", + tun_cfg.so_mark.unwrap(), + tun_cfg.route_table.unwrap() + ); + if !cmd.status.success() { + return Err(new_io_error(format!( + "delete rule failed: {}", + String::from_utf8_lossy(&cmd.stderr) + ))); + } + + let cmd = std::process::Command::new("ip") + .arg("rule") + .arg("del") + .arg("table") + .arg("main") + .arg("suppress_prefixlength") + .arg("0") + .output()?; + + warn!("executing: ip rule del table main suppress_prefixlength 0"); + if !cmd.status.success() { + return Err(new_io_error(format!( + "delete rule failed: {}", + String::from_utf8_lossy(&cmd.stderr) + ))); + } Ok(()) } diff --git a/clash_lib/src/proxy/tun/routes/macos.rs b/clash_lib/src/proxy/tun/routes/macos.rs index e78c86c1a..a6716b0b2 100644 --- a/clash_lib/src/proxy/tun/routes/macos.rs +++ b/clash_lib/src/proxy/tun/routes/macos.rs @@ -5,6 +5,7 @@ use tracing::warn; use crate::{ common::errors::new_io_error, + config::internal::config::TunConfig, proxy::utils::{get_outbound_interface, OutboundInterface}, }; @@ -86,7 +87,7 @@ pub fn maybe_add_default_route() -> std::io::Result<()> { } /// failing to delete the default route won't cause route failure -pub fn maybe_routes_clean_up() -> std::io::Result<()> { +pub fn maybe_routes_clean_up(_: &TunConfig) -> std::io::Result<()> { let gateway = get_default_gateway()?; if let Some(gateway) = gateway { let default_interface = diff --git a/clash_lib/src/proxy/tun/routes/mod.rs b/clash_lib/src/proxy/tun/routes/mod.rs index e18dee30b..6ab32c20a 100644 --- a/clash_lib/src/proxy/tun/routes/mod.rs +++ b/clash_lib/src/proxy/tun/routes/mod.rs @@ -2,6 +2,8 @@ mod windows; #[cfg(windows)] use windows::add_route; +#[cfg(windows)] +pub use windows::maybe_routes_clean_up; #[cfg(target_os = "macos")] mod macos; @@ -14,14 +16,14 @@ pub use macos::maybe_routes_clean_up; mod linux; #[cfg(target_os = "linux")] use linux::add_route; +#[cfg(target_os = "linux")] +pub use linux::maybe_routes_clean_up; #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] mod other; #[cfg(not(any(windows, target_os = "macos", target_os = "linux")))] use other::add_route; -use std::net::Ipv4Addr; - use tracing::warn; use crate::{ @@ -29,11 +31,13 @@ use crate::{ proxy::utils::OutboundInterface, }; -use ipnet::IpNet; use network_interface::NetworkInterfaceConfig; pub fn maybe_add_routes(cfg: &TunConfig, tun_name: &str) -> std::io::Result<()> { if cfg.route_all || !cfg.routes.is_empty() { + #[cfg(target_os = "linux")] + linux::check_ip_command_installed()?; + let tun_iface = network_interface::NetworkInterface::show() .map_err(map_io_error)? .into_iter() @@ -57,23 +61,35 @@ pub fn maybe_add_routes(cfg: &TunConfig, tun_name: &str) -> std::io::Result<()> "route_all is enabled, all traffic will be routed through the tun \ interface" ); - let default_routes = vec![ - IpNet::new(std::net::IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 1) - .unwrap(), - IpNet::new(std::net::IpAddr::V4(Ipv4Addr::new(128, 0, 0, 0)), 1) - .unwrap(), - ]; - for r in default_routes { - add_route(&tun_iface, &r).map_err(map_io_error)?; - } - #[cfg(target_os = "macos")] + #[cfg(not(target_os = "linux"))] + { + use ipnet::IpNet; + + use std::net::Ipv4Addr; + + let default_routes = vec![ + IpNet::new(std::net::IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 1) + .unwrap(), + IpNet::new(std::net::IpAddr::V4(Ipv4Addr::new(128, 0, 0, 0)), 1) + .unwrap(), + ]; + for r in default_routes { + add_route(&tun_iface, &r)?; + } + + #[cfg(target_os = "macos")] + { + macos::maybe_add_default_route()?; + } + } + #[cfg(target_os = "linux")] { - macos::maybe_add_default_route()?; + linux::setup_policy_routing(cfg, &tun_iface)?; } } else { for r in &cfg.routes { - add_route(&tun_iface, r).map_err(map_io_error)?; + add_route(&tun_iface, r)?; } } } diff --git a/clash_lib/src/proxy/tun/routes/windows.rs b/clash_lib/src/proxy/tun/routes/windows.rs index f91194e3a..fc1e042fe 100644 --- a/clash_lib/src/proxy/tun/routes/windows.rs +++ b/clash_lib/src/proxy/tun/routes/windows.rs @@ -13,7 +13,10 @@ use windows::Win32::{ Networking::WinSock::{AF_INET, AF_INET6, PROTO_IP_RIP}, }; -use crate::{common::errors::new_io_error, defer, proxy::utils::OutboundInterface}; +use crate::{ + common::errors::new_io_error, config::internal::config::TunConfig, defer, + proxy::utils::OutboundInterface, +}; const PROTO_TYPE_UCAST: u32 = 0; const PROTO_VENDOR_ID: u32 = 0xFFFF; @@ -48,6 +51,10 @@ pub fn add_route(via: &OutboundInterface, dest: &IpNet) -> io::Result<()> { } } +pub fn maybe_routes_clean_up(_: &TunConfig) -> std::io::Result<()> { + Ok(()) +} + /// Add a route to the routing table. /// https://learn.microsoft.com/en-us/windows/win32/rras/add-and-update-routes-using-rtmaddroutetodest /// FIXME: figure out why this doesn't work https://stackoverflow.com/questions/43632619/how-to-properly-use-rtmv2-and-rtmaddroutetodest diff --git a/clash_lib/src/proxy/urltest/mod.rs b/clash_lib/src/proxy/urltest/mod.rs index c8bae8353..1cf0f602b 100644 --- a/clash_lib/src/proxy/urltest/mod.rs +++ b/clash_lib/src/proxy/urltest/mod.rs @@ -24,7 +24,7 @@ use super::{ #[derive(Default)] pub struct HandlerOptions { - pub shared_opts: super::options::HandlerSharedOptions, + pub common_opts: super::options::HandlerCommonOptions, pub name: String, pub udp: bool, } @@ -228,6 +228,6 @@ impl OutboundHandler for Handler { } fn icon(&self) -> Option { - self.opts.shared_opts.icon.clone() + self.opts.common_opts.icon.clone() } } diff --git a/clash_lib/src/proxy/utils/platform/win.rs b/clash_lib/src/proxy/utils/platform/win.rs index 78f7057dc..18207dd65 100644 --- a/clash_lib/src/proxy/utils/platform/win.rs +++ b/clash_lib/src/proxy/utils/platform/win.rs @@ -8,7 +8,7 @@ use windows::Win32::{ }, }; -use crate::{common::errors::new_io_error, proxy::Interface}; +use crate::{common::errors::new_io_error, proxy::utils::Interface}; pub(crate) fn must_bind_socket_on_interface( socket: &socket2::Socket, diff --git a/clash_lib/src/proxy/utils/proxy_connector.rs b/clash_lib/src/proxy/utils/proxy_connector.rs index 648379741..86396f0a3 100644 --- a/clash_lib/src/proxy/utils/proxy_connector.rs +++ b/clash_lib/src/proxy/utils/proxy_connector.rs @@ -72,9 +72,7 @@ impl RemoteConnector for DirectConnector { address: &str, port: u16, iface: Option<&Interface>, - #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option< - u32, - >, + #[cfg(any(target_os = "linux", target_os = "android"))] so_mark: Option, ) -> std::io::Result { let dial_addr = resolver .resolve(address, false) @@ -86,7 +84,7 @@ impl RemoteConnector for DirectConnector { (dial_addr, port).into(), iface.cloned(), #[cfg(any(target_os = "linux", target_os = "android"))] - packet_mark, + so_mark, ) .await .map(|x| Box::new(x) as _) @@ -98,15 +96,13 @@ impl RemoteConnector for DirectConnector { src: Option, _destination: SocksAddr, iface: Option, - #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option< - u32, - >, + #[cfg(any(target_os = "linux", target_os = "android"))] so_mark: Option, ) -> std::io::Result { let dgram = new_udp_socket( src, iface, #[cfg(any(target_os = "linux", target_os = "android"))] - packet_mark, + so_mark, ) .await .map(|x| OutboundDatagramImpl::new(x, resolver))?; @@ -147,9 +143,7 @@ impl RemoteConnector for ProxyConnector { address: &str, port: u16, iface: Option<&Interface>, - #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option< - u32, - >, + #[cfg(any(target_os = "linux", target_os = "android"))] so_mark: Option, ) -> std::io::Result { let sess = Session { network: Network::Tcp, @@ -157,7 +151,7 @@ impl RemoteConnector for ProxyConnector { destination: crate::session::SocksAddr::Domain(address.to_owned(), port), iface: iface.cloned(), #[cfg(any(target_os = "linux", target_os = "android"))] - packet_mark, + so_mark, ..Default::default() }; @@ -184,9 +178,7 @@ impl RemoteConnector for ProxyConnector { _src: Option, destination: SocksAddr, iface: Option, - #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option< - u32, - >, + #[cfg(any(target_os = "linux", target_os = "android"))] so_mark: Option, ) -> std::io::Result { let sess = Session { network: Network::Udp, @@ -194,7 +186,7 @@ impl RemoteConnector for ProxyConnector { iface, destination: destination.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] - packet_mark, + so_mark, ..Default::default() }; let s = self diff --git a/clash_lib/src/proxy/utils/socket_helpers.rs b/clash_lib/src/proxy/utils/socket_helpers.rs index c6b9ed97b..41176e9e8 100644 --- a/clash_lib/src/proxy/utils/socket_helpers.rs +++ b/clash_lib/src/proxy/utils/socket_helpers.rs @@ -37,7 +37,7 @@ pub fn apply_tcp_options(s: TcpStream) -> std::io::Result { pub async fn new_tcp_stream( endpoint: SocketAddr, iface: Option, - #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option, + #[cfg(any(target_os = "linux", target_os = "android"))] so_mark: Option, ) -> io::Result { let (socket, family) = match endpoint { SocketAddr::V4(_) => ( @@ -64,8 +64,8 @@ pub async fn new_tcp_stream( } #[cfg(any(target_os = "linux", target_os = "android"))] - if let Some(packet_mark) = packet_mark { - socket.set_mark(packet_mark)?; + if let Some(so_mark) = so_mark { + socket.set_mark(so_mark)?; } socket.set_keepalive(true)?; @@ -82,7 +82,7 @@ pub async fn new_tcp_stream( pub async fn new_udp_socket( src: Option, iface: Option, - #[cfg(any(target_os = "linux", target_os = "android"))] packet_mark: Option, + #[cfg(any(target_os = "linux", target_os = "android"))] so_mark: Option, ) -> io::Result { let (socket, family) = match src { Some(src) => { @@ -139,8 +139,8 @@ pub async fn new_udp_socket( } #[cfg(any(target_os = "linux", target_os = "android"))] - if let Some(packet_mark) = packet_mark { - socket.set_mark(packet_mark)?; + if let Some(so_mark) = so_mark { + socket.set_mark(so_mark)?; } socket.set_broadcast(true)?; diff --git a/clash_lib/src/proxy/vmess/mod.rs b/clash_lib/src/proxy/vmess/mod.rs index a6fc1f24b..20f3b11ac 100644 --- a/clash_lib/src/proxy/vmess/mod.rs +++ b/clash_lib/src/proxy/vmess/mod.rs @@ -23,8 +23,8 @@ use super::{ options::{GrpcOption, Http2Option, HttpOption, WsOption}, transport::{self, Http2Config}, utils::{RemoteConnector, GLOBAL_DIRECT_CONNECTOR}, - AnyStream, CommonOption, ConnectorType, DialWithConnector, OutboundHandler, - OutboundType, + AnyStream, ConnectorType, DialWithConnector, HandlerCommonOptions, + OutboundHandler, OutboundType, }; pub enum VmessTransport { @@ -37,7 +37,7 @@ pub enum VmessTransport { pub struct HandlerOptions { pub name: String, - pub common_opts: CommonOption, + pub common_opts: HandlerCommonOptions, pub server: String, pub port: u16, pub uuid: String, @@ -247,9 +247,9 @@ impl OutboundHandler for Handler { resolver, self.opts.server.as_str(), self.opts.port, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; @@ -270,9 +270,9 @@ impl OutboundHandler for Handler { resolver, self.opts.server.as_str(), self.opts.port, - self.opts.common_opts.iface.as_ref().or(sess.iface.as_ref()), + sess.iface.as_ref(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; diff --git a/clash_lib/src/proxy/wg/mod.rs b/clash_lib/src/proxy/wg/mod.rs index a120eb282..f7dfa47c9 100644 --- a/clash_lib/src/proxy/wg/mod.rs +++ b/clash_lib/src/proxy/wg/mod.rs @@ -21,7 +21,7 @@ use crate::{ use self::{keys::KeyBytes, wireguard::Config}; use super::{ - utils::RemoteConnector, CommonOption, ConnectorType, DialWithConnector, + utils::RemoteConnector, ConnectorType, DialWithConnector, HandlerCommonOptions, OutboundHandler, OutboundType, }; @@ -42,7 +42,7 @@ mod wireguard; pub struct HandlerOptions { pub name: String, - pub common_opts: CommonOption, + pub common_opts: HandlerCommonOptions, pub server: String, pub port: u16, pub ip: Ipv4Addr, @@ -93,9 +93,13 @@ impl Handler { } } + /// this is a one time initialization, however in theory sess.so_mark + /// and sess.iface should be all the same + /// ideally we move the so_mark and iface to a global context async fn initialize_inner( &self, resolver: ThreadSafeDNSResolver, + sess: &Session, ) -> Result<&Inner, Error> { self.inner .get_or_try_init(|| async { @@ -169,6 +173,7 @@ impl Handler { send_pair.1, resolver.clone(), self.connector.lock().await.as_ref().cloned(), + sess, ) .await .map_err(map_io_error)?; @@ -246,7 +251,7 @@ impl OutboundHandler for Handler { resolver: ThreadSafeDNSResolver, ) -> io::Result { let inner = self - .initialize_inner(resolver.clone()) + .initialize_inner(resolver.clone(), sess) .await .map_err(map_io_error)?; @@ -293,11 +298,11 @@ impl OutboundHandler for Handler { /// connect to remote target via UDP async fn connect_datagram( &self, - _sess: &Session, + sess: &Session, resolver: ThreadSafeDNSResolver, ) -> io::Result { let inner = self - .initialize_inner(resolver) + .initialize_inner(resolver, sess) .await .map_err(map_io_error)?; diff --git a/clash_lib/src/proxy/wg/wireguard.rs b/clash_lib/src/proxy/wg/wireguard.rs index b028a71c8..ec4277c6d 100644 --- a/clash_lib/src/proxy/wg/wireguard.rs +++ b/clash_lib/src/proxy/wg/wireguard.rs @@ -31,7 +31,7 @@ use crate::{ utils::{RemoteConnector, GLOBAL_DIRECT_CONNECTOR}, AnyOutboundDatagram, }, - session::SocksAddr, + session::{Session, SocksAddr}, Error, }; @@ -83,6 +83,7 @@ impl WireguardTunnel { packet_reader: Receiver, resolver: ThreadSafeDNSResolver, connector: Option>, + sess: &Session, ) -> Result { let peer = Tunn::new( config.private_key, @@ -101,9 +102,9 @@ impl WireguardTunnel { resolver, None, remote_endpoint.into(), - None, // TODO: wg outbound interface https://github.com/Watfaq/clash-rs/issues/580 + sess.iface.clone(), #[cfg(any(target_os = "linux", target_os = "android"))] - None, + sess.so_mark, ) .await?; diff --git a/clash_lib/src/session.rs b/clash_lib/src/session.rs index 8b41fd83c..7ceb3cbdf 100644 --- a/clash_lib/src/session.rs +++ b/clash_lib/src/session.rs @@ -393,7 +393,7 @@ pub struct Session { /// The proxy target address of a proxy connection. pub destination: SocksAddr, /// The packet mark SO_MARK - pub packet_mark: Option, + pub so_mark: Option, /// The bind interface pub iface: Option, } @@ -426,7 +426,7 @@ impl Default for Session { typ: Type::Http, source: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0), destination: SocksAddr::any_ipv4(), - packet_mark: None, + so_mark: None, iface: None, } } @@ -448,7 +448,7 @@ impl Debug for Session { .field("network", &self.network) .field("source", &self.source) .field("destination", &self.destination) - .field("packet_mark", &self.packet_mark) + .field("packet_mark", &self.so_mark) .field("iface", &self.iface) .finish() } @@ -461,7 +461,7 @@ impl Clone for Session { typ: self.typ, source: self.source, destination: self.destination.clone(), - packet_mark: self.packet_mark, + so_mark: self.so_mark, iface: self.iface.as_ref().cloned(), } }