From 4e2a0b5b9c534a3bdf64ff22ecbca4a43f65e65c Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 8 Jun 2020 16:23:50 -0400 Subject: Enable IPv6 port binding 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 --- cmd/podman/common/util.go | 32 +++++++++++++++++++++++++++++++- libpod/oci_util.go | 36 ++++++++++++++++++++++++++++++++---- test/e2e/run_networking_test.go | 26 ++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index a3626b4e4..0d9f3ba26 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -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] diff --git a/libpod/oci_util.go b/libpod/oci_util.go index 53567d2d0..8b40dad81 100644 --- a/libpod/oci_util.go +++ b/libpod/oci_util.go @@ -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") } @@ -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") } diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 9db2f5d49..4fad85f00 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -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"}) -- cgit v1.2.3-54-g00ecf