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