Skip to content

Commit 3769e0d

Browse files
Merge pull request #217 from loss-and-quick/fix/tun2socks-yaml-config
fix(tun): drive tun2socks through its YAML config
2 parents 289cab1 + 0063db2 commit 3769e0d

11 files changed

Lines changed: 147 additions & 38 deletions

File tree

crates/kasumi-backend/src/lifecycle.rs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use kasumi_core::enums::{CoreEngine, TunEngine, tun_from_marker};
1616
use kasumi_core::hev_config::build_hev_config;
1717
use kasumi_core::singbox_config::build_singbox_bridge_config;
1818
use kasumi_core::state::{AppState, DEFAULT_LOCAL_SOCKS_PORT};
19+
use kasumi_core::tun2socks_config::build_tun2socks_config;
1920
// Aliased: `tun` alone would shadow the many `tun: TunEngine` params here.
2021
use kasumi_core::tun as tun_addr;
2122
use kasumi_core::tun::TunOptions;
@@ -375,33 +376,27 @@ pub async fn spawn_core(
375376
.await
376377
}
377378

378-
/// Spawn tun2socks bridging the tun to the local SOCKS port. `fwmark`, when set,
379-
/// marks tun2socks' own upstream socket so an `ip rule` can keep it out of the
380-
/// tunnel — a Linux SO_MARK feature. Windows has no fwmark (its server bypass is a
381-
/// host route), so it passes `None`.
379+
/// Spawn tun2socks bridging the tun to the local SOCKS port: render its YAML
380+
/// config (device/proxy/tuning — see `build_tun2socks_config`), write it next to
381+
/// the runtime state, then run `<bin> --config <cfg>`. `fwmark`, when set, marks
382+
/// tun2socks' own upstream socket so an `ip rule` can keep it out of the tunnel —
383+
/// a Linux SO_MARK feature. Windows has no fwmark (its server bypass is a host
384+
/// route), so it passes `None`.
382385
async fn spawn_tun2socks(s: &TunSpawn<'_>) -> std::io::Result<Child> {
383-
let mut argv = vec![
386+
let yaml = build_tun2socks_config(s.iface, s.socks_port, s.fwmark, s.opts);
387+
write_text(s.cfg_path, &yaml).await?;
388+
let argv = [
384389
s.bin.to_owned(),
385-
"-device".into(),
386-
format!("tun://{}", s.iface),
387-
"-proxy".into(),
388-
format!("socks5://127.0.0.1:{}", s.socks_port),
389-
// The TUN MTU setting applies to every external engine; tun2socks creates
390-
// its own tun, so it must be told the MTU here (hev takes it in its YAML).
391-
"-mtu".into(),
392-
s.opts.mtu.to_string(),
390+
"--config".into(),
391+
s.cfg_path.to_string_lossy().into_owned(),
393392
];
394-
if let Some(mark) = s.fwmark {
395-
argv.push("-fwmark".into());
396-
argv.push(mark.to_string());
397-
}
398393
spawn_logged(&argv, &std::collections::HashMap::new(), s.log_path, false).await
399394
}
400395

401396
/// Everything needed to bring up one external TUN engine, gathered so adding an
402397
/// engine is a single match arm. `bin` is the engine binary (resolved per-platform);
403-
/// `cfg_path` is where a config-file engine (hev / sidecar sing-box) writes its
404-
/// config; `ipv4`/`ipv6` are the host addresses such an engine assigns to the tun it
398+
/// `cfg_path` is where the engine's rendered config is written; `ipv4`/`ipv6` are
399+
/// the host addresses a self-addressing engine assigns to the tun it
405400
/// creates itself; `stack` is the sing-box tun stack (only the sidecar sing-box reads
406401
/// it); `opts` carries the resolved tuning.
407402
pub struct TunSpawn<'a> {

crates/kasumi-core/src/hev_config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ struct Socks5 {
3535
/// UDP-in-TCP framing, which needs a hev-socks5-server upstream.
3636
udp: &'static str,
3737
/// SO_MARK stamped on hev's own upstream sockets so an `ip rule` keeps them out
38-
/// of the tunnel — load-bearing on Android (mirrors tun2socks' `-fwmark`), unused
38+
/// of the tunnel — load-bearing on Android (mirrors tun2socks' `fwmark`), unused
3939
/// on desktop (the core binds to the uplink instead), so omitted when `None`.
4040
#[serde(skip_serializing_if = "Option::is_none")]
4141
mark: Option<u32>,
@@ -141,7 +141,7 @@ mod tests {
141141
#[test]
142142
fn mark_emitted_when_fwmark_set() {
143143
// Android passes a fwmark so hev's own sockets escape the tunnel like
144-
// tun2socks' `-fwmark`; desktop passes None (see `build_hev_config`).
144+
// tun2socks' `fwmark`; desktop passes None (see `build_hev_config`).
145145
let opts = AdvancedSettings::default().tun_options();
146146
let yaml = build_hev_config("kt0", crate::tun::TUN_IPV4, None, 10808, Some(255), &opts);
147147
assert!(yaml.contains("mark: 255"));

crates/kasumi-core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ pub mod singbox_config;
3535
pub mod state;
3636
pub mod sub_apply;
3737
pub mod tun;
38+
pub mod tun2socks_config;
3839
pub mod uid;
3940
pub mod xray_config;

crates/kasumi-core/src/state.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,11 @@ pub struct AdvancedSettings {
278278
pub tun_connect_timeout_ms: i64,
279279
/// TCP read/write timeout (ms) (hev `misc.tcp-read-write-timeout`).
280280
pub tun_tcp_rw_timeout_ms: i64,
281-
/// UDP read/write timeout (ms) (hev `misc.udp-read-write-timeout`).
281+
/// UDP read/write timeout (ms) (hev `misc.udp-read-write-timeout`, tun2socks
282+
/// `udp-timeout`).
282283
pub tun_udp_rw_timeout_ms: i64,
283-
/// Per-session TCP buffer size in bytes (hev `misc.tcp-buffer-size`).
284+
/// Per-session TCP buffer size in bytes (hev `misc.tcp-buffer-size`, tun2socks
285+
/// `tcp-send/receive-buffer-size`).
284286
pub tun_tcp_buffer_size: i64,
285287
/// UDP receive buffer (SO_RCVBUF) size in bytes (hev `misc.udp-recv-buffer-size`).
286288
pub tun_udp_recv_buffer_size: i64,

crates/kasumi-core/src/tun.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Runtime tuning for an external TUN engine, resolved from [`AdvancedSettings`].
22
//!
33
//! This is the small, transport-shaped subset of the settings an external engine
4-
//! (hev today) needs to spawn. It is resolved once where the data-path is started
4+
//! (hev, tun2socks) needs to spawn. It is resolved once where the data-path is started
55
//! and travels with the start request — on desktop that means across the privilege
66
//! boundary inside `StartDataPath` — so neither the root helper nor the Android
77
//! daemon has to re-read the settings schema to build the engine config.
@@ -60,8 +60,9 @@ fn knob(value: i64, default: u32) -> u32 {
6060
}
6161
}
6262

63-
/// Map the app log level to hev's vocabulary. hev has no "none"; the quietest it
64-
/// offers is `error`. An unset app level uses hev's own default, `warn`.
63+
/// Map the app log level to the engine vocabulary hev and tun2socks share. hev has
64+
/// no "none"; the quietest both offer is `error`. An unset app level uses `warn`,
65+
/// hev's own default.
6566
fn hev_log_level(level: Option<LogLevel>) -> &'static str {
6667
match level {
6768
Some(LogLevel::Debug) => "debug",
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
//! Build the YAML config for `tun2socks` (xjasonlyu/tun2socks).
2+
//!
3+
//! tun2socks creates the TUN device named in `device` (the OS routing layer then
4+
//! addresses it and steers traffic in — unlike hev it does not self-address) and
5+
//! relays everything to the local SOCKS5 the proxy core exposes. Passing a config
6+
//! file instead of CLI flags keeps us independent of the binary's flag parser
7+
//! (v2.7.0 switched to pflag, which rejects the single-dash long flags older
8+
//! builds accepted) and lets the TUN tuning knobs reach it like they reach hev.
9+
//! The keys mirror upstream `engine.Key`; only this file knows that shape.
10+
11+
use serde::Serialize;
12+
13+
use crate::tun::TunOptions;
14+
15+
#[derive(Serialize)]
16+
#[serde(rename_all = "kebab-case")]
17+
struct Tun2socksConfig {
18+
device: String,
19+
proxy: String,
20+
mtu: u32,
21+
loglevel: String,
22+
/// Wire format is a Go `time.Duration` string ("60000ms"); a bare integer is
23+
/// rejected by the parser.
24+
udp_timeout: String,
25+
/// Per-connection netstack TCP buffer sizes, in bytes (upstream parses the
26+
/// string with an optional size suffix).
27+
tcp_send_buffer_size: String,
28+
tcp_receive_buffer_size: String,
29+
/// SO_MARK stamped on tun2socks' own upstream sockets so an `ip rule` keeps
30+
/// them out of the tunnel — load-bearing on Android (mirrors hev's
31+
/// `socks5.mark`), unused on desktop (the core binds to the uplink instead),
32+
/// so omitted when `None`.
33+
#[serde(skip_serializing_if = "Option::is_none")]
34+
fwmark: Option<u32>,
35+
}
36+
37+
/// Render the tun2socks YAML for an interface `iface` (which tun2socks creates;
38+
/// the routing layer addresses it) bridging to `127.0.0.1:<socks_port>`.
39+
pub fn build_tun2socks_config(
40+
iface: &str,
41+
socks_port: u16,
42+
fwmark: Option<u32>,
43+
opts: &TunOptions,
44+
) -> String {
45+
let cfg = Tun2socksConfig {
46+
device: format!("tun://{iface}"),
47+
proxy: format!("socks5://127.0.0.1:{socks_port}"),
48+
mtu: opts.mtu,
49+
// The resolved level vocabulary (debug|info|warn|error) is a subset of
50+
// tun2socks' own (which adds silent), so it passes through unmapped.
51+
loglevel: opts.log_level.clone(),
52+
udp_timeout: format!("{}ms", opts.udp_rw_timeout_ms),
53+
tcp_send_buffer_size: opts.tcp_buffer_size.to_string(),
54+
tcp_receive_buffer_size: opts.tcp_buffer_size.to_string(),
55+
fwmark,
56+
};
57+
// Infallible for this plain-scalar shape.
58+
yaml_serde::to_string(&cfg).expect("tun2socks config serializes")
59+
}
60+
61+
#[cfg(test)]
62+
mod tests {
63+
use super::*;
64+
use crate::state::AdvancedSettings;
65+
66+
#[test]
67+
fn renders_expected_keys() {
68+
let opts = AdvancedSettings::default().tun_options();
69+
let yaml = build_tun2socks_config("kt0", 10808, None, &opts);
70+
assert!(yaml.contains("device: tun://kt0"));
71+
assert!(yaml.contains("proxy: socks5://127.0.0.1:10808"));
72+
assert!(yaml.contains("mtu: 9000"));
73+
assert!(yaml.contains("loglevel: warn"));
74+
assert!(yaml.contains("udp-timeout: 60000ms"));
75+
assert!(yaml.contains("tcp-send-buffer-size: '65536'"));
76+
assert!(yaml.contains("tcp-receive-buffer-size: '65536'"));
77+
// No fwmark passed → no key at all.
78+
assert!(!yaml.contains("fwmark"));
79+
}
80+
81+
#[test]
82+
fn fwmark_emitted_when_set() {
83+
// Android passes a fwmark so tun2socks' own sockets escape the tunnel;
84+
// desktop passes None (see `build_tun2socks_config`).
85+
let opts = AdvancedSettings::default().tun_options();
86+
let yaml = build_tun2socks_config("kt0", 10808, Some(255), &opts);
87+
assert!(yaml.contains("fwmark: 255"));
88+
}
89+
}

crates/kasumi-daemon/src/android/paths.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ pub const HEV_BIN: &str = "/data/adb/modules/kasumi-proxy/bin/hev-socks5-tunnel"
3535
/// hev writes its generated YAML here (and a second one for the force-proxy tun).
3636
pub const HEV_CONFIG: &str = "/data/adb/kasumi-proxy/run/hev.yml";
3737
pub const HEV2_CONFIG: &str = "/data/adb/kasumi-proxy/run/hev2.yml";
38+
/// tun2socks writes its generated YAML here (and a second one for the force-proxy tun).
39+
pub const TUN2SOCKS_CONFIG: &str = "/data/adb/kasumi-proxy/run/tun2socks.yml";
40+
pub const TUN2SOCKS2_CONFIG: &str = "/data/adb/kasumi-proxy/run/tun2socks2.yml";
3841
/// The sidecar sing-box (SingboxTun engine on a non-sing-box core) writes its
3942
/// generated bridge JSON here (and a second one for the force-proxy tun).
4043
pub const SINGBOX_BRIDGE_CONFIG: &str = "/data/adb/kasumi-proxy/run/singbox-bridge.json";

crates/kasumi-daemon/src/android/platform.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ use super::paths::{
3434
CORE_BINS, DATADIR, ENGINE_FILE, GEODAT2SRS_BIN, HEV_BIN, HEV_CONFIG, HEV2_CONFIG, IP, PIDFILE,
3535
RUN_DIR, SERVICE_STARTED_FILE, SERVICE_STATE_FILE, SINGBOX_BIN, SINGBOX_BRIDGE_CONFIG,
3636
SINGBOX_BRIDGE2_CONFIG, SOCKS_PORT_FILE, TUN_ENGINE_FILE, TUN_IFACE_FILE, TUN2_IFACE_FILE,
37-
TUN2SOCKS_BIN, TUN2SOCKS_PIDFILE, TUN2SOCKS2_PIDFILE, XRAY_BIN, backend_paths,
37+
TUN2SOCKS_BIN, TUN2SOCKS_CONFIG, TUN2SOCKS_PIDFILE, TUN2SOCKS2_CONFIG, TUN2SOCKS2_PIDFILE,
38+
XRAY_BIN, backend_paths,
3839
};
3940
use super::routing::{
4041
Action, AppFilter, FWMARK, RoutingState, apply_external_tun_routing, apply_strict_carveouts,
@@ -84,15 +85,16 @@ fn tun_helper_bin(tun: TunEngine) -> &'static str {
8485
}
8586
}
8687

87-
/// The config file a config-driven external engine writes at bring-up: hev's YAML or
88+
/// The config file an external engine writes at bring-up: tun2socks'/hev's YAML or
8889
/// the sidecar sing-box's JSON, per tun (the `2` variant is the force-proxy tun).
89-
/// tun2socks takes its args on the command line and ignores this.
9090
fn tun_cfg_path(tun: TunEngine, force: bool) -> &'static str {
9191
match (tun, force) {
9292
(TunEngine::SingboxTun, false) => SINGBOX_BRIDGE_CONFIG,
9393
(TunEngine::SingboxTun, true) => SINGBOX_BRIDGE2_CONFIG,
94-
(_, false) => HEV_CONFIG,
95-
(_, true) => HEV2_CONFIG,
94+
(TunEngine::Tun2socks, false) => TUN2SOCKS_CONFIG,
95+
(TunEngine::Tun2socks, true) => TUN2SOCKS2_CONFIG,
96+
(TunEngine::Hev, false) => HEV_CONFIG,
97+
(TunEngine::Hev, true) => HEV2_CONFIG,
9698
}
9799
}
98100

frontend/src/generated/bindings.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,9 +97,15 @@ export type AdvancedSettings_Deserialize = {
9797
tunConnectTimeoutMs?: number,
9898
/** TCP read/write timeout (ms) (hev `misc.tcp-read-write-timeout`). */
9999
tunTcpRwTimeoutMs?: number,
100-
/** UDP read/write timeout (ms) (hev `misc.udp-read-write-timeout`). */
100+
/**
101+
* UDP read/write timeout (ms) (hev `misc.udp-read-write-timeout`, tun2socks
102+
* `udp-timeout`).
103+
*/
101104
tunUdpRwTimeoutMs?: number,
102-
/** Per-session TCP buffer size in bytes (hev `misc.tcp-buffer-size`). */
105+
/**
106+
* Per-session TCP buffer size in bytes (hev `misc.tcp-buffer-size`, tun2socks
107+
* `tcp-send/receive-buffer-size`).
108+
*/
103109
tunTcpBufferSize?: number,
104110
/** UDP receive buffer (SO_RCVBUF) size in bytes (hev `misc.udp-recv-buffer-size`). */
105111
tunUdpRecvBufferSize?: number,
@@ -160,9 +166,15 @@ export type AdvancedSettings_Serialize = {
160166
tunConnectTimeoutMs: number,
161167
/** TCP read/write timeout (ms) (hev `misc.tcp-read-write-timeout`). */
162168
tunTcpRwTimeoutMs: number,
163-
/** UDP read/write timeout (ms) (hev `misc.udp-read-write-timeout`). */
169+
/**
170+
* UDP read/write timeout (ms) (hev `misc.udp-read-write-timeout`, tun2socks
171+
* `udp-timeout`).
172+
*/
164173
tunUdpRwTimeoutMs: number,
165-
/** Per-session TCP buffer size in bytes (hev `misc.tcp-buffer-size`). */
174+
/**
175+
* Per-session TCP buffer size in bytes (hev `misc.tcp-buffer-size`, tun2socks
176+
* `tcp-send/receive-buffer-size`).
177+
*/
166178
tunTcpBufferSize: number,
167179
/** UDP receive buffer (SO_RCVBUF) size in bytes (hev `misc.udp-recv-buffer-size`). */
168180
tunUdpRecvBufferSize: number,

src-tauri/src/desktop/paths.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ pub struct DesktopPaths {
7373
pub hev_bin: String,
7474
/// Where hev's generated YAML config is written at bring-up (run_dir).
7575
pub hev_config: String,
76+
/// Where tun2socks' generated YAML config is written at bring-up (run_dir).
77+
pub tun2socks_config: String,
7678
/// Where the sidecar sing-box (SingboxTun engine on a non-sing-box core) writes
7779
/// its generated bridge config at bring-up (run_dir).
7880
pub singbox_bridge_config: String,
@@ -216,6 +218,7 @@ impl DesktopPaths {
216218
tun2socks_bin: format!("{bin}/tun2socks"),
217219
hev_bin: format!("{bin}/hev-socks5-tunnel"),
218220
hev_config: format!("{run_dir}/hev.yml"),
221+
tun2socks_config: format!("{run_dir}/tun2socks.yml"),
219222
singbox_bridge_config: format!("{run_dir}/singbox-bridge.json"),
220223
geodat2srs_bin: format!("{bin}/geodat2srs"),
221224
backend,
@@ -308,6 +311,7 @@ impl DesktopPaths {
308311
tun2socks_bin: format!(r"{bin}\tun2socks.exe"),
309312
hev_bin: format!(r"{bin}\hev-socks5-tunnel.exe"),
310313
hev_config: format!(r"{run_dir}\hev.yml"),
314+
tun2socks_config: format!(r"{run_dir}\tun2socks.yml"),
311315
singbox_bridge_config: format!(r"{run_dir}\singbox-bridge.json"),
312316
geodat2srs_bin: format!(r"{bin}\geodat2srs.exe"),
313317
wintun_dll: format!(r"{bin}\wintun.dll"),

0 commit comments

Comments
 (0)