summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/util.go26
-rw-r--r--docs/source/markdown/podman-create.1.md2
-rw-r--r--docs/source/markdown/podman-run.1.md3
-rw-r--r--pkg/specgen/generate/ports.go112
-rw-r--r--pkg/specgen/specgen.go3
-rw-r--r--test/e2e/run_networking_test.go24
6 files changed, 136 insertions, 34 deletions
diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go
index ce323a4ba..6c8c22147 100644
--- a/cmd/podman/common/util.go
+++ b/cmd/podman/common/util.go
@@ -184,22 +184,24 @@ func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string)
}
if hostPort != nil {
if *hostPort == "" {
- return newPort, errors.Errorf("must provide a non-empty container host port to publish")
- }
- hostStart, hostLen, err := parseAndValidateRange(*hostPort)
- if err != nil {
- return newPort, errors.Wrapf(err, "error parsing host port")
- }
- if hostLen != ctrLen {
- return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen)
+ // Set 0 as a placeholder. The server side of Specgen
+ // will find a random, open, unused port to use.
+ newPort.HostPort = 0
+ } else {
+ hostStart, hostLen, err := parseAndValidateRange(*hostPort)
+ if err != nil {
+ return newPort, errors.Wrapf(err, "error parsing host port")
+ }
+ if hostLen != ctrLen {
+ return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen)
+ }
+ newPort.HostPort = hostStart
}
- newPort.HostPort = hostStart
+ } else {
+ newPort.HostPort = newPort.ContainerPort
}
hport := newPort.HostPort
- if hport == 0 {
- hport = newPort.ContainerPort
- }
logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol)
return newPort, nil
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index ded668f34..30ac54de2 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -626,6 +626,8 @@ When specifying ranges for both, the number of container ports in the range must
(e.g., `podman run -p 1234-1236:1222-1224 --name thisWorks -t busybox`
but not `podman run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`)
With ip: `podman run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage`
+Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`).
+If it is not, the container port will be randomly assigned a port on the host.
Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT`
**--publish-all**, **-P**=*true|false*
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 83971107f..2e2adbc7e 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -638,6 +638,9 @@ Both hostPort and containerPort can be specified as a range of ports.
When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range.
+Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`).
+If it is not, the container port will be randomly assigned a port on the host.
+
Use **podman port** to see the actual mapping: **podman port $CONTAINER $CONTAINERPORT**.
**--publish-all**, **-P**=**true**|**false**
diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go
index 91c8e68d1..5c06d3bc3 100644
--- a/pkg/specgen/generate/ports.go
+++ b/pkg/specgen/generate/ports.go
@@ -43,6 +43,8 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
containerPortValidate[proto] = make(map[string]map[uint16]uint16)
}
+ postAssignHostPort := false
+
// Iterate through all port mappings, generating OCICNI PortMapping
// structs and validating there is no overlap.
for _, port := range portMappings {
@@ -71,9 +73,6 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
return nil, nil, nil, errors.Errorf("container port number must be non-0")
}
hostPort := port.HostPort
- if hostPort == 0 {
- hostPort = containerPort
- }
if uint32(len-1)+uint32(containerPort) > 65535 {
return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number")
}
@@ -105,26 +104,42 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
cPort := containerPort + index
hPort := hostPort + index
- if cPort == 0 || hPort == 0 {
- return nil, nil, nil, errors.Errorf("host and container ports cannot be 0")
- }
-
- testCPort := ctrPortMap[cPort]
- if testCPort != 0 && testCPort != hPort {
- // This is an attempt to redefine a port
- return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p)
+ if cPort == 0 {
+ return nil, nil, nil, errors.Errorf("container port cannot be 0")
}
- ctrPortMap[cPort] = hPort
- testHPort := hostPortMap[hPort]
- if testHPort != 0 && testHPort != cPort {
- return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p)
- }
- hostPortMap[hPort] = cPort
-
- // If we have an exact duplicate, just continue
- if testCPort == hPort && testHPort == cPort {
- continue
+ // Host port is allowed to be 0. If it is, we
+ // select a random port on the host.
+ // This will happen *after* all other ports are
+ // placed, to ensure we don't accidentally
+ // select a port that a later mapping wanted.
+ if hPort == 0 {
+ // If we already have a host port
+ // assigned to their container port -
+ // just use that.
+ if ctrPortMap[cPort] != 0 {
+ hPort = ctrPortMap[cPort]
+ } else {
+ postAssignHostPort = true
+ }
+ } else {
+ testCPort := ctrPortMap[cPort]
+ if testCPort != 0 && testCPort != hPort {
+ // This is an attempt to redefine a port
+ return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p)
+ }
+ ctrPortMap[cPort] = hPort
+
+ testHPort := hostPortMap[hPort]
+ if testHPort != 0 && testHPort != cPort {
+ return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p)
+ }
+ hostPortMap[hPort] = cPort
+
+ // If we have an exact duplicate, just continue
+ if testCPort == hPort && testHPort == cPort {
+ continue
+ }
}
// We appear to be clear. Make an OCICNI port
@@ -142,6 +157,61 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping,
}
}
+ // Handle any 0 host ports now by setting random container ports.
+ if postAssignHostPort {
+ remadeMappings := make([]ocicni.PortMapping, 0, len(finalMappings))
+
+ // Iterate over all
+ for _, p := range finalMappings {
+ if p.HostPort != 0 {
+ remadeMappings = append(remadeMappings, p)
+ continue
+ }
+
+ hostIPMap := hostPortValidate[p.Protocol]
+ ctrIPMap := containerPortValidate[p.Protocol]
+
+ hostPortMap, ok := hostIPMap[p.HostIP]
+ if !ok {
+ hostPortMap = make(map[uint16]uint16)
+ hostIPMap[p.HostIP] = hostPortMap
+ }
+ ctrPortMap, ok := ctrIPMap[p.HostIP]
+ if !ok {
+ ctrPortMap = make(map[uint16]uint16)
+ ctrIPMap[p.HostIP] = ctrPortMap
+ }
+
+ // See if container port has been used elsewhere
+ if ctrPortMap[uint16(p.ContainerPort)] != 0 {
+ // Duplicate definition. Let's not bother
+ // including it.
+ continue
+ }
+
+ // Max retries to ensure we don't loop forever.
+ for i := 0; i < 15; i++ {
+ candidate, err := getRandomPort()
+ if err != nil {
+ return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort)
+ }
+
+ if hostPortMap[uint16(candidate)] == 0 {
+ logrus.Debugf("Successfully assigned container port %d to host port %d (IP %s Protocol %s)", p.ContainerPort, candidate, p.HostIP, p.Protocol)
+ hostPortMap[uint16(candidate)] = uint16(p.ContainerPort)
+ ctrPortMap[uint16(p.ContainerPort)] = uint16(candidate)
+ p.HostPort = int32(candidate)
+ break
+ }
+ }
+ if p.HostPort == 0 {
+ return nil, nil, nil, errors.Errorf("could not find open host port to map container port %d to", p.ContainerPort)
+ }
+ remadeMappings = append(remadeMappings, p)
+ }
+ return remadeMappings, containerPortValidate, hostPortValidate, nil
+ }
+
return finalMappings, containerPortValidate, hostPortValidate, nil
}
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 3d5bf03e5..361f09379 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -435,7 +435,8 @@ type PortMapping struct {
ContainerPort uint16 `json:"container_port"`
// HostPort is the port number that will be forwarded from the host into
// the container.
- // If omitted, will be assumed to be identical to
+ // If omitted, a random port on the host (guaranteed to be over 1024)
+ // will be assigned.
HostPort uint16 `json:"host_port,omitempty"`
// Range is the number of ports that will be forwarded, starting at
// HostPort and ContainerPort and counting up.
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index afba12ccd..cdfbd5530 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -184,6 +184,30 @@ var _ = Describe("Podman run networking", func() {
Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal(""))
})
+ It("podman run -p 127.0.0.1::8080/udp", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1::8080/udp", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["8080/udp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostPort).To(Not(Equal("8080")))
+ Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostIP).To(Equal("127.0.0.1"))
+ })
+
+ It("podman run -p :8080", func() {
+ name := "testctr"
+ session := podmanTest.Podman([]string{"create", "-t", "-p", ":8080", "--name", name, ALPINE, "/bin/sh"})
+ session.WaitWithDefaultTimeout()
+ inspectOut := podmanTest.InspectContainer(name)
+ Expect(len(inspectOut)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1))
+ Expect(len(inspectOut[0].NetworkSettings.Ports["8080/tcp"])).To(Equal(1))
+ Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostPort).To(Not(Equal("8080")))
+ Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostIP).To(Equal(""))
+ })
+
It("podman run network expose host port 80 to container port 8000", func() {
SkipIfRootless()
session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"})