github.com/rumpl/bof@v23.0.0-rc.2+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  }