@@ -798,6 +798,140 @@ func TestDirectRoutingOpenPorts(t *testing.T) {
798798 }
799799}
800800
801+ // TestRoutedNonGateway checks whether a published container port on an endpoint in a
802+ // gateway mode "routed" network is accessible when the routed network is not providing
803+ // the container's default gateway.
804+ func TestRoutedNonGateway (t * testing.T ) {
805+ skip .If (t , testEnv .IsRootless ())
806+ skip .If (t , networking .FirewalldRunning (), "Firewalld's IPv6_rpfilter=yes breaks IPv6 direct routing from L3Segment" )
807+
808+ ctx := setupTest (t )
809+ d := daemon .New (t )
810+ d .StartWithBusybox (ctx , t )
811+ defer d .Stop (t )
812+ c := d .NewClientT (t )
813+ defer c .Close ()
814+
815+ // Simulate the remote host.
816+ l3 := networking .NewL3Segment (t , "test-routed-open-ports" ,
817+ netip .MustParsePrefix ("192.168.124.1/24" ),
818+ netip .MustParsePrefix ("fdc0:36dc:a4dd::1/64" ))
819+ defer l3 .Destroy (t )
820+ // "docker" is the host where dockerd is running.
821+ const dockerHostIPv4 = "192.168.124.2"
822+ const dockerHostIPv6 = "fdc0:36dc:a4dd::2"
823+ l3 .AddHost (t , "docker" , networking .CurrentNetns , "eth-test" ,
824+ netip .MustParsePrefix (dockerHostIPv4 + "/24" ),
825+ netip .MustParsePrefix (dockerHostIPv6 + "/64" ))
826+ // "remote" simulates the remote host.
827+ l3 .AddHost (t , "remote" , "test-remote-host" , "eth0" ,
828+ netip .MustParsePrefix ("192.168.124.3/24" ),
829+ netip .MustParsePrefix ("fdc0:36dc:a4dd::3/64" ))
830+ // Add default routes from the "remote" Host to the "docker" Host.
831+ l3 .Hosts ["remote" ].MustRun (t , "ip" , "route" , "add" , "default" , "via" , "192.168.124.2" )
832+ l3 .Hosts ["remote" ].MustRun (t , "ip" , "-6" , "route" , "add" , "default" , "via" , "fdc0:36dc:a4dd::2" )
833+
834+ // Create a dual-stack NAT'd network.
835+ const natNetName = "ds_nat"
836+ network .CreateNoError (ctx , t , c , natNetName ,
837+ network .WithIPv6 (),
838+ network .WithOption (bridge .BridgeName , natNetName ),
839+ )
840+ defer network .RemoveNoError (ctx , t , c , natNetName )
841+
842+ // Create a dual-stack routed network.
843+ const routedNetName = "ds_routed"
844+ network .CreateNoError (ctx , t , c , routedNetName ,
845+ network .WithIPv6 (),
846+ network .WithOption (bridge .BridgeName , routedNetName ),
847+ network .WithOption (bridge .IPv4GatewayMode , "routed" ),
848+ network .WithOption (bridge .IPv6GatewayMode , "routed" ),
849+ )
850+ defer network .RemoveNoError (ctx , t , c , routedNetName )
851+
852+ // Run a web server attached to both networks, and make sure the nat
853+ // network is selected as the gateway.
854+ ctrId := container .Run (ctx , t , c ,
855+ container .WithCmd ("httpd" , "-f" ),
856+ container .WithExposedPorts ("80/tcp" ),
857+ container .WithPortMap (nat.PortMap {"80/tcp" : {{HostPort : "8080" }}}),
858+ container .WithNetworkMode (natNetName ),
859+ container .WithNetworkMode (routedNetName ),
860+ container .WithEndpointSettings (natNetName , & networktypes.EndpointSettings {GwPriority : 1 }),
861+ container .WithEndpointSettings (routedNetName , & networktypes.EndpointSettings {GwPriority : 0 }))
862+ defer container .Remove (ctx , t , c , ctrId , containertypes.RemoveOptions {Force : true })
863+
864+ testHttp := func (t * testing.T , addr , port , expOut string ) {
865+ t .Helper ()
866+ l3 .Hosts ["remote" ].Do (t , func () {
867+ t .Helper ()
868+ t .Parallel ()
869+ u := "http://" + net .JoinHostPort (addr , port )
870+ res := icmd .RunCommand ("curl" , "--max-time" , "3" , "--show-error" , "--silent" , u )
871+ assert .Check (t , is .Contains (res .Combined (), expOut ), "url:%s" , u )
872+ })
873+ }
874+
875+ const (
876+ httpSuccess = "404 Not Found"
877+ httpFail = "Connection timed out"
878+ )
879+
880+ insp := container .Inspect (ctx , t , c , ctrId )
881+ testcases := []struct {
882+ name string
883+ addr string
884+ port string
885+ expHttp string
886+ }{
887+ {
888+ name : "nat/published/v4" ,
889+ addr : dockerHostIPv4 ,
890+ port : "8080" ,
891+ expHttp : httpSuccess ,
892+ },
893+ {
894+ name : "nat/published/v6" ,
895+ addr : dockerHostIPv6 ,
896+ port : "8080" ,
897+ expHttp : httpSuccess ,
898+ },
899+ {
900+ name : "nat/direct/v4" ,
901+ addr : insp .NetworkSettings .Networks [natNetName ].IPAddress ,
902+ port : "80" ,
903+ expHttp : httpFail ,
904+ },
905+ {
906+ name : "nat/direct/v6" ,
907+ addr : insp .NetworkSettings .Networks [natNetName ].GlobalIPv6Address ,
908+ port : "80" ,
909+ expHttp : httpFail ,
910+ },
911+ {
912+ name : "routed/direct/v4" ,
913+ addr : insp .NetworkSettings .Networks [routedNetName ].IPAddress ,
914+ port : "80" ,
915+ expHttp : httpFail ,
916+ },
917+ {
918+ name : "routed/direct/v6" ,
919+ addr : insp .NetworkSettings .Networks [routedNetName ].GlobalIPv6Address ,
920+ port : "80" ,
921+ expHttp : httpFail ,
922+ },
923+ }
924+
925+ // Wrap parallel tests, otherwise defer statements run before tests finish.
926+ t .Run ("w" , func (t * testing.T ) {
927+ for _ , tc := range testcases {
928+ t .Run (tc .name , func (t * testing.T ) {
929+ testHttp (t , tc .addr , tc .port , tc .expHttp )
930+ })
931+ }
932+ })
933+ }
934+
801935// TestAccessPublishedPortFromAnotherNetwork checks that a container can access
802936// ports published on the host by a container attached to a different network
803937// using both its gateway IP address, and the host IP address.
0 commit comments