@@ -10,7 +10,7 @@ use std::ptr;
1010use std:: sync:: atomic:: { AtomicI32 , Ordering } ;
1111use std:: time:: { Duration , Instant } ;
1212
13- use crate :: { embedded_runtime, ffi, procguard} ;
13+ use crate :: { embedded_runtime, ffi, nft_ruleset , procguard} ;
1414
1515pub const VM_RUNTIME_DIR_ENV : & str = "OPENSHELL_VM_RUNTIME_DIR" ;
1616
@@ -444,6 +444,12 @@ fn setup_tap_networking(tap_device: &str, host_ip: &str, gateway_port: u16) -> R
444444 enable_ip_forwarding ( ) ?;
445445
446446 let subnet = tap_subnet_from_host_ip ( host_ip) ;
447+ let table_name = nft_ruleset:: teardown_table_name ( tap_device) ;
448+
449+ // Delete any stale nftables table from a previous driver run.
450+ let _ = run_cmd ( "nft" , & [ "delete" , "table" , "ip" , & table_name] ) ;
451+
452+ // Clean up legacy iptables rules from older driver versions.
447453 let _ = run_cmd (
448454 "iptables" ,
449455 & [
@@ -457,27 +463,10 @@ fn setup_tap_networking(tap_device: &str, host_ip: &str, gateway_port: u16) -> R
457463 "MASQUERADE" ,
458464 ] ,
459465 ) ;
460- run_cmd (
461- "iptables" ,
462- & [
463- "-t" ,
464- "nat" ,
465- "-A" ,
466- "POSTROUTING" ,
467- "-s" ,
468- & subnet,
469- "-j" ,
470- "MASQUERADE" ,
471- ] ,
472- ) ?;
473466 let _ = run_cmd (
474467 "iptables" ,
475468 & [ "-D" , "FORWARD" , "-i" , tap_device, "-j" , "ACCEPT" ] ,
476469 ) ;
477- run_cmd (
478- "iptables" ,
479- & [ "-A" , "FORWARD" , "-i" , tap_device, "-j" , "ACCEPT" ] ,
480- ) ?;
481470 let _ = run_cmd (
482471 "iptables" ,
483472 & [
@@ -493,43 +482,31 @@ fn setup_tap_networking(tap_device: &str, host_ip: &str, gateway_port: u16) -> R
493482 "ACCEPT" ,
494483 ] ,
495484 ) ;
496- run_cmd (
497- "iptables" ,
498- & [
499- "-A" ,
500- "FORWARD" ,
501- "-o" ,
502- tap_device,
503- "-m" ,
504- "state" ,
505- "--state" ,
506- "RELATED,ESTABLISHED" ,
507- "-j" ,
508- "ACCEPT" ,
509- ] ,
510- ) ?;
511- // Allow guest → host traffic only to the gateway gRPC port.
512- // Previous versions accepted ALL inbound traffic from the TAP
513- // interface; scope to the specific port so the guest cannot reach
514- // other host services.
515485 let port_str = gateway_port. to_string ( ) ;
516486 let _ = run_cmd (
517487 "iptables" ,
518488 & [
519489 "-D" , "INPUT" , "-i" , tap_device, "-p" , "tcp" , "--dport" , & port_str, "-j" , "ACCEPT" ,
520490 ] ,
521491 ) ;
522- run_cmd (
492+ let _ = run_cmd (
523493 "iptables" ,
524- & [
525- "-A" , "INPUT" , "-i" , tap_device, "-p" , "tcp" , "--dport" , & port_str, "-j" , "ACCEPT" ,
526- ] ,
527- ) ?;
494+ & [ "-D" , "INPUT" , "-i" , tap_device, "-j" , "ACCEPT" ] ,
495+ ) ;
496+
497+ // Load nftables ruleset atomically.
498+ let ruleset = nft_ruleset:: generate_tap_ruleset ( tap_device, & subnet, gateway_port) ;
499+ run_nft_stdin ( & ruleset) ?;
528500
529501 Ok ( ( ) )
530502}
531503
532504fn teardown_tap_networking ( tap_device : & str , host_ip : & str , gateway_port : u16 ) {
505+ // Delete the entire nftables table — single atomic operation.
506+ let table_name = nft_ruleset:: teardown_table_name ( tap_device) ;
507+ let _ = run_cmd ( "nft" , & [ "delete" , "table" , "ip" , & table_name] ) ;
508+
509+ // Clean up legacy iptables rules from older driver versions.
533510 let subnet = tap_subnet_from_host_ip ( host_ip) ;
534511 let _ = run_cmd (
535512 "iptables" ,
@@ -550,8 +527,6 @@ fn teardown_tap_networking(tap_device: &str, host_ip: &str, gateway_port: u16) {
550527 "iptables" ,
551528 & [ "-D" , "FORWARD" , "-i" , tap_device, "-j" , "ACCEPT" ] ,
552529 ) ;
553- // Remove the port-scoped INPUT rule. Also try the legacy blanket
554- // rule so stale rules from older driver versions are cleaned up.
555530 if gateway_port > 0 {
556531 let port_str = gateway_port. to_string ( ) ;
557532 let _ = run_cmd (
@@ -578,6 +553,7 @@ fn teardown_tap_networking(tap_device: &str, host_ip: &str, gateway_port: u16) {
578553 "MASQUERADE" ,
579554 ] ,
580555 ) ;
556+
581557 let _ = run_cmd ( "ip" , & [ "link" , "set" , tap_device, "down" ] ) ;
582558 let _ = run_cmd ( "ip" , & [ "tuntap" , "del" , "dev" , tap_device, "mode" , "tap" ] ) ;
583559}
@@ -614,6 +590,35 @@ fn run_cmd(cmd: &str, args: &[&str]) -> Result<(), String> {
614590 }
615591}
616592
593+ fn run_nft_stdin ( ruleset : & str ) -> Result < ( ) , String > {
594+ use std:: io:: Write ;
595+
596+ let mut child = StdCommand :: new ( "nft" )
597+ . args ( [ "-f" , "-" ] )
598+ . stdin ( Stdio :: piped ( ) )
599+ . stdout ( Stdio :: piped ( ) )
600+ . stderr ( Stdio :: piped ( ) )
601+ . spawn ( )
602+ . map_err ( |e| format ! ( "failed to run nft: {e}" ) ) ?;
603+
604+ if let Some ( mut stdin) = child. stdin . take ( ) {
605+ stdin
606+ . write_all ( ruleset. as_bytes ( ) )
607+ . map_err ( |e| format ! ( "failed to write nft ruleset: {e}" ) ) ?;
608+ }
609+
610+ let output = child
611+ . wait_with_output ( )
612+ . map_err ( |e| format ! ( "failed to wait for nft: {e}" ) ) ?;
613+
614+ if output. status . success ( ) {
615+ Ok ( ( ) )
616+ } else {
617+ let stderr = String :: from_utf8_lossy ( & output. stderr ) ;
618+ Err ( format ! ( "nft -f - failed: {stderr}" ) )
619+ }
620+ }
621+
617622/// RAII guard that tears down TAP networking on drop.
618623struct TapGuard {
619624 tap_device : String ,
@@ -731,7 +736,7 @@ fn run_libkrun_vm(config: &VmLaunchConfig) -> Result<(), String> {
731736 // on its own service ports (DNS:53, DHCP, HTTP API:80).
732737 //
733738 // That network plane is also what the sandbox supervisor's
734- // per-sandbox netns (veth pair + iptables , see
739+ // per-sandbox netns (veth pair + nftables , see
735740 // `openshell-sandbox/src/sandbox/linux/netns.rs`) branches off of;
736741 // libkrun's built-in TSI socket impersonation would not satisfy
737742 // those kernel-level primitives.
@@ -1384,4 +1389,17 @@ mod tests {
13841389 assert ! ( !cmdline. contains( "VM_NET_DNS=" ) ) ;
13851390 assert ! ( !cmdline. contains( "GPU_ENABLED=" ) ) ;
13861391 }
1392+
1393+ #[ test]
1394+ fn tap_subnet_from_host_ip_calculates_slash30_base ( ) {
1395+ assert_eq ! ( tap_subnet_from_host_ip( "10.0.128.1" ) , "10.0.128.0/30" ) ;
1396+ assert_eq ! ( tap_subnet_from_host_ip( "10.0.128.2" ) , "10.0.128.0/30" ) ;
1397+ assert_eq ! ( tap_subnet_from_host_ip( "10.0.128.5" ) , "10.0.128.4/30" ) ;
1398+ }
1399+
1400+ #[ test]
1401+ fn tap_subnet_from_host_ip_handles_invalid_ip ( ) {
1402+ let result = tap_subnet_from_host_ip ( "not-an-ip" ) ;
1403+ assert_eq ! ( result, "not-an-ip/30" ) ;
1404+ }
13871405}
0 commit comments