aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/common/netflags.go8
-rw-r--r--cmd/podman/common/specgen.go1
-rw-r--r--docs/source/markdown/podman-create.1.md5
-rw-r--r--docs/source/markdown/podman-run.1.md5
-rw-r--r--libpod/container.go2
-rw-r--r--libpod/networking_linux.go134
-rw-r--r--libpod/options.go13
-rw-r--r--pkg/domain/entities/types.go2
-rw-r--r--pkg/namespaces/namespaces.go2
-rw-r--r--pkg/specgen/generate/namespaces.go12
-rw-r--r--pkg/specgen/namespaces.go10
-rw-r--r--pkg/specgen/specgen.go3
-rw-r--r--test/e2e/run_networking_test.go29
13 files changed, 214 insertions, 12 deletions
diff --git a/cmd/podman/common/netflags.go b/cmd/podman/common/netflags.go
index 5c83a3c9c..82a82b310 100644
--- a/cmd/podman/common/netflags.go
+++ b/cmd/podman/common/netflags.go
@@ -2,6 +2,7 @@ package common
import (
"net"
+ "strings"
"github.com/containers/libpod/v2/cmd/podman/parse"
"github.com/containers/libpod/v2/libpod/define"
@@ -164,11 +165,18 @@ func NetFlagsToNetOptions(cmd *cobra.Command) (*entities.NetOptions, error) {
return nil, err
}
+ parts := strings.SplitN(network, ":", 2)
+
ns, cniNets, err := specgen.ParseNetworkNamespace(network)
if err != nil {
return nil, err
}
+ if len(parts) > 1 {
+ opts.NetworkOptions = make(map[string][]string)
+ opts.NetworkOptions[parts[0]] = strings.Split(parts[1], ",")
+ cniNets = nil
+ }
opts.Network = ns
opts.CNINetworks = cniNets
}
diff --git a/cmd/podman/common/specgen.go b/cmd/podman/common/specgen.go
index 8a265cedf..08099da4b 100644
--- a/cmd/podman/common/specgen.go
+++ b/cmd/podman/common/specgen.go
@@ -417,6 +417,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
s.DNSOptions = c.Net.DNSOptions
s.StaticIP = c.Net.StaticIP
s.StaticMAC = c.Net.StaticMAC
+ s.NetworkOptions = c.Net.NetworkOptions
s.UseImageHosts = c.Net.NoHosts
s.ImageVolumeMode = c.ImageVolume
diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md
index fd5e14535..4466e6616 100644
--- a/docs/source/markdown/podman-create.1.md
+++ b/docs/source/markdown/podman-create.1.md
@@ -553,7 +553,10 @@ Valid values are:
- `<network-name>|<network-id>`: connect to a user-defined network, multiple networks should be comma separated
- `ns:<path>`: path to a network namespace to join
- `private`: create a new namespace for the container (default)
-- `slirp4netns`: use slirp4netns to create a user network stack. This is the default for rootless containers
+- `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.
**--network-alias**=*alias*
diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md
index 68ffb69e5..de1d8aff6 100644
--- a/docs/source/markdown/podman-run.1.md
+++ b/docs/source/markdown/podman-run.1.md
@@ -561,7 +561,10 @@ Valid _mode_ values are:
- _network-id_: connect to a user-defined network, multiple networks should be comma separated;
- **ns:**_path_: path to a network namespace to join;
- `private`: create a new namespace for the container (default)
-- **slirp4netns**: use **slirp4netns**(1) to create a user network stack. This is the default for rootless containers.
+- **slirp4netns[:OPTIONS,...]**: use **slirp4netns**(1) 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.
**--network-alias**=*alias*
diff --git a/libpod/container.go b/libpod/container.go
index f7abfb005..1ca38ae7e 100644
--- a/libpod/container.go
+++ b/libpod/container.go
@@ -341,6 +341,8 @@ type ContainerConfig struct {
Networks []string `json:"networks,omitempty"`
// Network mode specified for the default network.
NetMode namespaces.NetworkMode `json:"networkMode,omitempty"`
+ // NetworkOptions are additional options for each network
+ NetworkOptions map[string][]string `json:"network_options,omitempty"`
// Image Config
diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go
index 1e79e8732..35e58f916 100644
--- a/libpod/networking_linux.go
+++ b/libpod/networking_linux.go
@@ -173,6 +173,19 @@ type slirpFeatures struct {
HasEnableSeccomp bool
}
+type slirp4netnsCmdArg struct {
+ Proto string `json:"proto,omitempty"`
+ HostAddr string `json:"host_addr"`
+ HostPort int32 `json:"host_port"`
+ GuestAddr string `json:"guest_addr"`
+ GuestPort int32 `json:"guest_port"`
+}
+
+type slirp4netnsCmd struct {
+ Execute string `json:"execute"`
+ Args slirp4netnsCmdArg `json:"arguments"`
+}
+
func checkSlirpFlags(path string) (*slirpFeatures, error) {
cmd := exec.Command(path, "--help")
out, err := cmd.CombinedOutput()
@@ -210,12 +223,33 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
havePortMapping := len(ctr.Config().PortMappings) > 0
logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID))
+ isSlirpHostForward := false
+ disableHostLoopback := true
+ 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
+ default:
+ return errors.Errorf("unknown option for slirp4netns: %q", o)
+
+ }
+ }
+ }
+
cmdArgs := []string{}
slirpFeatures, err := checkSlirpFlags(path)
if err != nil {
return errors.Wrapf(err, "error checking slirp4netns binary %s: %q", path, err)
}
- if slirpFeatures.HasDisableHostLoopback {
+ if disableHostLoopback && slirpFeatures.HasDisableHostLoopback {
cmdArgs = append(cmdArgs, "--disable-host-loopback")
}
if slirpFeatures.HasMTU {
@@ -228,6 +262,12 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
cmdArgs = append(cmdArgs, "--enable-seccomp")
}
+ var apiSocket string
+ if havePortMapping && isSlirpHostForward {
+ apiSocket = filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID))
+ cmdArgs = append(cmdArgs, "--api-socket", apiSocket)
+ }
+
// the slirp4netns arguments being passed are describes as follows:
// from the slirp4netns documentation: https://github.com/rootless-containers/slirp4netns
// -c, --configure Brings up the tap interface
@@ -291,7 +331,11 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
}
if havePortMapping {
- return r.setupRootlessPortMapping(ctr, netnsPath)
+ if isSlirpHostForward {
+ return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket)
+ } else {
+ return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
+ }
}
return nil
}
@@ -342,7 +386,7 @@ func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout t
return nil
}
-func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) error {
+func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath string) error {
syncR, syncW, err := os.Pipe()
if err != nil {
return errors.Wrapf(err, "failed to open pipe")
@@ -419,6 +463,90 @@ func (r *Runtime) setupRootlessPortMapping(ctr *Container, netnsPath string) err
return nil
}
+func (r *Runtime) setupRootlessPortMappingViaSlirp(ctr *Container, cmd *exec.Cmd, apiSocket string) (err error) {
+ const pidWaitTimeout = 60 * time.Second
+ chWait := make(chan error)
+ go func() {
+ interval := 25 * time.Millisecond
+ for i := time.Duration(0); i < pidWaitTimeout; i += interval {
+ // Check if the process is still running.
+ var status syscall.WaitStatus
+ pid, err := syscall.Wait4(cmd.Process.Pid, &status, syscall.WNOHANG, nil)
+ if err != nil {
+ break
+ }
+ if pid != cmd.Process.Pid {
+ continue
+ }
+ if status.Exited() || status.Signaled() {
+ chWait <- fmt.Errorf("slirp4netns exited with status %d", status.ExitStatus())
+ }
+ time.Sleep(interval)
+ }
+ }()
+ defer close(chWait)
+
+ // wait that API socket file appears before trying to use it.
+ if _, err := WaitForFile(apiSocket, chWait, pidWaitTimeout); err != nil {
+ return errors.Wrapf(err, "waiting for slirp4nets to create the api socket file %s", apiSocket)
+ }
+
+ // for each port we want to add we need to open a connection to the slirp4netns control socket
+ // and send the add_hostfwd command.
+ for _, i := range ctr.config.PortMappings {
+ conn, err := net.Dial("unix", apiSocket)
+ if err != nil {
+ return errors.Wrapf(err, "cannot open connection to %s", apiSocket)
+ }
+ defer func() {
+ if err := conn.Close(); err != nil {
+ logrus.Errorf("unable to close connection: %q", err)
+ }
+ }()
+ hostIP := i.HostIP
+ if hostIP == "" {
+ hostIP = "0.0.0.0"
+ }
+ apiCmd := slirp4netnsCmd{
+ Execute: "add_hostfwd",
+ Args: slirp4netnsCmdArg{
+ Proto: i.Protocol,
+ HostAddr: hostIP,
+ HostPort: i.HostPort,
+ GuestPort: i.ContainerPort,
+ },
+ }
+ // create the JSON payload and send it. Mark the end of request shutting down writes
+ // to the socket, as requested by slirp4netns.
+ data, err := json.Marshal(&apiCmd)
+ if err != nil {
+ return errors.Wrapf(err, "cannot marshal JSON for slirp4netns")
+ }
+ if _, err := conn.Write([]byte(fmt.Sprintf("%s\n", data))); err != nil {
+ return errors.Wrapf(err, "cannot write to control socket %s", apiSocket)
+ }
+ if err := conn.(*net.UnixConn).CloseWrite(); err != nil {
+ return errors.Wrapf(err, "cannot shutdown the socket %s", apiSocket)
+ }
+ buf := make([]byte, 2048)
+ readLength, err := conn.Read(buf)
+ if err != nil {
+ return errors.Wrapf(err, "cannot read from control socket %s", apiSocket)
+ }
+ // if there is no 'error' key in the received JSON data, then the operation was
+ // successful.
+ var y map[string]interface{}
+ if err := json.Unmarshal(buf[0:readLength], &y); err != nil {
+ return errors.Wrapf(err, "error parsing error status from slirp4netns")
+ }
+ if e, found := y["error"]; found {
+ return errors.Errorf("error from slirp4netns while setting up port redirection: %v", e)
+ }
+ }
+ logrus.Debug("slirp4netns port-forwarding setup via add_hostfwd is ready")
+ return nil
+}
+
// Configure the network namespace using the container process
func (r *Runtime) setupNetNS(ctr *Container) error {
nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
diff --git a/libpod/options.go b/libpod/options.go
index b3c11ebc1..32748a3c1 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1003,6 +1003,19 @@ func WithStaticIP(ip net.IP) CtrCreateOption {
}
}
+// WithNetworkOptions sets additional options for the networks.
+func WithNetworkOptions(options map[string][]string) CtrCreateOption {
+ return func(ctr *Container) error {
+ if ctr.valid {
+ return define.ErrCtrFinalized
+ }
+
+ ctr.config.NetworkOptions = options
+
+ return nil
+ }
+}
+
// WithStaticMAC indicates that the container should request a static MAC from
// the CNI plugins.
// It cannot be set unless WithNetNS has already been passed.
diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go
index 7e910ff61..b313e5f8b 100644
--- a/pkg/domain/entities/types.go
+++ b/pkg/domain/entities/types.go
@@ -42,6 +42,8 @@ type NetOptions struct {
PublishPorts []specgen.PortMapping
StaticIP *net.IP
StaticMAC *net.HardwareAddr
+ // NetworkOptions are additional options for each network
+ NetworkOptions map[string][]string
}
// All CLI inspect commands and inspect sub-commands use the same options
diff --git a/pkg/namespaces/namespaces.go b/pkg/namespaces/namespaces.go
index 2ffbde977..7831af8f9 100644
--- a/pkg/namespaces/namespaces.go
+++ b/pkg/namespaces/namespaces.go
@@ -385,7 +385,7 @@ func (n NetworkMode) IsBridge() bool {
// IsSlirp4netns indicates if we are running a rootless network stack
func (n NetworkMode) IsSlirp4netns() bool {
- return n == slirpType
+ return n == slirpType || strings.HasPrefix(string(n), slirpType+":")
}
// IsNS indicates a network namespace passed in by path (ns:<path>)
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
index 09d6ba445..a19009bc2 100644
--- a/pkg/specgen/generate/namespaces.go
+++ b/pkg/specgen/generate/namespaces.go
@@ -2,6 +2,7 @@ package generate
import (
"context"
+ "fmt"
"os"
"strings"
@@ -226,7 +227,11 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if err != nil {
return nil, err
}
- toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, "slirp4netns", nil))
+ val := "slirp4netns"
+ if s.NetNS.Value != "" {
+ val = fmt.Sprintf("slirp4netns:%s", s.NetNS.Value)
+ }
+ toReturn = append(toReturn, libpod.WithNetNS(portMappings, postConfigureNetNS, val, nil))
case specgen.Bridge:
portMappings, err := createPortMappings(ctx, s, img)
if err != nil {
@@ -261,6 +266,9 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
if s.StaticMAC != nil {
toReturn = append(toReturn, libpod.WithStaticMAC(*s.StaticMAC))
}
+ if s.NetworkOptions != nil {
+ toReturn = append(toReturn, libpod.WithNetworkOptions(s.NetworkOptions))
+ }
return toReturn, nil
}
@@ -465,7 +473,7 @@ func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) {
case "pid":
options = append(options, libpod.WithPodPID())
case "user":
- return erroredOptions, errors.Errorf("User sharing functionality not supported on pod level")
+ continue
case "ipc":
options = append(options, libpod.WithPodIPC())
case "uts":
diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go
index 5f56b242b..9bf2c5d05 100644
--- a/pkg/specgen/namespaces.go
+++ b/pkg/specgen/namespaces.go
@@ -108,7 +108,9 @@ func validateNetNS(n *Namespace) error {
return nil
}
switch n.NSMode {
- case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge, Slirp:
+ case Slirp:
+ break
+ case "", Default, Host, Path, FromContainer, FromPod, Private, NoNetwork, Bridge:
break
default:
return errors.Errorf("invalid network %q", n.NSMode)
@@ -119,8 +121,8 @@ func validateNetNS(n *Namespace) error {
if len(n.Value) < 1 {
return errors.Errorf("namespace mode %s requires a value", n.NSMode)
}
- } else {
- // All others must NOT set a string value
+ } else if n.NSMode != Slirp {
+ // All others except must NOT set a string value
if len(n.Value) > 0 {
return errors.Errorf("namespace value %s cannot be provided with namespace mode %s", n.Value, n.NSMode)
}
@@ -250,7 +252,7 @@ func ParseNetworkNamespace(ns string) (Namespace, []string, error) {
var cniNetworks []string
// Net defaults to Slirp on rootless
switch {
- case ns == "slirp4netns":
+ case ns == "slirp4netns", strings.HasPrefix(ns, "slirp4netns:"):
toReturn.NSMode = Slirp
case ns == "pod":
toReturn.NSMode = FromPod
diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go
index 17583d82a..a346a9742 100644
--- a/pkg/specgen/specgen.go
+++ b/pkg/specgen/specgen.go
@@ -379,6 +379,9 @@ type ContainerNetworkConfig struct {
// Conflicts with UseImageHosts.
// Optional.
HostAdd []string `json:"hostadd,omitempty"`
+ // NetworkOptions are additional options for each network
+ // Optional.
+ NetworkOptions map[string][]string `json:"network_options,omitempty"`
}
// ContainerResourceConfig contains information on container resource limits.
diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go
index 467d0c5ef..317f760db 100644
--- a/test/e2e/run_networking_test.go
+++ b/test/e2e/run_networking_test.go
@@ -235,6 +235,35 @@ var _ = Describe("Podman run networking", func() {
Expect(ncBusy).To(ExitWithError())
})
+ It("podman run network expose host port 8081 to container port 8000 using rootlesskit port handler", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "slirp4netns:port_handler=rootlesskit", "-dt", "-p", "8081:8000", ALPINE, "/bin/sh"})
+ session.Wait(30)
+ Expect(session.ExitCode()).To(Equal(0))
+
+ ncBusy := SystemExec("nc", []string{"-l", "-p", "8081"})
+ Expect(ncBusy).To(ExitWithError())
+ })
+
+ It("podman run network expose host port 8082 to container port 8000 using slirp4netns port handler", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "slirp4netns:port_handler=slirp4netns", "-dt", "-p", "8082:8000", ALPINE, "/bin/sh"})
+ session.Wait(30)
+ Expect(session.ExitCode()).To(Equal(0))
+ ncBusy := SystemExec("nc", []string{"-l", "-p", "8082"})
+ Expect(ncBusy).To(ExitWithError())
+ })
+
+ It("podman run network expose host port 8080 to container port 8000 using invalid port handler", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "slirp4netns:port_handler=invalid", "-dt", "-p", "8080:8000", ALPINE, "/bin/sh"})
+ session.Wait(30)
+ Expect(session.ExitCode()).To(Not(Equal(0)))
+ })
+
+ It("podman run slirp4netns network with host loopback", func() {
+ session := podmanTest.Podman([]string{"run", "--network", "slirp4netns:allow_host_loopback=true", ALPINE, "ping", "-c1", "10.0.2.2"})
+ session.Wait(30)
+ Expect(session.ExitCode()).To(Equal(0))
+ })
+
It("podman run network expose ports in image metadata", func() {
session := podmanTest.Podman([]string{"create", "-dt", "-P", nginx})
session.Wait(90)