From e6a5a56aa6b0ef25eb6ae47de6b5415f84259991 Mon Sep 17 00:00:00 2001 From: 5eraph <5eraph@protonmail.com> Date: Tue, 4 Aug 2020 15:18:02 +0200 Subject: [PATCH] changes to support outbound-addr Fixes #6064 Signed-off-by: Bohumil Cervenka <5eraph@protonmail.com> --- docs/source/markdown/podman-create.1.md | 11 ++- libpod/networking_linux.go | 91 ++++++++++++++++++++++--- test/e2e/run_networking_test.go | 50 ++++++++++++++ test/utils/utils.go | 10 +++ 4 files changed, 149 insertions(+), 13 deletions(-) diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index b4456225e6a7..d57af0a067a6 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -560,9 +560,14 @@ Valid values are: - `ns:`: path to a network namespace to join - `private`: create a new namespace for the container (default) - `slirp4netns[:OPTIONS,...]`: use slirp4netns to create a user network stack. This is the default for rootless containers. It is possible to specify these additional options: - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default to false. + - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default is false. + - **enable_ipv6=true|false**: Enable IPv6. Default is false. (Required for `outbound_addr6`). + - **outbound_addr=INTERFACE**: Specify the outbound interface slirp should bind to (ipv4 traffic only). + - **outbound_addr=IPv4**: Specify the outbound ipv4 address slirp should bind to. + - **outbound_addr6=INTERFACE**: Specify the outbound interface slirp should bind to (ipv6 traffic only). + - **outbound_addr6=IPv6**: Specify the outbound ipv6 address slirp should bind to. + - **port_handler=rootlesskit**: Use rootlesskit for port forwarding. Default. + - **port_handler=slirp4netns**: Use the slirp4netns port forwarding. **--network-alias**=*alias* diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 844748970325..ed8f82c46ad6 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -171,6 +171,8 @@ type slirpFeatures struct { HasMTU bool HasEnableSandbox bool HasEnableSeccomp bool + HasOutboundAddr bool + HasIPv6 bool } type slirp4netnsCmdArg struct { @@ -197,6 +199,8 @@ func checkSlirpFlags(path string) (*slirpFeatures, error) { HasMTU: strings.Contains(string(out), "--mtu"), HasEnableSandbox: strings.Contains(string(out), "--enable-sandbox"), HasEnableSeccomp: strings.Contains(string(out), "--enable-seccomp"), + HasOutboundAddr: strings.Contains(string(out), "--outbound-addr"), + HasIPv6: strings.Contains(string(out), "--enable-ipv6"), }, nil } @@ -225,21 +229,64 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { isSlirpHostForward := false disableHostLoopback := true + enableIPv6 := false + outboundAddr := "" + outboundAddr6 := "" + if ctr.config.NetworkOptions != nil { slirpOptions := ctr.config.NetworkOptions["slirp4netns"] for _, o := range slirpOptions { - switch o { - case "port_handler=slirp4netns": - isSlirpHostForward = true - case "port_handler=rootlesskit": - isSlirpHostForward = false - case "allow_host_loopback=true": - disableHostLoopback = false - case "allow_host_loopback=false": - disableHostLoopback = true + parts := strings.Split(o, "=") + option, value := parts[0], parts[1] + + switch option { + case "port_handler": + switch value { + case "slirp4netns": + isSlirpHostForward = true + case "rootlesskit": + isSlirpHostForward = false + default: + return errors.Errorf("unknown port_handler for slirp4netns: %q", value) + } + case "allow_host_loopback": + switch value { + case "true": + disableHostLoopback = false + case "false": + disableHostLoopback = true + default: + return errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value) + } + case "enable_ipv6": + switch value { + case "true": + enableIPv6 = true + case "false": + enableIPv6 = false + default: + return errors.Errorf("invalid value of enable_ipv6 for slirp4netns: %q", value) + } + case "outbound_addr": + ipv4 := net.ParseIP(value) + if ipv4 == nil || ipv4.To4() == nil { + _, err := net.InterfaceByName(value) + if err != nil { + return errors.Errorf("invalid outbound_addr %q", value) + } + } + outboundAddr = value + case "outbound_addr6": + ipv6 := net.ParseIP(value) + if ipv6 == nil || ipv6.To4() != nil { + _, err := net.InterfaceByName(value) + if err != nil { + return errors.Errorf("invalid outbound_addr6: %q", value) + } + } + outboundAddr6 = value default: return errors.Errorf("unknown option for slirp4netns: %q", o) - } } } @@ -262,6 +309,30 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { cmdArgs = append(cmdArgs, "--enable-seccomp") } + if enableIPv6 { + if !slirpFeatures.HasIPv6 { + return errors.Errorf("enable_ipv6 not supported") + } + cmdArgs = append(cmdArgs, "--enable-ipv6") + } + + if outboundAddr != "" { + if !slirpFeatures.HasOutboundAddr { + return errors.Errorf("outbound_addr not supported") + } + cmdArgs = append(cmdArgs, fmt.Sprintf("--outbound-addr=%s", outboundAddr)) + } + + if outboundAddr6 != "" { + if !slirpFeatures.HasOutboundAddr || !slirpFeatures.HasIPv6 { + return errors.Errorf("outbound_addr6 not supported") + } + if !enableIPv6 { + return errors.Errorf("enable_ipv6=true is required for outbound_addr6") + } + cmdArgs = append(cmdArgs, fmt.Sprintf("--outbound-addr6=%s", outboundAddr6)) + } + var apiSocket string if havePortMapping && isSlirpHostForward { apiSocket = filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID)) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 87b74052af7f..d37b3510e6ad 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -1,11 +1,14 @@ package integration import ( + "fmt" "os" + "strings" . "github.com/containers/podman/v2/test/utils" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/uber/jaeger-client-go/utils" ) var _ = Describe("Podman run networking", func() { @@ -278,6 +281,53 @@ var _ = Describe("Podman run networking", func() { Expect(session.ExitCode()).To(Equal(0)) }) + It("podman run network bind to 127.0.0.1", func() { + slirp4netnsHelp := SystemExec("slirp4netns", []string{"--help"}) + Expect(slirp4netnsHelp.ExitCode()).To(Equal(0)) + networkConfiguration := "slirp4netns:outbound_addr=127.0.0.1,allow_host_loopback=true" + + if strings.Contains(slirp4netnsHelp.OutputToString(), "outbound-addr") { + ncListener := StartSystemExec("nc", []string{"-v", "-n", "-l", "-p", "8083"}) + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8083"}) + session.Wait(30) + ncListener.Wait(30) + + Expect(session.ExitCode()).To(Equal(0)) + Expect(ncListener.ExitCode()).To(Equal(0)) + Expect(ncListener.ErrorToString()).To(ContainSubstring("127.0.0.1")) + } else { + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8083"}) + session.Wait(30) + Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("outbound_addr not supported")) + } + }) + + It("podman run network bind to HostIP", func() { + ip, err := utils.HostIP() + Expect(err).To(BeNil()) + + slirp4netnsHelp := SystemExec("slirp4netns", []string{"--help"}) + Expect(slirp4netnsHelp.ExitCode()).To(Equal(0)) + networkConfiguration := fmt.Sprintf("slirp4netns:outbound_addr=%s,allow_host_loopback=true", ip.String()) + + if strings.Contains(slirp4netnsHelp.OutputToString(), "outbound-addr") { + ncListener := StartSystemExec("nc", []string{"-v", "-n", "-l", "-p", "8084"}) + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8084"}) + session.Wait(30) + ncListener.Wait(30) + + Expect(session.ExitCode()).To(Equal(0)) + Expect(ncListener.ExitCode()).To(Equal(0)) + Expect(ncListener.ErrorToString()).To(ContainSubstring(ip.String())) + } else { + session := podmanTest.Podman([]string{"run", "--network", networkConfiguration, "-dt", ALPINE, "nc", "-w", "2", "10.0.2.2", "8084"}) + session.Wait(30) + Expect(session.ExitCode()).ToNot(Equal(0)) + Expect(session.ErrorToString()).To(ContainSubstring("outbound_addr not supported")) + } + }) + It("podman run network expose ports in image metadata", func() { session := podmanTest.Podman([]string{"create", "--name", "test", "-dt", "-P", nginx}) session.Wait(90) diff --git a/test/utils/utils.go b/test/utils/utils.go index 2c454f532af1..8ee702423b5b 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -341,6 +341,16 @@ func SystemExec(command string, args []string) *PodmanSession { return &PodmanSession{session} } +// StartSystemExec is used to start exec a system command +func StartSystemExec(command string, args []string) *PodmanSession { + c := exec.Command(command, args...) + session, err := gexec.Start(c, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run command: %s %s", command, strings.Join(args, " "))) + } + return &PodmanSession{session} +} + // StringInSlice determines if a string is in a string slice, returns bool func StringInSlice(s string, sl []string) bool { for _, i := range sl {