1- import { ensureFirewallNetwork , setupHostIptables , cleanupHostIptables , cleanupFirewallNetwork } from './host-iptables' ;
1+ import { ensureFirewallNetwork , setupHostIptables , cleanupHostIptables , cleanupFirewallNetwork , _resetIpv6State } from './host-iptables' ;
22import execa from 'execa' ;
33
44// Mock execa
@@ -19,6 +19,7 @@ jest.mock('./logger', () => ({
1919describe ( 'host-iptables' , ( ) => {
2020 beforeEach ( ( ) => {
2121 jest . clearAllMocks ( ) ;
22+ _resetIpv6State ( ) ;
2223 } ) ;
2324
2425 describe ( 'ensureFirewallNetwork' , ( ) => {
@@ -490,6 +491,48 @@ describe('host-iptables', () => {
490491 ] ) ;
491492 } ) ;
492493
494+ it ( 'should disable IPv6 via sysctl when ip6tables unavailable' , async ( ) => {
495+ // Make ip6tables unavailable
496+ mockedExeca
497+ . mockResolvedValueOnce ( { stdout : 'fw-bridge' , stderr : '' , exitCode : 0 } as any )
498+ // iptables -L DOCKER-USER permission check
499+ . mockResolvedValueOnce ( { stdout : '' , stderr : '' , exitCode : 0 } as any )
500+ // chain existence check (doesn't exist)
501+ . mockResolvedValueOnce ( { exitCode : 1 } as any ) ;
502+
503+ // All subsequent calls succeed (except ip6tables)
504+ mockedExeca . mockImplementation ( ( ( cmd : string , _args : string [ ] ) => {
505+ if ( cmd === 'ip6tables' ) {
506+ return Promise . reject ( new Error ( 'ip6tables not found' ) ) ;
507+ }
508+ return Promise . resolve ( { stdout : '' , stderr : '' , exitCode : 0 } ) ;
509+ } ) as any ) ;
510+
511+ await setupHostIptables ( '172.30.0.10' , 3128 , [ '8.8.8.8' , '8.8.4.4' ] ) ;
512+
513+ // Verify sysctl was called to disable IPv6
514+ expect ( mockedExeca ) . toHaveBeenCalledWith ( 'sysctl' , [ '-w' , 'net.ipv6.conf.all.disable_ipv6=1' ] ) ;
515+ expect ( mockedExeca ) . toHaveBeenCalledWith ( 'sysctl' , [ '-w' , 'net.ipv6.conf.default.disable_ipv6=1' ] ) ;
516+ } ) ;
517+
518+ it ( 'should not disable IPv6 via sysctl when ip6tables is available' , async ( ) => {
519+ mockedExeca
520+ // Mock getNetworkBridgeName
521+ . mockResolvedValueOnce ( { stdout : 'fw-bridge' , stderr : '' , exitCode : 0 } as any )
522+ // Mock iptables -L DOCKER-USER (permission check)
523+ . mockResolvedValueOnce ( { stdout : '' , stderr : '' , exitCode : 0 } as any )
524+ // Mock chain existence check (doesn't exist)
525+ . mockResolvedValueOnce ( { exitCode : 1 } as any ) ;
526+
527+ mockedExeca . mockResolvedValue ( { stdout : '' , stderr : '' , exitCode : 0 } as any ) ;
528+
529+ await setupHostIptables ( '172.30.0.10' , 3128 , [ '8.8.8.8' , '8.8.4.4' ] ) ;
530+
531+ // Verify sysctl was NOT called to disable IPv6
532+ expect ( mockedExeca ) . not . toHaveBeenCalledWith ( 'sysctl' , [ '-w' , 'net.ipv6.conf.all.disable_ipv6=1' ] ) ;
533+ expect ( mockedExeca ) . not . toHaveBeenCalledWith ( 'sysctl' , [ '-w' , 'net.ipv6.conf.default.disable_ipv6=1' ] ) ;
534+ } ) ;
535+
493536 it ( 'should not create IPv6 chain when no IPv6 DNS servers' , async ( ) => {
494537 mockedExeca
495538 // Mock getNetworkBridgeName
@@ -541,6 +584,39 @@ describe('host-iptables', () => {
541584 expect ( mockedExeca ) . toHaveBeenCalledWith ( 'ip6tables' , [ '-t' , 'filter' , '-X' , 'FW_WRAPPER_V6' ] , { reject : false } ) ;
542585 } ) ;
543586
587+ it ( 'should re-enable IPv6 via sysctl on cleanup if it was disabled' , async ( ) => {
588+ // First, simulate setup that disabled IPv6
589+ mockedExeca
590+ . mockResolvedValueOnce ( { stdout : 'fw-bridge' , stderr : '' , exitCode : 0 } as any )
591+ . mockResolvedValueOnce ( { stdout : '' , stderr : '' , exitCode : 0 } as any )
592+ . mockResolvedValueOnce ( { exitCode : 1 } as any ) ;
593+
594+ // Make ip6tables unavailable to trigger sysctl disable
595+ mockedExeca . mockImplementation ( ( ( cmd : string ) => {
596+ if ( cmd === 'ip6tables' ) {
597+ return Promise . reject ( new Error ( 'ip6tables not found' ) ) ;
598+ }
599+ return Promise . resolve ( { stdout : '' , stderr : '' , exitCode : 0 } ) ;
600+ } ) as any ) ;
601+
602+ await setupHostIptables ( '172.30.0.10' , 3128 , [ '8.8.8.8' ] ) ;
603+
604+ // Now run cleanup
605+ jest . clearAllMocks ( ) ;
606+ mockedExeca . mockImplementation ( ( ( cmd : string ) => {
607+ if ( cmd === 'ip6tables' ) {
608+ return Promise . reject ( new Error ( 'ip6tables not found' ) ) ;
609+ }
610+ return Promise . resolve ( { stdout : '' , stderr : '' , exitCode : 0 } ) ;
611+ } ) as any ) ;
612+
613+ await cleanupHostIptables ( ) ;
614+
615+ // Verify IPv6 was re-enabled via sysctl
616+ expect ( mockedExeca ) . toHaveBeenCalledWith ( 'sysctl' , [ '-w' , 'net.ipv6.conf.all.disable_ipv6=0' ] ) ;
617+ expect ( mockedExeca ) . toHaveBeenCalledWith ( 'sysctl' , [ '-w' , 'net.ipv6.conf.default.disable_ipv6=0' ] ) ;
618+ } ) ;
619+
544620 it ( 'should not throw on errors (best-effort cleanup)' , async ( ) => {
545621 mockedExeca . mockRejectedValue ( new Error ( 'iptables error' ) ) ;
546622
0 commit comments