@@ -485,45 +485,77 @@ fn install_sidecar_nft_bypass_rules(proxy_uid: u32) -> Result<()> {
485485}
486486
487487const SIDECAR_IPTABLES_CHAIN : & str = "OPENSHELL_SIDECAR_BYPASS" ;
488+ const PROC_NET_IF_INET6_PATH : & str = "/proc/net/if_inet6" ;
488489
489490fn install_sidecar_iptables_legacy_bypass_rules ( proxy_uid : u32 ) -> Result < ( ) > {
490491 let ipv4_filter_tool = find_iptables_legacy ( ) . ok_or_else ( || {
491492 miette:: miette!(
492493 "trusted iptables-legacy helper not found; sidecar network enforcement fallback unavailable"
493494 )
494495 } ) ?;
495- let ipv6_fence_tool = find_ip6tables_legacy ( ) . ok_or_else ( || {
496- miette:: miette!(
497- "trusted ip6tables-legacy helper not found; sidecar network enforcement fallback cannot fence IPv6"
498- )
499- } ) ?;
500496
501- cleanup_sidecar_iptables_legacy_rules ( & ipv4_filter_tool) ;
502- cleanup_sidecar_iptables_legacy_rules ( & ipv6_fence_tool) ;
497+ let ipv6_fence_tool = if current_namespace_has_non_loopback_ipv6 ( ) ? {
498+ Some ( find_ip6tables_legacy ( ) . ok_or_else ( || {
499+ miette:: miette!(
500+ "trusted ip6tables-legacy helper not found; sidecar network enforcement fallback cannot fence IPv6"
501+ )
502+ } ) ?)
503+ } else {
504+ warn ! (
505+ "Skipping IPv6 sidecar iptables-legacy fallback because the current namespace has no non-loopback IPv6 interface"
506+ ) ;
507+ None
508+ } ;
509+
510+ cleanup_sidecar_iptables_legacy_rule_families ( & ipv4_filter_tool, ipv6_fence_tool. as_deref ( ) ) ;
503511
504512 if let Err ( e) = install_sidecar_iptables_legacy_family_rules (
505513 & ipv4_filter_tool,
506514 proxy_uid,
507515 "icmp-port-unreachable" ,
508516 ) {
509- cleanup_sidecar_iptables_legacy_rules ( & ipv4_filter_tool) ;
510- cleanup_sidecar_iptables_legacy_rules ( & ipv6_fence_tool) ;
517+ cleanup_sidecar_iptables_legacy_rule_families (
518+ & ipv4_filter_tool,
519+ ipv6_fence_tool. as_deref ( ) ,
520+ ) ;
511521 return Err ( e) ;
512522 }
513523
514- if let Err ( e) = install_sidecar_iptables_legacy_family_rules (
515- & ipv6_fence_tool,
516- proxy_uid,
517- "icmp6-port-unreachable" ,
518- ) {
519- cleanup_sidecar_iptables_legacy_rules ( & ipv4_filter_tool) ;
520- cleanup_sidecar_iptables_legacy_rules ( & ipv6_fence_tool) ;
521- return Err ( e) ;
524+ if let Some ( ipv6_fence_tool) = ipv6_fence_tool {
525+ if let Err ( e) = install_sidecar_iptables_legacy_family_rules (
526+ & ipv6_fence_tool,
527+ proxy_uid,
528+ "icmp6-port-unreachable" ,
529+ ) {
530+ cleanup_sidecar_iptables_legacy_rule_families (
531+ & ipv4_filter_tool,
532+ Some ( & ipv6_fence_tool) ,
533+ ) ;
534+ return Err ( e) ;
535+ }
522536 }
523537
524538 Ok ( ( ) )
525539}
526540
541+ fn current_namespace_has_non_loopback_ipv6 ( ) -> Result < bool > {
542+ match std:: fs:: read_to_string ( PROC_NET_IF_INET6_PATH ) {
543+ Ok ( content) => Ok ( has_non_loopback_ipv6_interface ( & content) ) ,
544+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: NotFound => Ok ( false ) ,
545+ Err ( e) => Err ( miette:: miette!(
546+ "failed to inspect {PROC_NET_IF_INET6_PATH} before installing sidecar IPv6 fence: {e}"
547+ ) ) ,
548+ }
549+ }
550+
551+ fn has_non_loopback_ipv6_interface ( content : & str ) -> bool {
552+ content. lines ( ) . any ( |line| {
553+ line. split_whitespace ( )
554+ . nth ( 5 )
555+ . is_some_and ( |iface| iface != "lo" )
556+ } )
557+ }
558+
527559fn install_sidecar_iptables_legacy_family_rules (
528560 cmd : & str ,
529561 proxy_uid : u32 ,
@@ -597,6 +629,13 @@ fn cleanup_sidecar_iptables_legacy_rules(iptables_cmd: &str) {
597629 let _ = run_iptables_legacy_current_namespace ( iptables_cmd, & [ "-X" , SIDECAR_IPTABLES_CHAIN ] ) ;
598630}
599631
632+ fn cleanup_sidecar_iptables_legacy_rule_families ( ipv4_cmd : & str , ipv6_cmd : Option < & str > ) {
633+ cleanup_sidecar_iptables_legacy_rules ( ipv4_cmd) ;
634+ if let Some ( ipv6_cmd) = ipv6_cmd {
635+ cleanup_sidecar_iptables_legacy_rules ( ipv6_cmd) ;
636+ }
637+ }
638+
600639/// Run an `ip` command on the host.
601640fn run_ip ( args : & [ & str ] ) -> Result < ( ) > {
602641 let ip_path = find_trusted_binary ( "ip" , IP_SEARCH_PATHS ) ?;
@@ -906,6 +945,29 @@ mod tests {
906945 }
907946 }
908947
948+ #[ test]
949+ fn non_loopback_ipv6_detector_ignores_empty_input ( ) {
950+ assert ! ( !has_non_loopback_ipv6_interface( "" ) ) ;
951+ assert ! ( !has_non_loopback_ipv6_interface( "\n \n " ) ) ;
952+ }
953+
954+ #[ test]
955+ fn non_loopback_ipv6_detector_ignores_loopback ( ) {
956+ let content = "00000000000000000000000000000001 01 80 10 80 lo\n " ;
957+
958+ assert ! ( !has_non_loopback_ipv6_interface( content) ) ;
959+ }
960+
961+ #[ test]
962+ fn non_loopback_ipv6_detector_detects_pod_interface ( ) {
963+ let content = "\
964+ 00000000000000000000000000000001 01 80 10 80 lo
965+ fe800000000000000000000000000001 02 40 20 80 eth0
966+ " ;
967+
968+ assert ! ( has_non_loopback_ipv6_interface( content) ) ;
969+ }
970+
909971 #[ test]
910972 #[ ignore = "requires root privileges" ]
911973 fn test_create_and_drop_namespace ( ) {
0 commit comments