@@ -12,7 +12,9 @@ use regex::Regex;
1212use tokio:: process:: Child ;
1313
1414use kasumi_core:: enums:: { CoreEngine , TunEngine } ;
15+ use kasumi_core:: hev_config:: build_hev_config;
1516use kasumi_core:: state:: { AppState , DEFAULT_LOCAL_SOCKS_PORT } ;
17+ use kasumi_core:: tun:: TunOptions ;
1618
1719use crate :: commands:: { CommandError , build_profile_config} ;
1820use crate :: fs:: { exists, read_text, remove_file, write_text} ;
@@ -51,11 +53,12 @@ pub fn random_tun_iface() -> String {
5153}
5254
5355/// Build the config for `profile_id` (else the active profile), write it and the
54- /// engine marker, and return the engine + resolved TUN engine + local SOCKS port.
56+ /// engine marker, and return the engine + resolved TUN engine + external-engine
57+ /// tuning + local SOCKS port.
5558pub async fn resolve_and_write_config (
5659 platform : & dyn Platform ,
5760 profile_id : Option < & str > ,
58- ) -> Result < ( Engine , TunEngine , u16 ) , CommandError > {
61+ ) -> Result < ( Engine , TunEngine , TunOptions , u16 ) , CommandError > {
5962 let paths = platform. paths ( ) ;
6063 let state = read_json :: < AppState > ( & paths. app_state ) . await ;
6164 let id = profile_id
@@ -78,10 +81,12 @@ pub async fn resolve_and_write_config(
7881 write_text ( & paths. engine_file , engine_label ( engine) )
7982 . await
8083 . map_err ( |e| CommandError ( e. to_string ( ) ) ) ?;
81- let socks_port = state
82- . and_then ( |s| s. settings . local_socks_port )
84+ let settings = state. map ( |s| s. settings ) . unwrap_or_default ( ) ;
85+ let socks_port = settings
86+ . local_socks_port
8387 . unwrap_or ( DEFAULT_LOCAL_SOCKS_PORT ) ;
84- Ok ( ( engine, tun, socks_port) )
88+ let tun_opts = settings. tun_options ( ) ;
89+ Ok ( ( engine, tun, tun_opts, socks_port) )
8590}
8691
8792fn engine_label ( engine : CoreEngine ) -> & ' static str {
@@ -326,62 +331,68 @@ pub async fn spawn_core(
326331 . await
327332}
328333
329- /// Spawn tun2socks bridging `iface` to the SOCKS port, logging to `log_path`.
330- /// `fwmark`, when set, marks tun2socks' own upstream socket so an `ip rule` can
331- /// keep it out of the tunnel — that's a Linux SO_MARK feature. Windows has no
332- /// fwmark (its server bypass is a host route), so it passes `None`.
333- pub async fn spawn_tun2socks (
334- bin : & str ,
335- iface : & str ,
336- socks_port : u16 ,
337- log_path : & Path ,
338- fwmark : Option < u32 > ,
339- ) -> std:: io:: Result < Child > {
340- spawn_logged (
341- & tun2socks_argv ( bin, iface, socks_port, fwmark) ,
342- & HashMap :: new ( ) ,
343- log_path,
344- false ,
345- )
346- . await
347- }
348-
349- /// tun2socks' argv. Split out (like [`core_argv`]) so a caller that supervises the
350- /// spawn itself can reuse the exact command. tun2socks needs no special env.
351- pub fn tun2socks_argv ( bin : & str , iface : & str , socks_port : u16 , fwmark : Option < u32 > ) -> Vec < String > {
334+ /// Spawn tun2socks bridging the tun to the local SOCKS port. `fwmark`, when set,
335+ /// marks tun2socks' own upstream socket so an `ip rule` can keep it out of the
336+ /// tunnel — a Linux SO_MARK feature. Windows has no fwmark (its server bypass is a
337+ /// host route), so it passes `None`.
338+ async fn spawn_tun2socks ( s : & TunSpawn < ' _ > ) -> std:: io:: Result < Child > {
352339 let mut argv = vec ! [
353- bin. to_owned( ) ,
340+ s . bin. to_owned( ) ,
354341 "-device" . into( ) ,
355- format!( "tun://{iface}" ) ,
342+ format!( "tun://{}" , s . iface ) ,
356343 "-proxy" . into( ) ,
357- format!( "socks5://127.0.0.1:{socks_port}" ) ,
344+ format!( "socks5://127.0.0.1:{}" , s. socks_port) ,
345+ // The TUN MTU setting applies to every external engine; tun2socks creates
346+ // its own tun, so it must be told the MTU here (hev takes it in its YAML).
347+ "-mtu" . into( ) ,
348+ s. opts. mtu. to_string( ) ,
358349 ] ;
359- if let Some ( mark) = fwmark {
350+ if let Some ( mark) = s . fwmark {
360351 argv. push ( "-fwmark" . into ( ) ) ;
361352 argv. push ( mark. to_string ( ) ) ;
362353 }
363- argv
354+ spawn_logged ( & argv, & std:: collections:: HashMap :: new ( ) , s. log_path , false ) . await
355+ }
356+
357+ /// Everything needed to bring up one external TUN engine, gathered so adding an
358+ /// engine is a single match arm. `bin` is the engine binary (resolved per-platform);
359+ /// `cfg_path` is where a config-file engine (hev) writes its YAML; `ipv4`/`ipv6` are
360+ /// the addresses such an engine assigns to the tun it creates itself; `opts` carries
361+ /// the resolved tuning.
362+ pub struct TunSpawn < ' a > {
363+ pub bin : & ' a str ,
364+ pub iface : & ' a str ,
365+ pub ipv4 : & ' a str ,
366+ pub ipv6 : Option < & ' a str > ,
367+ pub socks_port : u16 ,
368+ pub log_path : & ' a Path ,
369+ pub fwmark : Option < u32 > ,
370+ pub cfg_path : & ' a Path ,
371+ pub opts : & ' a TunOptions ,
364372}
365373
366374/// The single place that knows how to launch an external TUN engine. Every shell
367375/// (desktop, root daemon) routes its bring-up through here, so adding a new engine
368376/// is one more arm — nothing else in the orchestration learns engine specifics.
369- /// `bin` is the engine's binary (resolved per-platform). `SingboxTun` has no
370- /// external helper (the sing-box core owns the tun) and must not reach this.
371- pub async fn spawn_tun_engine (
372- tun : TunEngine ,
373- bin : & str ,
374- iface : & str ,
375- socks_port : u16 ,
376- log_path : & Path ,
377- fwmark : Option < u32 > ,
378- ) -> std:: io:: Result < Child > {
377+ /// `SingboxTun` has no external helper (the sing-box core owns the tun) and must
378+ /// not reach this.
379+ pub async fn spawn_tun_engine ( tun : TunEngine , s : & TunSpawn < ' _ > ) -> std:: io:: Result < Child > {
379380 match tun {
380- TunEngine :: Tun2socks => spawn_tun2socks ( bin, iface, socks_port, log_path, fwmark) . await ,
381+ TunEngine :: Tun2socks => spawn_tun2socks ( s) . await ,
382+ TunEngine :: Hev => spawn_hev ( s) . await ,
381383 TunEngine :: SingboxTun => unreachable ! ( "SingboxTun has no external helper to spawn" ) ,
382384 }
383385}
384386
387+ /// hev creates and addresses its own tun from a YAML config: render it, write it
388+ /// next to the runtime state, then run `<hev_bin> <cfg>`.
389+ async fn spawn_hev ( s : & TunSpawn < ' _ > ) -> std:: io:: Result < Child > {
390+ let yaml = build_hev_config ( s. iface , s. ipv4 , s. ipv6 , s. socks_port , s. fwmark , s. opts ) ;
391+ write_text ( s. cfg_path , & yaml) . await ?;
392+ let argv = [ s. bin . to_owned ( ) , s. cfg_path . to_string_lossy ( ) . into_owned ( ) ] ;
393+ spawn_logged ( & argv, & std:: collections:: HashMap :: new ( ) , s. log_path , false ) . await
394+ }
395+
385396/// Confirm the core stayed up: a bad config makes it exit within ~1s.
386397pub async fn verify_core_alive ( pid : i32 , bin : & str , attempts : u32 , delay : Duration ) -> bool {
387398 for _ in 0 ..attempts {
@@ -448,7 +459,7 @@ mod tests {
448459 . unwrap ( ) ;
449460
450461 // No explicit id → uses active_id.
451- let ( engine, tun, socks) = resolve_and_write_config ( & p, None ) . await . unwrap ( ) ;
462+ let ( engine, tun, _tun_opts , socks) = resolve_and_write_config ( & p, None ) . await . unwrap ( ) ;
452463 assert_eq ! ( engine, CoreEngine :: Xray ) ;
453464 assert_eq ! ( tun, TunEngine :: Tun2socks ) ;
454465 assert_eq ! ( socks, 11080 ) ;
0 commit comments