@@ -53,6 +53,96 @@ fn s(x: impl Into<String>) -> String {
5353 x. into ( )
5454}
5555
56+ /// Run a command silently, ignoring exit status and output. Used by the
57+ /// pre-clean pass where "not present" is the expected outcome on a fresh box.
58+ fn try_cmd ( args : & [ & str ] ) {
59+ let _ = Command :: new ( args[ 0 ] ) . args ( & args[ 1 ..] ) . output ( ) ;
60+ }
61+
62+ /// Best-effort removal of any leftover state this config would install, so we
63+ /// survive a previous crashed run (SIGKILL / panic=abort / OOM) that didn't
64+ /// run the Drop-based rollback. All failures are expected on a clean system
65+ /// and silently ignored.
66+ pub fn pre_clean ( cfg : & Config , local_rules : & [ CompiledRule ] , remote_rules : & [ CompiledRule ] ) {
67+ let tun = & cfg. tun ;
68+ let table = cfg. table . to_string ( ) ;
69+ let tproxy_table = ( cfg. table + 1 ) . to_string ( ) ;
70+ let mark_tproxy = format ! ( "0x{:x}" , cfg. mark_tproxy) ;
71+
72+ // iptables / ip6tables: delete the exact TPROXY rule we'd add. iptables -D
73+ // only removes an exactly-matching rule, so we rebuild the full arg list.
74+ for r in remote_rules {
75+ let v6 = matches ! ( r. af, AfSpec :: Ip6 ) ;
76+ let bin = if v6 { "ip6tables" } else { "iptables" } ;
77+ let on_ip = if v6 { "::1" } else { "127.0.0.1" } ;
78+ let mut args: Vec < String > = vec ! [
79+ s( bin) , s( "-t" ) , s( "mangle" ) , s( "-D" ) , s( "PREROUTING" ) ,
80+ s( "!" ) , s( "-i" ) , tun. clone( ) ,
81+ s( "-p" ) , s( "udp" ) ,
82+ ] ;
83+ if let Some ( dp) = r. dstport {
84+ args. extend ( [ s ( "--dport" ) , dp. to_string ( ) ] ) ;
85+ }
86+ if let Some ( net) = & r. srcip {
87+ args. extend ( [ s ( "-s" ) , net. to_string ( ) ] ) ;
88+ }
89+ if let Some ( net) = & r. dstip {
90+ args. extend ( [ s ( "-d" ) , net. to_string ( ) ] ) ;
91+ }
92+ if let Some ( ( lo, hi) ) = r. srcport {
93+ args. extend ( [ s ( "--sport" ) , format ! ( "{}:{}" , lo, hi) ] ) ;
94+ }
95+ args. extend ( [ s ( "-m" ) , s ( "mark" ) , s ( "!" ) , s ( "--mark" ) , format ! ( "0x{:x}" , cfg. mark_reinject) ] ) ;
96+ args. extend ( [ s ( "-m" ) , s ( "addrtype" ) , s ( "--dst-type" ) , s ( "LOCAL" ) ] ) ;
97+ args. extend ( [ s ( "-j" ) , s ( "TPROXY" ) ] ) ;
98+ args. extend ( [ s ( "--on-ip" ) , s ( on_ip) ] ) ;
99+ args. extend ( [ s ( "--on-port" ) , cfg. tproxy_port . to_string ( ) ] ) ;
100+ args. extend ( [ s ( "--tproxy-mark" ) , mark_tproxy. clone ( ) ] ) ;
101+ let refs: Vec < & str > = args. iter ( ) . map ( |x| x. as_str ( ) ) . collect ( ) ;
102+ try_cmd ( & refs) ;
103+ }
104+
105+ // Per-local_traffic ip rule: build the same spec as apply() but with `del`.
106+ for r in local_rules {
107+ let v6 = matches ! ( r. af, AfSpec :: Ip6 ) ;
108+ let mut args: Vec < String > = vec ! [ s( "ip" ) ] ;
109+ if v6 {
110+ args. push ( s ( "-6" ) ) ;
111+ }
112+ args. extend ( [ s ( "rule" ) , s ( "del" ) , s ( "iif" ) , s ( "lo" ) ] ) ;
113+ if let Some ( net) = & r. dstip {
114+ args. extend ( [ s ( "to" ) , net. to_string ( ) ] ) ;
115+ }
116+ if let Some ( net) = & r. srcip {
117+ args. extend ( [ s ( "from" ) , net. to_string ( ) ] ) ;
118+ }
119+ if let Some ( dp) = r. dstport {
120+ args. extend ( [ s ( "dport" ) , dp. to_string ( ) ] ) ;
121+ }
122+ if let Some ( ( lo, hi) ) = r. srcport {
123+ args. extend ( [ s ( "sport" ) , format ! ( "{}-{}" , lo, hi) ] ) ;
124+ }
125+ args. extend ( [ s ( "fwmark" ) , format ! ( "0x0/0x{:x}" , cfg. mark_reinject) ] ) ;
126+ args. extend ( [ s ( "lookup" ) , table. clone ( ) ] ) ;
127+ let refs: Vec < & str > = args. iter ( ) . map ( |x| x. as_str ( ) ) . collect ( ) ;
128+ try_cmd ( & refs) ;
129+ }
130+
131+ // tproxy fwmark rule (v4 + v6)
132+ try_cmd ( & [ "ip" , "rule" , "del" , "fwmark" , & mark_tproxy, "lookup" , & tproxy_table] ) ;
133+ try_cmd ( & [ "ip" , "-6" , "rule" , "del" , "fwmark" , & mark_tproxy, "lookup" , & tproxy_table] ) ;
134+
135+ // Routes: flushing our own tables is cheaper + more robust than reverse-
136+ // engineering each entry, and the tables are ours by config so it is safe.
137+ try_cmd ( & [ "ip" , "route" , "flush" , "table" , & table] ) ;
138+ try_cmd ( & [ "ip" , "-6" , "route" , "flush" , "table" , & table] ) ;
139+ try_cmd ( & [ "ip" , "route" , "flush" , "table" , & tproxy_table] ) ;
140+ try_cmd ( & [ "ip" , "-6" , "route" , "flush" , "table" , & tproxy_table] ) ;
141+
142+ // Leftover TUN device from a crashed run. The new apply() will re-open it.
143+ try_cmd ( & [ "ip" , "link" , "del" , tun] ) ;
144+ }
145+
56146fn read_sysctl ( path : & str , fallback : & str ) -> String {
57147 std:: fs:: read_to_string ( path)
58148 . map ( |x| x. trim ( ) . to_string ( ) )
0 commit comments