From 99d3447f7ed346b61d6460158664714be3fad494 Mon Sep 17 00:00:00 2001 From: fahed dorgaa Date: Sun, 21 Mar 2021 02:21:27 +0100 Subject: [PATCH] setup ipv6 for nerdctl Signed-off-by: fahed dorgaa --- .github/workflows/test.yml | 5 ++ go.mod | 4 +- go.sum | 7 +-- pkg/portutil/portutil.go | 95 ++++++++++++++++++++++++-------------- port_test.go | 40 ++++++++++++++++ run.go | 6 +-- 6 files changed, 115 insertions(+), 42 deletions(-) create mode 100644 port_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da6f797a5ee..7d9ae6cc754 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -98,6 +98,11 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 1 + - name: "Enable Ipv6 on Docker" + run: | + sudo apt-get --no-install-recommends install moreutils + jq '. |= . + { "ipv6": true, "fixed-cidr-v6": "fd00::/64" }' /etc/docker/daemon.json | sudo sponge /etc/docker/daemon.json + sudo systemctl restart docker.service - name: "Ensure that the test suite is compatible with Docker" run: go test -v -exec sudo -test.target=docker -test.kill-daemon . diff --git a/go.mod b/go.mod index 37b4f02e821..519178e7e5c 100644 --- a/go.mod +++ b/go.mod @@ -28,8 +28,8 @@ require ( github.com/rootless-containers/rootlesskit v0.14.1 github.com/sirupsen/logrus v1.8.1 github.com/urfave/cli/v2 v2.3.0 - golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a - golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c + golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 gotest.tools/v3 v3.0.3 ) diff --git a/go.sum b/go.sum index c8d16e7f32f..08e0a386a26 100644 --- a/go.sum +++ b/go.sum @@ -640,8 +640,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9 h1:phUcVbl53swtrUN8kQEXFhUxPlIlWyBfKmidCu7P95o= -golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -789,8 +789,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 h1:V066+OYJ66oTjnhm4Yrn7SXIwSCiDQJxpBxmvqb1N1c= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/pkg/portutil/portutil.go b/pkg/portutil/portutil.go index b453c8ad8ef..913c0ceee7f 100644 --- a/pkg/portutil/portutil.go +++ b/pkg/portutil/portutil.go @@ -25,7 +25,24 @@ import ( "github.com/pkg/errors" ) -func ParseFlagP(s string) (*gocni.PortMapping, error) { +func splitParts(rawport string) (string, string, string) { + parts := strings.Split(rawport, ":") + n := len(parts) + containerport := parts[n-1] + + switch n { + case 1: + return "", "", containerport + case 2: + return "", parts[0], containerport + case 3: + return parts[0], parts[1], containerport + default: + return strings.Join(parts[:n-2], ":"), parts[n-2], containerport + } +} + +func ParseFlagP(s string) ([]gocni.PortMapping, error) { proto := "tcp" splitBySlash := strings.Split(s, "/") switch len(splitBySlash) { @@ -42,44 +59,54 @@ func ParseFlagP(s string) (*gocni.PortMapping, error) { return nil, errors.Errorf("failed to parse %q, unexpected slashes", s) } - res := &gocni.PortMapping{ + res := gocni.PortMapping{ Protocol: proto, - HostIP: "0.0.0.0", } - splitByColon := strings.Split(splitBySlash[0], ":") - switch len(splitByColon) { - case 1: + multi_res := []gocni.PortMapping{} + + ip, hostPort, containerPort := splitParts(splitBySlash[0]) + + if containerPort == "" { + return nil, errors.Errorf("No port specified: %s", splitBySlash[0]) + } + + if hostPort == "" { return nil, errors.Errorf("automatic host port assignment is not supported yet (FIXME)") - case 2: - i, err := strconv.Atoi(splitByColon[0]) - if err != nil { - return nil, err - } - res.HostPort = int32(i) - i, err = strconv.Atoi(splitByColon[1]) - if err != nil { - return nil, err - } - res.ContainerPort = int32(i) - return res, nil - case 3: - res.HostIP = splitByColon[0] - if net.ParseIP(res.HostIP) == nil { - return nil, errors.Errorf("invalid IP %q", res.HostIP) - } - i, err := strconv.Atoi(splitByColon[1]) - if err != nil { - return nil, err + } + + i, err := strconv.Atoi(hostPort) + if err != nil { + return nil, err + } + res.HostPort = int32(i) + + i, err = strconv.Atoi(containerPort) + if err != nil { + return nil, err + } + res.ContainerPort = int32(i) + + if ip == "" { + res.HostIP = "0.0.0.0" + multi_res = append(multi_res, res) + res.HostIP = "::" + multi_res = append(multi_res, res) + } else { + if ip[0] == '[' { + // Strip [] from IPV6 addresses + rawIP, _, err := net.SplitHostPort(ip + ":") + if err != nil { + return nil, errors.Errorf("Invalid ip address %v: %s", ip, err) + } + ip = rawIP } - res.HostPort = int32(i) - i, err = strconv.Atoi(splitByColon[2]) - if err != nil { - return nil, err + + if net.ParseIP(ip) == nil { + return nil, errors.Errorf("Invalid ip address: %s", ip) } - res.ContainerPort = int32(i) - return res, nil - default: - return nil, errors.Errorf("failed to parse %q, unexpected colons", s) + res.HostIP = ip + multi_res = append(multi_res, res) } + return multi_res, nil } diff --git a/port_test.go b/port_test.go new file mode 100644 index 00000000000..cc74ec6b653 --- /dev/null +++ b/port_test.go @@ -0,0 +1,40 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package main + +import ( + "testing" + + "github.com/containerd/nerdctl/pkg/testutil" +) + +func TestRunPortMappingWithEmptyIP(t *testing.T) { + base := testutil.NewBase(t) + defer base.Cmd("rm", "-f", "testPortMappingWithEmptyIP").Run() + const expected = `80/tcp -> 0.0.0.0:80 +80/tcp -> :::80` + base.Cmd("run", "-d", "--name", "testPortMappingWithEmptyIP", "-p", "80:80", testutil.NginxAlpineImage).Run() + base.Cmd("port", "testPortMappingWithEmptyIP").AssertOut(expected) +} + +func TestRunPortMappingWithIPv6(t *testing.T) { + base := testutil.NewBase(t) + defer base.Cmd("rm", "-f", "testPortMappingWithIPv6").Run() + base.Cmd("run", "-d", "--name", "testPortMappingWithIPv6", "-p", "[::]:80:80", testutil.NginxAlpineImage).Run() + const expected = `80/tcp -> :::80` + base.Cmd("port", "testPortMappingWithIPv6").AssertOut(expected) +} diff --git a/run.go b/run.go index c2dd1ca95fb..e8e879f8b18 100644 --- a/run.go +++ b/run.go @@ -328,7 +328,7 @@ func runAction(clicontext *cli.Context) error { portSlice := strutil.DedupeStrSlice(clicontext.StringSlice("p")) netSlice := strutil.DedupeStrSlice(clicontext.StringSlice("net")) - ports := make([]gocni.PortMapping, len(portSlice)) + ports := make([]gocni.PortMapping, 0) if len(netSlice) != 1 { return errors.New("currently, number of networks must be 1") } @@ -369,12 +369,12 @@ func runAction(clicontext *cli.Context) error { return err } opts = append(opts, withCustomResolvConf(resolvConfPath), withCustomHosts(etcHostsPath)) - for i, p := range portSlice { + for _, p := range portSlice { pm, err := portutil.ParseFlagP(p) if err != nil { return err } - ports[i] = *pm + ports = append(ports, pm...) } }