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