Skip to content

Commit

Permalink
Enable IPv6 port binding
Browse files Browse the repository at this point in the history
Two areas needed tweaking to accomplish this: port parsing and
binding ports on the host.

Parsing is an obvious problem - we have to accomodate an IPv6
address enclosed by [] as well as a normal IPv4 address. It was
slightly complicated by the fact that we previously just counted
the number of colons in the whole port definition (a thousand
curses on whoever in the IPv6 standard body decided to reuse
colons for address separators), but did not end up being that
bad.

Libpod also (optionally) binds ports on the host to prevent their
reuse by host processes. This code was IPv4 only for TCP, and
bound to both for UDP (which I'm fairly certain is not correct,
and has been adjusted). This just needed protocols adjusted to
read "tcp4"/"tcp6" and "udp4"/"udp6" based on what we wanted to
bind to.

Fixes #5715

Signed-off-by: Matthew Heon <[email protected]>
  • Loading branch information
mheon committed Jun 10, 2020
1 parent 6c5bd15 commit 4e2a0b5
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 5 deletions.
32 changes: 31 additions & 1 deletion cmd/podman/common/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,44 @@ func createPortBindings(ports []string) ([]specgen.PortMapping, error) {
return nil, errors.Errorf("invalid port format - protocol can only be specified once")
}

splitPort := strings.Split(splitProto[0], ":")
remainder := splitProto[0]
haveV6 := false

// Check for an IPv6 address in brackets
splitV6 := strings.Split(remainder, "]")
switch len(splitV6) {
case 1:
// Do nothing, proceed as before
case 2:
// We potentially have an IPv6 address
haveV6 = true
if !strings.HasPrefix(splitV6[0], "[") {
return nil, errors.Errorf("invalid port format - IPv6 addresses must be enclosed by []")
}
if !strings.HasPrefix(splitV6[1], ":") {
return nil, errors.Errorf("invalid port format - IPv6 address must be followed by a colon (':')")
}
ipNoPrefix := strings.TrimPrefix(splitV6[0], "[")
hostIP = &ipNoPrefix
remainder = strings.TrimPrefix(splitV6[1], ":")
default:
return nil, errors.Errorf("invalid port format - at most one IPv6 address can be specified in a --publish")
}

splitPort := strings.Split(remainder, ":")
switch len(splitPort) {
case 1:
if haveV6 {
return nil, errors.Errorf("invalid port format - must provide host and destination port if specifying an IP")
}
ctrPort = splitPort[0]
case 2:
hostPort = &(splitPort[0])
ctrPort = splitPort[1]
case 3:
if haveV6 {
return nil, errors.Errorf("invalid port format - when v6 address specified, must be [ipv6]:hostPort:ctrPort")
}
hostIP = &(splitPort[0])
hostPort = &(splitPort[1])
ctrPort = splitPort[2]
Expand Down
36 changes: 32 additions & 4 deletions libpod/oci_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,30 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) {
var files []*os.File
notifySCTP := false
for _, i := range ports {
isV6 := net.ParseIP(i.HostIP).To4() == nil
if i.HostIP == "" {
isV6 = false
}
switch i.Protocol {
case "udp":
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
var (
addr *net.UDPAddr
err error
)
if isV6 {
addr, err = net.ResolveUDPAddr("udp6", fmt.Sprintf("[%s]:%d", i.HostIP, i.HostPort))
} else {
addr, err = net.ResolveUDPAddr("udp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
}
if err != nil {
return nil, errors.Wrapf(err, "cannot resolve the UDP address")
}

server, err := net.ListenUDP("udp", addr)
proto := "udp4"
if isV6 {
proto = "udp6"
}
server, err := net.ListenUDP(proto, addr)
if err != nil {
return nil, errors.Wrapf(err, "cannot listen on the UDP port")
}
Expand All @@ -54,12 +70,24 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) {
files = append(files, f)

case "tcp":
addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
var (
addr *net.TCPAddr
err error
)
if isV6 {
addr, err = net.ResolveTCPAddr("tcp6", fmt.Sprintf("[%s]:%d", i.HostIP, i.HostPort))
} else {
addr, err = net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort))
}
if err != nil {
return nil, errors.Wrapf(err, "cannot resolve the TCP address")
}

server, err := net.ListenTCP("tcp4", addr)
proto := "tcp4"
if isV6 {
proto = "tcp6"
}
server, err := net.ListenTCP(proto, addr)
if err != nil {
return nil, errors.Wrapf(err, "cannot listen on the TCP port")
}
Expand Down
26 changes: 26 additions & 0 deletions test/e2e/run_networking_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,32 @@ var _ = Describe("Podman run networking", func() {
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("127.0.0.1"))
})

It("podman run -p [::1]:8080:80/udp", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "[::1]:8080:80/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(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("udp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("::1"))
})

It("podman run -p [::1]:8080:80/tcp", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "-p", "[::1]:8080:80/tcp", "--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(inspectOut[0].NetworkSettings.Ports[0].HostPort).To(Equal(int32(8080)))
Expect(inspectOut[0].NetworkSettings.Ports[0].ContainerPort).To(Equal(int32(80)))
Expect(inspectOut[0].NetworkSettings.Ports[0].Protocol).To(Equal("tcp"))
Expect(inspectOut[0].NetworkSettings.Ports[0].HostIP).To(Equal("::1"))
})

It("podman run --expose 80 -P", func() {
name := "testctr"
session := podmanTest.Podman([]string{"create", "-t", "--expose", "80", "-P", "--name", name, ALPINE, "/bin/sh"})
Expand Down

0 comments on commit 4e2a0b5

Please sign in to comment.