github.com/tonistiigi/docker@v0.10.1-0.20240229224939-974013b0dc6a/integration-cli/docker_cli_port_test.go (about) 1 package main 2 3 import ( 4 "context" 5 "fmt" 6 "regexp" 7 "sort" 8 "strconv" 9 "strings" 10 "testing" 11 12 "github.com/docker/docker/integration-cli/cli" 13 "github.com/docker/docker/testutil" 14 "gotest.tools/v3/assert" 15 is "gotest.tools/v3/assert/cmp" 16 ) 17 18 type DockerCLIPortSuite struct { 19 ds *DockerSuite 20 } 21 22 func (s *DockerCLIPortSuite) TearDownTest(ctx context.Context, c *testing.T) { 23 s.ds.TearDownTest(ctx, c) 24 } 25 26 func (s *DockerCLIPortSuite) OnTimeout(c *testing.T) { 27 s.ds.OnTimeout(c) 28 } 29 30 func (s *DockerCLIPortSuite) TestPortList(c *testing.T) { 31 testRequires(c, DaemonIsLinux) 32 ctx := testutil.GetContext(c) 33 34 // one port 35 firstID := cli.DockerCmd(c, "run", "-d", "-p", "9876:80", "busybox", "top").Stdout() 36 firstID = strings.TrimSpace(firstID) 37 38 out := cli.DockerCmd(c, "port", firstID, "80").Stdout() 39 40 assertPortList(c, out, []string{"0.0.0.0:9876", "[::]:9876"}) 41 42 out = cli.DockerCmd(c, "port", firstID).Stdout() 43 44 assertPortList(c, out, []string{"80/tcp -> 0.0.0.0:9876", "80/tcp -> [::]:9876"}) 45 46 cli.DockerCmd(c, "rm", "-f", firstID) 47 48 // three port 49 id := cli.DockerCmd(c, "run", "-d", 50 "-p", "9876:80", 51 "-p", "9877:81", 52 "-p", "9878:82", 53 "busybox", "top", 54 ).Stdout() 55 id = strings.TrimSpace(id) 56 57 out = cli.DockerCmd(c, "port", id, "80").Stdout() 58 59 assertPortList(c, out, []string{"0.0.0.0:9876", "[::]:9876"}) 60 61 out = cli.DockerCmd(c, "port", id).Stdout() 62 63 assertPortList(c, out, []string{ 64 "80/tcp -> 0.0.0.0:9876", 65 "80/tcp -> [::]:9876", 66 "81/tcp -> 0.0.0.0:9877", 67 "81/tcp -> [::]:9877", 68 "82/tcp -> 0.0.0.0:9878", 69 "82/tcp -> [::]:9878", 70 }) 71 72 cli.DockerCmd(c, "rm", "-f", id) 73 74 // more and one port mapped to the same container port 75 id = cli.DockerCmd(c, "run", "-d", 76 "-p", "9876:80", 77 "-p", "9999:80", 78 "-p", "9877:81", 79 "-p", "9878:82", 80 "busybox", "top", 81 ).Stdout() 82 id = strings.TrimSpace(id) 83 84 out = cli.DockerCmd(c, "port", id, "80").Stdout() 85 86 assertPortList(c, out, []string{"0.0.0.0:9876", "[::]:9876", "0.0.0.0:9999", "[::]:9999"}) 87 88 out = cli.DockerCmd(c, "port", id).Stdout() 89 90 assertPortList(c, out, []string{ 91 "80/tcp -> 0.0.0.0:9876", 92 "80/tcp -> 0.0.0.0:9999", 93 "80/tcp -> [::]:9876", 94 "80/tcp -> [::]:9999", 95 "81/tcp -> 0.0.0.0:9877", 96 "81/tcp -> [::]:9877", 97 "82/tcp -> 0.0.0.0:9878", 98 "82/tcp -> [::]:9878", 99 }) 100 cli.DockerCmd(c, "rm", "-f", id) 101 102 testRange := func() { 103 // host port ranges used 104 IDs := make([]string, 3) 105 for i := 0; i < 3; i++ { 106 out = cli.DockerCmd(c, "run", "-d", "-p", "9090-9092:80", "busybox", "top").Stdout() 107 IDs[i] = strings.TrimSpace(out) 108 109 out = cli.DockerCmd(c, "port", IDs[i]).Stdout() 110 111 assertPortList(c, out, []string{ 112 fmt.Sprintf("80/tcp -> 0.0.0.0:%d", 9090+i), 113 fmt.Sprintf("80/tcp -> [::]:%d", 9090+i), 114 }) 115 } 116 117 // test port range exhaustion 118 out, _, err := dockerCmdWithError("run", "-d", "-p", "9090-9092:80", "busybox", "top") 119 // Exhausted port range did not return an error 120 assert.Assert(c, err != nil, "out: %s", out) 121 122 for i := 0; i < 3; i++ { 123 cli.DockerCmd(c, "rm", "-f", IDs[i]) 124 } 125 } 126 testRange() 127 // Verify we ran re-use port ranges after they are no longer in use. 128 testRange() 129 130 // test invalid port ranges 131 for _, invalidRange := range []string{"9090-9089:80", "9090-:80", "-9090:80"} { 132 out, _, err := dockerCmdWithError("run", "-d", "-p", invalidRange, "busybox", "top") 133 // Port range should have returned an error 134 assert.Assert(c, err != nil, "out: %s", out) 135 } 136 137 // test host range:container range spec. 138 id = cli.DockerCmd(c, "run", "-d", "-p", "9800-9803:80-83", "busybox", "top").Stdout() 139 id = strings.TrimSpace(id) 140 141 out = cli.DockerCmd(c, "port", id).Stdout() 142 143 assertPortList(c, out, []string{ 144 "80/tcp -> 0.0.0.0:9800", 145 "80/tcp -> [::]:9800", 146 "81/tcp -> 0.0.0.0:9801", 147 "81/tcp -> [::]:9801", 148 "82/tcp -> 0.0.0.0:9802", 149 "82/tcp -> [::]:9802", 150 "83/tcp -> 0.0.0.0:9803", 151 "83/tcp -> [::]:9803", 152 }) 153 cli.DockerCmd(c, "rm", "-f", id) 154 155 // test mixing protocols in same port range 156 id = cli.DockerCmd(c, "run", "-d", "-p", "8000-8080:80", "-p", "8000-8080:80/udp", "busybox", "top").Stdout() 157 id = strings.TrimSpace(id) 158 159 out = cli.DockerCmd(c, "port", id).Stdout() 160 // Running this test multiple times causes the TCP port to increment. 161 err := assertPortRange(ctx, id, []int{8000, 8080}, []int{8000, 8080}) 162 assert.Check(c, err) 163 cli.DockerCmd(c, "rm", "-f", id) 164 } 165 166 func assertPortList(c *testing.T, out string, expected []string) { 167 c.Helper() 168 lines := strings.Split(strings.Trim(out, "\n "), "\n") 169 assert.Assert(c, is.Len(lines, len(expected)), "exepcted: %s", strings.Join(expected, ", ")) 170 171 sort.Strings(lines) 172 sort.Strings(expected) 173 174 // "docker port" does not yet have a "--format" flag, and older versions 175 // of the CLI used an incorrect output format for mappings on IPv6 addresses 176 // for example, "80/tcp -> :::80" instead of "80/tcp -> [::]:80". 177 oldFormat := func(mapping string) string { 178 old := strings.Replace(mapping, "[", "", 1) 179 old = strings.Replace(old, "]:", ":", 1) 180 return old 181 } 182 183 for i := 0; i < len(expected); i++ { 184 if lines[i] == expected[i] { 185 continue 186 } 187 assert.Equal(c, lines[i], oldFormat(expected[i])) 188 } 189 } 190 191 func assertPortRange(ctx context.Context, id string, expectedTCP, expectedUDP []int) error { 192 client := testEnv.APIClient() 193 inspect, err := client.ContainerInspect(ctx, id) 194 if err != nil { 195 return err 196 } 197 198 var validTCP, validUDP bool 199 for portAndProto, binding := range inspect.NetworkSettings.Ports { 200 if portAndProto.Proto() == "tcp" && len(expectedTCP) == 0 { 201 continue 202 } 203 if portAndProto.Proto() == "udp" && len(expectedTCP) == 0 { 204 continue 205 } 206 207 for _, b := range binding { 208 port, err := strconv.Atoi(b.HostPort) 209 if err != nil { 210 return err 211 } 212 213 if len(expectedTCP) > 0 { 214 if port < expectedTCP[0] || port > expectedTCP[1] { 215 return fmt.Errorf("tcp port (%d) not in range expected range %d-%d", port, expectedTCP[0], expectedTCP[1]) 216 } 217 validTCP = true 218 } 219 if len(expectedUDP) > 0 { 220 if port < expectedUDP[0] || port > expectedUDP[1] { 221 return fmt.Errorf("udp port (%d) not in range expected range %d-%d", port, expectedUDP[0], expectedUDP[1]) 222 } 223 validUDP = true 224 } 225 } 226 } 227 if !validTCP { 228 return fmt.Errorf("tcp port not found") 229 } 230 if !validUDP { 231 return fmt.Errorf("udp port not found") 232 } 233 return nil 234 } 235 236 func stopRemoveContainer(id string, c *testing.T) { 237 cli.DockerCmd(c, "rm", "-f", id) 238 } 239 240 func (s *DockerCLIPortSuite) TestUnpublishedPortsInPsOutput(c *testing.T) { 241 testRequires(c, DaemonIsLinux) 242 // Run busybox with command line expose (equivalent to EXPOSE in image's Dockerfile) for the following ports 243 port1 := 80 244 port2 := 443 245 expose1 := fmt.Sprintf("--expose=%d", port1) 246 expose2 := fmt.Sprintf("--expose=%d", port2) 247 cli.DockerCmd(c, "run", "-d", expose1, expose2, "busybox", "sleep", "5") 248 249 // Check docker ps o/p for last created container reports the unpublished ports 250 unpPort1 := fmt.Sprintf("%d/tcp", port1) 251 unpPort2 := fmt.Sprintf("%d/tcp", port2) 252 out := cli.DockerCmd(c, "ps", "-n=1").Stdout() 253 // Missing unpublished ports in docker ps output 254 assert.Assert(c, strings.Contains(out, unpPort1)) 255 // Missing unpublished ports in docker ps output 256 assert.Assert(c, strings.Contains(out, unpPort2)) 257 // Run the container forcing to publish the exposed ports 258 cli.DockerCmd(c, "run", "-d", "-P", expose1, expose2, "busybox", "sleep", "5") 259 260 // Check docker ps o/p for last created container reports the exposed ports in the port bindings 261 expBndRegx1 := regexp.MustCompile(`0.0.0.0:\d\d\d\d\d->` + unpPort1) 262 expBndRegx2 := regexp.MustCompile(`0.0.0.0:\d\d\d\d\d->` + unpPort2) 263 out = cli.DockerCmd(c, "ps", "-n=1").Stdout() 264 // Cannot find expected port binding port (0.0.0.0:xxxxx->unpPort1) in docker ps output 265 assert.Equal(c, expBndRegx1.MatchString(out), true, fmt.Sprintf("out: %s; unpPort1: %s", out, unpPort1)) 266 // Cannot find expected port binding port (0.0.0.0:xxxxx->unpPort2) in docker ps output 267 assert.Equal(c, expBndRegx2.MatchString(out), true, fmt.Sprintf("out: %s; unpPort2: %s", out, unpPort2)) 268 269 // Run the container specifying explicit port bindings for the exposed ports 270 offset := 10000 271 pFlag1 := fmt.Sprintf("%d:%d", offset+port1, port1) 272 pFlag2 := fmt.Sprintf("%d:%d", offset+port2, port2) 273 274 id := cli.DockerCmd(c, "run", "-d", "-p", pFlag1, "-p", pFlag2, expose1, expose2, "busybox", "sleep", "5").Stdout() 275 id = strings.TrimSpace(id) 276 277 // Check docker ps o/p for last created container reports the specified port mappings 278 expBnd1 := fmt.Sprintf("0.0.0.0:%d->%s", offset+port1, unpPort1) 279 expBnd2 := fmt.Sprintf("0.0.0.0:%d->%s", offset+port2, unpPort2) 280 out = cli.DockerCmd(c, "ps", "-n=1").Stdout() 281 // Cannot find expected port binding (expBnd1) in docker ps output 282 assert.Assert(c, strings.Contains(out, expBnd1)) 283 // Cannot find expected port binding (expBnd2) in docker ps output 284 assert.Assert(c, strings.Contains(out, expBnd2)) 285 // Remove container now otherwise it will interfere with next test 286 stopRemoveContainer(id, c) 287 288 // Run the container with explicit port bindings and no exposed ports 289 id = cli.DockerCmd(c, "run", "-d", "-p", pFlag1, "-p", pFlag2, "busybox", "sleep", "5").Stdout() 290 id = strings.TrimSpace(id) 291 292 // Check docker ps o/p for last created container reports the specified port mappings 293 out = cli.DockerCmd(c, "ps", "-n=1").Stdout() 294 // Cannot find expected port binding (expBnd1) in docker ps output 295 assert.Assert(c, strings.Contains(out, expBnd1)) 296 // Cannot find expected port binding (expBnd2) in docker ps output 297 assert.Assert(c, strings.Contains(out, expBnd2)) 298 // Remove container now otherwise it will interfere with next test 299 stopRemoveContainer(id, c) 300 301 // Run the container with one unpublished exposed port and one explicit port binding 302 cli.DockerCmd(c, "run", "-d", expose1, "-p", pFlag2, "busybox", "sleep", "5") 303 304 // Check docker ps o/p for last created container reports the specified unpublished port and port mapping 305 out = cli.DockerCmd(c, "ps", "-n=1").Stdout() 306 // Missing unpublished exposed ports (unpPort1) in docker ps output 307 assert.Assert(c, strings.Contains(out, unpPort1)) 308 // Missing port binding (expBnd2) in docker ps output 309 assert.Assert(c, strings.Contains(out, expBnd2)) 310 } 311 312 func (s *DockerCLIPortSuite) TestPortHostBinding(c *testing.T) { 313 testRequires(c, DaemonIsLinux, NotUserNamespace) 314 firstID := cli.DockerCmd(c, "run", "-d", "-p", "9876:80", "busybox", "nc", "-l", "-p", "80").Stdout() 315 firstID = strings.TrimSpace(firstID) 316 317 out := cli.DockerCmd(c, "port", firstID, "80").Stdout() 318 319 assertPortList(c, out, []string{"0.0.0.0:9876", "[::]:9876"}) 320 321 cli.DockerCmd(c, "run", "--net=host", "busybox", "nc", "localhost", "9876") 322 323 cli.DockerCmd(c, "rm", "-f", firstID) 324 325 out, _, err := dockerCmdWithError("run", "--net=host", "busybox", "nc", "localhost", "9876") 326 // Port is still bound after the Container is removed 327 assert.Assert(c, err != nil, out) 328 } 329 330 func (s *DockerCLIPortSuite) TestPortExposeHostBinding(c *testing.T) { 331 testRequires(c, DaemonIsLinux, NotUserNamespace) 332 firstID := cli.DockerCmd(c, "run", "-d", "-P", "--expose", "80", "busybox", "nc", "-l", "-p", "80").Stdout() 333 firstID = strings.TrimSpace(firstID) 334 335 exposedPort := cli.DockerCmd(c, "inspect", "--format", `{{index .NetworkSettings.Ports "80/tcp" 0 "HostPort" }}`, firstID).Stdout() 336 exposedPort = strings.TrimSpace(exposedPort) 337 cli.DockerCmd(c, "run", "--net=host", "busybox", "nc", "127.0.0.1", exposedPort) 338 339 cli.DockerCmd(c, "rm", "-f", firstID) 340 341 out, _, err := dockerCmdWithError("run", "--net=host", "busybox", "nc", "127.0.0.1", exposedPort) 342 // Port is still bound after the Container is removed 343 assert.Assert(c, err != nil, "out: %s", out) 344 } 345 346 func (s *DockerCLIPortSuite) TestPortBindingOnSandbox(c *testing.T) { 347 testRequires(c, DaemonIsLinux, NotUserNamespace) 348 cli.DockerCmd(c, "network", "create", "--internal", "-d", "bridge", "internal-net") 349 nr := getNetworkResource(c, "internal-net") 350 assert.Equal(c, nr.Internal, true) 351 352 cli.DockerCmd(c, "run", "--net", "internal-net", "-d", "--name", "c1", "-p", "8080:8080", "busybox", "nc", "-l", "-p", "8080") 353 cli.WaitRun(c, "c1") 354 355 _, _, err := dockerCmdWithError("run", "--net=host", "busybox", "nc", "localhost", "8080") 356 assert.Assert(c, err != nil, "Port mapping on internal network is expected to fail") 357 // Connect container to another normal bridge network 358 cli.DockerCmd(c, "network", "create", "-d", "bridge", "foo-net") 359 cli.DockerCmd(c, "network", "connect", "foo-net", "c1") 360 361 _, _, err = dockerCmdWithError("run", "--net=host", "busybox", "nc", "localhost", "8080") 362 assert.Assert(c, err == nil, "Port mapping on the new network is expected to succeed") 363 }