diff --git a/common/docs/containers.conf.5.md b/common/docs/containers.conf.5.md index a7f233709c..6f15afe3c1 100644 --- a/common/docs/containers.conf.5.md +++ b/common/docs/containers.conf.5.md @@ -298,9 +298,9 @@ Logging driver for the container. Currently available options are k8s-file, jour **log_path**="" -Default path for container logs to be stored in. When empty, logs will be stored +Default path for container logs to be stored in. When empty, logs will be stored in the container's default storage and removed when the container is removed. -A subdirectory named with the container ID will be created under the specified +A subdirectory named with the container ID will be created under the specified path, and the log file will have the default name `ctr.log` within that directory. This option can be overridden by the `--log-opt` flag. @@ -495,8 +495,8 @@ default_subnet_pools = [ **default_rootless_network_cmd**="pasta" -Configure which rootless network program to use by default. Valid options are -`slirp4netns` and `pasta` (default). +Configure which rootless network program to use by default. Only current valid option is +`pasta` (default). **network_config_dir**="/etc/cni/net.d/" @@ -675,7 +675,6 @@ The following binaries are searched in these directories: - catatonit - netavark - pasta - - slirp4netns Podman machine uses it for these binaries: - gvproxy @@ -776,28 +775,6 @@ create new containers and pods in that namespace. The default namespace is "", which corresponds to no namespace. When no namespace is set, all containers and pods are visible. -**network_cmd_path**="" - -Path to the slirp4netns binary. - -**network_cmd_options**=[] - -Default options to pass to the slirp4netns binary. - -Valid options values are: - - - **allow_host_loopback=true|false**: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). Default is false. - - **mtu=MTU**: Specify the MTU to use for this network. (Default is `65520`). - - **cidr=CIDR**: Specify ip range to use for this network. (Default is `10.0.2.0/24`). - - **enable_ipv6=true|false**: Enable IPv6. Default is true. (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. - Note: Rootlesskit changes the source IP address of incoming packets to a IP address in the container network namespace, usually `10.0.2.100`. If your application requires the real source IP address, e.g. web server logs, use the slirp4netns port handler. The rootlesskit port handler is also used for rootless containers when connected to user-defined networks. - - **port_handler=slirp4netns**: Use the slirp4netns port forwarding, it is slower than rootlesskit but preserves the correct source IP address. This port handler cannot be used for user-defined networks. - **no_pivot_root**=false Whether to use chroot instead of pivot_root in the runtime. diff --git a/common/libnetwork/internal/rootlessnetns/netns_linux.go b/common/libnetwork/internal/rootlessnetns/netns_linux.go index 05b3b16dd6..3056a7d51d 100644 --- a/common/libnetwork/internal/rootlessnetns/netns_linux.go +++ b/common/libnetwork/internal/rootlessnetns/netns_linux.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io/fs" - "net" "os" "path/filepath" "strconv" @@ -18,7 +17,6 @@ import ( "github.com/sirupsen/logrus" "go.podman.io/common/libnetwork/pasta" "go.podman.io/common/libnetwork/resolvconf" - "go.podman.io/common/libnetwork/slirp4netns" "go.podman.io/common/libnetwork/types" "go.podman.io/common/pkg/config" "go.podman.io/common/pkg/netns" @@ -38,7 +36,7 @@ const ( // infoCacheFile file name for the cache file used to store the rootless netns info. infoCacheFile = "info.json" - // rootlessNetNsConnPidFile is the name of the rootless netns slirp4netns/pasta pid file. + // rootlessNetNsConnPidFile is the name of the rootless netns pasta pid file. rootlessNetNsConnPidFile = "rootless-netns-conn.pid" // persistentCNIDir is the directory where the CNI files are stored. @@ -114,7 +112,7 @@ func (n *Netns) getOrCreateNetns() (ns.NetNS, bool, error) { pidPath := n.getPath(rootlessNetNsConnPidFile) pid, err := readPidFile(pidPath) if err == nil { - // quick check if pasta/slirp4netns are still running + // quick check if pasta is still running err := unix.Kill(pid, 0) if err == nil { if err := n.deserializeInfo(); err != nil { @@ -156,14 +154,12 @@ func (n *Netns) getOrCreateNetns() (ns.NetNS, bool, error) { } } switch strings.ToLower(n.config.Network.DefaultRootlessNetworkCmd) { - case "", slirp4netns.BinaryName: - err = n.setupSlirp4netns(nsPath) - case pasta.BinaryName: + case "", pasta.BinaryName: err = n.setupPasta(nsPath) default: err = fmt.Errorf("invalid rootless network command %q", n.config.Network.DefaultRootlessNetworkCmd) } - // If pasta or slirp4netns fail here we need to get rid of the netns again to not leak it, + // If pasta fails here we need to get rid of the netns again to not leak it, // otherwise the next command thinks the netns was successfully setup. if err != nil { if nerr := netns.UnmountNS(nsPath); nerr != nil { @@ -222,7 +218,7 @@ func (n *Netns) setupPasta(nsPath string) error { return fmt.Errorf("unable to decode pasta PID: %w", err) } - if err := systemd.MoveRootlessNetnsSlirpProcessToUserSlice(pid); err != nil { + if err := systemd.MoveRootlessNetnsProcessToUserSlice(pid); err != nil { // only log this, it is not fatal but can lead to issues when running podman inside systemd units logrus.Errorf("failed to move the rootless netns pasta process to the systemd user.slice: %v", err) } @@ -253,68 +249,6 @@ func (n *Netns) setupPasta(nsPath string) error { return nil } -func (n *Netns) setupSlirp4netns(nsPath string) error { - res, err := slirp4netns.Setup(&slirp4netns.SetupOptions{ - Config: n.config, - ContainerID: "rootless-netns", - Netns: nsPath, - }) - if err != nil { - return wrapError("start slirp4netns", err) - } - // create pid file for the slirp4netns process - // this is need to kill the process in the cleanup - pid := strconv.Itoa(res.Pid) - err = os.WriteFile(n.getPath(rootlessNetNsConnPidFile), []byte(pid), 0o600) - if err != nil { - return wrapError("write slirp4netns pid file", err) - } - - if systemd.RunsOnSystemd() { - // move to systemd scope to prevent systemd from killing it - err = systemd.MoveRootlessNetnsSlirpProcessToUserSlice(res.Pid) - if err != nil { - // only log this, it is not fatal but can lead to issues when running podman inside systemd units - logrus.Errorf("failed to move the rootless netns slirp4netns process to the systemd user.slice: %v", err) - } - } - - // build a new resolv.conf file which uses the slirp4netns dns server address - resolveIP, err := slirp4netns.GetDNS(res.Subnet) - if err != nil { - return wrapError("determine default slirp4netns DNS address", err) - } - nameservers := []string{resolveIP.String()} - - netnsIP, err := slirp4netns.GetIP(res.Subnet) - if err != nil { - return wrapError("determine default slirp4netns ip address", err) - } - - if err := resolvconf.New(&resolvconf.Params{ - Path: n.getPath(resolvConfName), - // fake the netns since we want to filter localhost - Namespaces: []specs.LinuxNamespace{ - {Type: specs.NetworkNamespace}, - }, - IPv6Enabled: res.IPv6, - KeepHostServers: true, - Nameservers: nameservers, - }); err != nil { - return wrapError("create resolv.conf", err) - } - - n.info = &types.RootlessNetnsInfo{ - IPAddresses: []net.IP{*netnsIP}, - DnsForwardIps: nameservers, - } - if err := n.serializeInfo(); err != nil { - return wrapError("serialize info", err) - } - - return nil -} - func (n *Netns) cleanupRootlessNetns() error { pidFile := n.getPath(rootlessNetNsConnPidFile) pid, err := readPidFile(pidFile) @@ -324,7 +258,7 @@ func (n *Netns) cleanupRootlessNetns() error { return nil } if err == nil { - // kill the slirp/pasta process so we do not leak it + // kill the pasta process so we do not leak it err = unix.Kill(pid, unix.SIGTERM) if err == unix.ESRCH { err = nil diff --git a/common/libnetwork/slirp4netns/const.go b/common/libnetwork/slirp4netns/const.go deleted file mode 100644 index 82f3bff3a0..0000000000 --- a/common/libnetwork/slirp4netns/const.go +++ /dev/null @@ -1,17 +0,0 @@ -package slirp4netns - -import "net" - -const ( - BinaryName = "slirp4netns" -) - -// SetupResult return type from Setup(). -type SetupResult struct { - // Pid of the created slirp4netns process - Pid int - // Subnet which is used by slirp4netns - Subnet *net.IPNet - // IPv6 whenever Ipv6 is enabled in slirp4netns - IPv6 bool -} diff --git a/common/libnetwork/slirp4netns/const_linux.go b/common/libnetwork/slirp4netns/const_linux.go deleted file mode 100644 index 8e2742fe3f..0000000000 --- a/common/libnetwork/slirp4netns/const_linux.go +++ /dev/null @@ -1,11 +0,0 @@ -package slirp4netns - -const ( - ipv6ConfDefaultAcceptDadSysctl = "/proc/sys/net/ipv6/conf/default/accept_dad" - - // defaultMTU the default MTU override. - defaultMTU = 65520 - - // default slirp4ns subnet. - defaultSubnet = "10.0.2.0/24" -) diff --git a/common/libnetwork/slirp4netns/slirp4netns.go b/common/libnetwork/slirp4netns/slirp4netns.go deleted file mode 100644 index 083a4e5fcc..0000000000 --- a/common/libnetwork/slirp4netns/slirp4netns.go +++ /dev/null @@ -1,743 +0,0 @@ -//go:build linux - -package slirp4netns - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io" - "net" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "sync" - "syscall" - "time" - - "github.com/containernetworking/plugins/pkg/ns" - "github.com/sirupsen/logrus" - "go.podman.io/common/libnetwork/types" - "go.podman.io/common/pkg/config" - "go.podman.io/common/pkg/rootlessport" - "go.podman.io/common/pkg/servicereaper" - "go.podman.io/common/pkg/util" -) - -type slirpFeatures struct { - HasDisableHostLoopback bool - HasMTU bool - HasEnableSandbox bool - HasEnableSeccomp bool - HasCIDR bool - HasOutboundAddr bool - HasIPv6 bool -} - -type slirp4netnsCmdArg struct { - Proto string `json:"proto,omitempty"` - HostAddr string `json:"host_addr"` - HostPort uint16 `json:"host_port"` - GuestAddr string `json:"guest_addr"` - GuestPort uint16 `json:"guest_port"` -} - -type slirp4netnsCmd struct { - Execute string `json:"execute"` - Args slirp4netnsCmdArg `json:"arguments"` -} - -type networkOptions struct { - cidr string - disableHostLoopback bool - enableIPv6 bool - isSlirpHostForward bool - noPivotRoot bool - mtu int - outboundAddr string - outboundAddr6 string -} - -type SetupOptions struct { - // Config used to get slip4netns path and other default options - Config *config.Config - // ContainerID is the ID of the container - ContainerID string - // Netns path to the netns - Netns string - // Ports the should be forwarded - Ports []types.PortMapping - // ExtraOptions for slirp4netns that were set on the cli - ExtraOptions []string - // Slirp4netnsExitPipeR pipe used to exit the slirp4netns process. - // This is must be the reading end, the writer must be kept open until you want the - // process to exit. For podman, conmon will hold the pipe open. - // It can be set to nil in which case we do not use the pipe exit and the caller - // must use the returned pid to kill the process after it is done. - Slirp4netnsExitPipeR *os.File - // RootlessPortSyncPipe pipe used to exit the rootlessport process. - // Same as Slirp4netnsExitPipeR, except this is only used when ports are given. - RootlessPortExitPipeR *os.File - // Pdeathsig is the signal which is send to slirp4netns process if the calling thread - // exits. The caller is responsible for locking the thread with runtime.LockOSThread(). - Pdeathsig syscall.Signal -} - -type logrusDebugWriter struct { - prefix string -} - -func (w *logrusDebugWriter) Write(p []byte) (int, error) { - logrus.Debugf("%s%s", w.prefix, string(p)) - return len(p), nil -} - -func checkSlirpFlags(path string) (*slirpFeatures, error) { - cmd := exec.Command(path, "--help") - out, err := cmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("slirp4netns %q: %w", out, err) - } - return &slirpFeatures{ - HasDisableHostLoopback: strings.Contains(string(out), "--disable-host-loopback"), - HasMTU: strings.Contains(string(out), "--mtu"), - HasEnableSandbox: strings.Contains(string(out), "--enable-sandbox"), - HasEnableSeccomp: strings.Contains(string(out), "--enable-seccomp"), - HasCIDR: strings.Contains(string(out), "--cidr"), - HasOutboundAddr: strings.Contains(string(out), "--outbound-addr"), - HasIPv6: strings.Contains(string(out), "--enable-ipv6"), - }, nil -} - -func parseNetworkOptions(config *config.Config, extraOptions []string) (*networkOptions, error) { - options := make([]string, 0, len(config.Engine.NetworkCmdOptions.Get())+len(extraOptions)) - options = append(options, config.Engine.NetworkCmdOptions.Get()...) - options = append(options, extraOptions...) - opts := &networkOptions{ - // overwrite defaults - disableHostLoopback: true, - mtu: defaultMTU, - noPivotRoot: config.Engine.NoPivotRoot, - enableIPv6: true, - } - for _, o := range options { - option, value, ok := strings.Cut(o, "=") - if !ok { - return nil, fmt.Errorf("unknown option for slirp4netns: %q", o) - } - switch option { - case "cidr": - ipv4, _, err := net.ParseCIDR(value) - if err != nil || ipv4.To4() == nil { - return nil, fmt.Errorf("invalid cidr %q", value) - } - opts.cidr = value - case "port_handler": - switch value { - case "slirp4netns": - opts.isSlirpHostForward = true - case "rootlesskit": - opts.isSlirpHostForward = false - default: - return nil, fmt.Errorf("unknown port_handler for slirp4netns: %q", value) - } - case "allow_host_loopback": - switch value { - case "true": - opts.disableHostLoopback = false - case "false": - opts.disableHostLoopback = true - default: - return nil, fmt.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value) - } - case "enable_ipv6": - switch value { - case "true": - opts.enableIPv6 = true - case "false": - opts.enableIPv6 = false - default: - return nil, fmt.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 nil, fmt.Errorf("invalid outbound_addr %q", value) - } - } - opts.outboundAddr = value - case "outbound_addr6": - ipv6 := net.ParseIP(value) - if ipv6 == nil || ipv6.To4() != nil { - _, err := net.InterfaceByName(value) - if err != nil { - return nil, fmt.Errorf("invalid outbound_addr6: %q", value) - } - } - opts.outboundAddr6 = value - case "mtu": - var err error - opts.mtu, err = strconv.Atoi(value) - if opts.mtu < 68 || err != nil { - return nil, fmt.Errorf("invalid mtu %q", value) - } - default: - return nil, fmt.Errorf("unknown option for slirp4netns: %q", o) - } - } - return opts, nil -} - -func createBasicSlirpCmdArgs(options *networkOptions, features *slirpFeatures) ([]string, error) { - cmdArgs := []string{} - if options.disableHostLoopback && features.HasDisableHostLoopback { - cmdArgs = append(cmdArgs, "--disable-host-loopback") - } - if options.mtu > -1 && features.HasMTU { - cmdArgs = append(cmdArgs, "--mtu="+strconv.Itoa(options.mtu)) - } - if !options.noPivotRoot && features.HasEnableSandbox { - cmdArgs = append(cmdArgs, "--enable-sandbox") - } - if features.HasEnableSeccomp { - cmdArgs = append(cmdArgs, "--enable-seccomp") - } - - if options.cidr != "" { - if !features.HasCIDR { - return nil, errors.New("cidr not supported") - } - cmdArgs = append(cmdArgs, "--cidr="+options.cidr) - } - - if options.enableIPv6 { - if !features.HasIPv6 { - return nil, errors.New("enable_ipv6 not supported") - } - cmdArgs = append(cmdArgs, "--enable-ipv6") - } - - if options.outboundAddr != "" { - if !features.HasOutboundAddr { - return nil, errors.New("outbound_addr not supported") - } - cmdArgs = append(cmdArgs, "--outbound-addr="+options.outboundAddr) - } - - if options.outboundAddr6 != "" { - if !features.HasOutboundAddr || !features.HasIPv6 { - return nil, errors.New("outbound_addr6 not supported") - } - if !options.enableIPv6 { - return nil, errors.New("enable_ipv6=true is required for outbound_addr6") - } - cmdArgs = append(cmdArgs, "--outbound-addr6="+options.outboundAddr6) - } - - return cmdArgs, nil -} - -// Setup can be called in rootful as well as in rootless. -// Spawns the slirp4netns process and setup port forwarding if ports are given. -func Setup(opts *SetupOptions) (*SetupResult, error) { - path := opts.Config.Engine.NetworkCmdPath - if path == "" { - var err error - path, err = opts.Config.FindHelperBinary(BinaryName, true) - if err != nil { - return nil, fmt.Errorf("could not find slirp4netns, the network namespace can't be configured: %w", err) - } - } - - syncR, syncW, err := os.Pipe() - if err != nil { - return nil, fmt.Errorf("failed to open pipe: %w", err) - } - defer closeQuiet(syncR) - defer closeQuiet(syncW) - - havePortMapping := len(opts.Ports) > 0 - logPath := filepath.Join(opts.Config.Engine.TmpDir, fmt.Sprintf("slirp4netns-%s.log", opts.ContainerID)) - - netOptions, err := parseNetworkOptions(opts.Config, opts.ExtraOptions) - if err != nil { - return nil, err - } - slirpFeatures, err := checkSlirpFlags(path) - if err != nil { - return nil, fmt.Errorf("checking slirp4netns binary %s: %q: %w", path, err, err) - } - cmdArgs, err := createBasicSlirpCmdArgs(netOptions, slirpFeatures) - if err != nil { - return nil, err - } - - // the slirp4netns arguments being passed are described as follows: - // from the slirp4netns documentation: https://github.com/rootless-containers/slirp4netns - // -c, --configure Brings up the tap interface - // -e, --exit-fd=FD specify the FD for terminating slirp4netns - // -r, --ready-fd=FD specify the FD to write to when the initialization steps are finished - cmdArgs = append(cmdArgs, "-c", "-r", "3") - if opts.Slirp4netnsExitPipeR != nil { - cmdArgs = append(cmdArgs, "-e", "4") - } - - var apiSocket string - if havePortMapping && netOptions.isSlirpHostForward { - apiSocket = filepath.Join(opts.Config.Engine.TmpDir, opts.ContainerID+".net") - cmdArgs = append(cmdArgs, "--api-socket", apiSocket) - } - - cmdArgs = append(cmdArgs, "--netns-type=path", opts.Netns, "tap0") - - cmd := exec.Command(path, cmdArgs...) - logrus.Debugf("slirp4netns command: %s", strings.Join(cmd.Args, " ")) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - Pdeathsig: opts.Pdeathsig, - } - - // workaround for https://github.com/rootless-containers/slirp4netns/pull/153 - if !netOptions.noPivotRoot && slirpFeatures.HasEnableSandbox { - cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWNS - cmd.SysProcAttr.Unshareflags = syscall.CLONE_NEWNS - } - - // Leak one end of the pipe in slirp4netns, the other will be sent to conmon - cmd.ExtraFiles = append(cmd.ExtraFiles, syncW) - if opts.Slirp4netnsExitPipeR != nil { - cmd.ExtraFiles = append(cmd.ExtraFiles, opts.Slirp4netnsExitPipeR) - } - - logFile, err := os.Create(logPath) - if err != nil { - return nil, fmt.Errorf("failed to open slirp4netns log file %s: %w", logPath, err) - } - defer logFile.Close() - // Unlink immediately the file so we won't need to worry about cleaning it up later. - // It is still accessible through the open fd logFile. - if err := os.Remove(logPath); err != nil { - return nil, fmt.Errorf("delete file %s: %w", logPath, err) - } - cmd.Stdout = logFile - cmd.Stderr = logFile - - var slirpReadyWg, netnsReadyWg *sync.WaitGroup - if netOptions.enableIPv6 { - // use two wait groups to make sure we set the sysctl before - // starting slirp and reset it only after slirp is ready - slirpReadyWg = &sync.WaitGroup{} - netnsReadyWg = &sync.WaitGroup{} - slirpReadyWg.Add(1) - netnsReadyWg.Add(1) - - go func() { - err := ns.WithNetNSPath(opts.Netns, func(_ ns.NetNS) error { - // Duplicate Address Detection slows the ipv6 setup down for 1-2 seconds. - // Since slirp4netns is run in its own namespace and not directly routed - // we can skip this to make the ipv6 address immediately available. - // We change the default to make sure the slirp tap interface gets the - // correct value assigned so DAD is disabled for it - // Also make sure to change this value back to the original after slirp4netns - // is ready in case users rely on this sysctl. - orgValue, err := os.ReadFile(ipv6ConfDefaultAcceptDadSysctl) - if err != nil { - netnsReadyWg.Done() - // on ipv6 disabled systems the sysctl does not exist - // so we should not error - if errors.Is(err, os.ErrNotExist) { - return nil - } - return err - } - err = os.WriteFile(ipv6ConfDefaultAcceptDadSysctl, []byte("0"), 0o644) - netnsReadyWg.Done() - if err != nil { - return err - } - - // wait until slirp4nets is ready before resetting this value - slirpReadyWg.Wait() - return os.WriteFile(ipv6ConfDefaultAcceptDadSysctl, orgValue, 0o644) - }) - if err != nil { - logrus.Warnf("failed to set net.ipv6.conf.default.accept_dad sysctl: %v", err) - } - }() - - // wait until we set the sysctl - netnsReadyWg.Wait() - } - - if err := cmd.Start(); err != nil { - if netOptions.enableIPv6 { - slirpReadyWg.Done() - } - return nil, fmt.Errorf("failed to start slirp4netns process: %w", err) - } - defer func() { - servicereaper.AddPID(cmd.Process.Pid) - if err := cmd.Process.Release(); err != nil { - logrus.Errorf("Unable to release command process: %q", err) - } - }() - - err = waitForSync(syncR, cmd, logFile, 1*time.Second) - if netOptions.enableIPv6 { - slirpReadyWg.Done() - } - if err != nil { - return nil, err - } - - // Set a default slirp subnet. Parsing a string with the net helper is easier than building the struct myself - _, slirpSubnet, _ := net.ParseCIDR(defaultSubnet) - - // Set slirp4netnsSubnet addresses now that we are pretty sure the command executed - if netOptions.cidr != "" { - ipv4, ipv4network, err := net.ParseCIDR(netOptions.cidr) - if err != nil || ipv4.To4() == nil { - return nil, fmt.Errorf("invalid cidr %q", netOptions.cidr) - } - slirpSubnet = ipv4network - } - - if havePortMapping { - if netOptions.isSlirpHostForward { - err = setupRootlessPortMappingViaSlirp(opts.Ports, cmd, apiSocket) - } else { - err = SetupRootlessPortMappingViaRLK(opts, slirpSubnet, nil) - } - if err != nil { - return nil, err - } - } - - return &SetupResult{ - Pid: cmd.Process.Pid, - Subnet: slirpSubnet, - IPv6: netOptions.enableIPv6, - }, nil -} - -// GetIP returns the slirp ipv4 address based on subnet. If subnet is null use default subnet. -// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description -func GetIP(subnet *net.IPNet) (*net.IP, error) { - _, slirpSubnet, _ := net.ParseCIDR(defaultSubnet) - if subnet != nil { - slirpSubnet = subnet - } - expectedIP, err := addToIP(slirpSubnet, uint32(100)) - if err != nil { - return nil, fmt.Errorf("calculating expected ip for slirp4netns: %w", err) - } - return expectedIP, nil -} - -// GetGateway returns the slirp gateway ipv4 address based on subnet. -// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description -func GetGateway(subnet *net.IPNet) (*net.IP, error) { - _, slirpSubnet, _ := net.ParseCIDR(defaultSubnet) - if subnet != nil { - slirpSubnet = subnet - } - expectedGatewayIP, err := addToIP(slirpSubnet, uint32(2)) - if err != nil { - return nil, fmt.Errorf("calculating expected gateway ip for slirp4netns: %w", err) - } - return expectedGatewayIP, nil -} - -// GetDNS returns slirp DNS ipv4 address based on subnet. -// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description -func GetDNS(subnet *net.IPNet) (*net.IP, error) { - _, slirpSubnet, _ := net.ParseCIDR(defaultSubnet) - if subnet != nil { - slirpSubnet = subnet - } - expectedDNSIP, err := addToIP(slirpSubnet, uint32(3)) - if err != nil { - return nil, fmt.Errorf("calculating expected dns ip for slirp4netns: %w", err) - } - return expectedDNSIP, nil -} - -// Helper function to calculate slirp ip address offsets -// Adapted from: https://github.com/signalsciences/ipv4/blob/master/int.go#L12-L24 -func addToIP(subnet *net.IPNet, offset uint32) (*net.IP, error) { - // I have no idea why I have to do this, but if I don't ip is 0 - ipFixed := subnet.IP.To4() - - ipInteger := uint32(ipFixed[3]) | uint32(ipFixed[2])<<8 | uint32(ipFixed[1])<<16 | uint32(ipFixed[0])<<24 - ipNewRaw := ipInteger + offset - // Avoid overflows - if ipNewRaw < ipInteger { - return nil, fmt.Errorf("integer overflow while calculating ip address offset, %s + %d", ipFixed, offset) - } - ipNew := net.IPv4(byte(ipNewRaw>>24), byte(ipNewRaw>>16&0xFF), byte(ipNewRaw>>8)&0xFF, byte(ipNewRaw&0xFF)) - if !subnet.Contains(ipNew) { - return nil, fmt.Errorf("calculated ip address %s is not within given subnet %s", ipNew.String(), subnet.String()) - } - return &ipNew, nil -} - -func waitForSync(syncR *os.File, cmd *exec.Cmd, logFile io.ReadSeeker, timeout time.Duration) error { - prog := filepath.Base(cmd.Path) - if len(cmd.Args) > 0 { - prog = cmd.Args[0] - } - b := make([]byte, 16) - for { - if err := syncR.SetDeadline(time.Now().Add(timeout)); err != nil { - return fmt.Errorf("setting %s pipe timeout: %w", prog, err) - } - // FIXME: return err as soon as proc exits, without waiting for timeout - _, err := syncR.Read(b) - if err == nil { - break - } - if errors.Is(err, os.ErrDeadlineExceeded) { - // 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 { - return fmt.Errorf("failed to read %s process status: %w", prog, err) - } - if pid != cmd.Process.Pid { - continue - } - if status.Exited() { - // Seek at the beginning of the file and read all its content - if _, err := logFile.Seek(0, 0); err != nil { - logrus.Errorf("Could not seek log file: %q", err) - } - logContent, err := io.ReadAll(logFile) - if err != nil { - return fmt.Errorf("%s failed: %w", prog, err) - } - return fmt.Errorf("%s failed: %q", prog, logContent) - } - if status.Signaled() { - return fmt.Errorf("%s killed by signal", prog) - } - continue - } - return fmt.Errorf("failed to read from %s sync pipe: %w", prog, err) - } - return nil -} - -func SetupRootlessPortMappingViaRLK(opts *SetupOptions, slirpSubnet *net.IPNet, netStatus map[string]types.StatusBlock) error { - syncR, syncW, err := os.Pipe() - if err != nil { - return fmt.Errorf("failed to open pipe: %w", err) - } - defer closeQuiet(syncR) - defer closeQuiet(syncW) - - logPath := filepath.Join(opts.Config.Engine.TmpDir, fmt.Sprintf("rootlessport-%s.log", opts.ContainerID)) - logFile, err := os.Create(logPath) - if err != nil { - return fmt.Errorf("failed to open rootlessport log file %s: %w", logPath, err) - } - defer logFile.Close() - // Unlink immediately the file so we won't need to worry about cleaning it up later. - // It is still accessible through the open fd logFile. - if err := os.Remove(logPath); err != nil { - return fmt.Errorf("delete file %s: %w", logPath, err) - } - - childIP := GetRootlessPortChildIP(slirpSubnet, netStatus) - cfg := rootlessport.Config{ - Mappings: opts.Ports, - NetNSPath: opts.Netns, - ExitFD: 3, - ReadyFD: 4, - TmpDir: opts.Config.Engine.TmpDir, - ChildIP: childIP, - ContainerID: opts.ContainerID, - RootlessCNI: netStatus != nil, - } - cfgJSON, err := json.Marshal(cfg) - if err != nil { - return err - } - cfgR := bytes.NewReader(cfgJSON) - var stdout bytes.Buffer - path, err := opts.Config.FindHelperBinary(rootlessport.BinaryName, false) - if err != nil { - return err - } - cmd := exec.Command(path) - cmd.Args = []string{rootlessport.BinaryName} - - // Leak one end of the pipe in rootlessport process, the other will be sent to conmon - cmd.ExtraFiles = append(cmd.ExtraFiles, opts.RootlessPortExitPipeR, syncW) - cmd.Stdin = cfgR - // stdout is for human-readable error, stderr is for debug log - cmd.Stdout = &stdout - cmd.Stderr = io.MultiWriter(logFile, &logrusDebugWriter{"rootlessport: "}) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setpgid: true, - } - if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to start rootlessport process: %w", err) - } - defer func() { - servicereaper.AddPID(cmd.Process.Pid) - if err := cmd.Process.Release(); err != nil { - logrus.Errorf("Unable to release rootlessport process: %q", err) - } - }() - if err := waitForSync(syncR, cmd, logFile, 3*time.Second); err != nil { - stdoutStr := stdout.String() - if stdoutStr != "" { - // err contains full debug log and too verbose, so return stdoutStr - logrus.Debug(err) - return errors.New("rootlessport " + strings.TrimSuffix(stdoutStr, "\n")) - } - return err - } - logrus.Debug("rootlessport is ready") - return nil -} - -func setupRootlessPortMappingViaSlirp(ports []types.PortMapping, 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 := util.WaitForFile(apiSocket, chWait, pidWaitTimeout); err != nil { - return fmt.Errorf("waiting for slirp4nets to create the api socket file %s: %w", apiSocket, err) - } - - // 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 _, port := range ports { - for protocol := range strings.SplitSeq(port.Protocol, ",") { - hostIP := port.HostIP - if hostIP == "" { - hostIP = "0.0.0.0" - } - for i := range port.Range { - if err := openSlirp4netnsPort(apiSocket, protocol, hostIP, port.HostPort+i, port.ContainerPort+i); err != nil { - return err - } - } - } - } - logrus.Debug("slirp4netns port-forwarding setup via add_hostfwd is ready") - return nil -} - -// openSlirp4netnsPort sends the slirp4netns pai quey to the given socket. -func openSlirp4netnsPort(apiSocket, proto, hostip string, hostport, guestport uint16) error { - conn, err := net.Dial("unix", apiSocket) - if err != nil { - return fmt.Errorf("cannot open connection to %s: %w", apiSocket, err) - } - defer func() { - if err := conn.Close(); err != nil { - logrus.Errorf("Unable to close slirp4netns connection: %q", err) - } - }() - apiCmd := slirp4netnsCmd{ - Execute: "add_hostfwd", - Args: slirp4netnsCmdArg{ - Proto: proto, - HostAddr: hostip, - HostPort: hostport, - GuestPort: guestport, - }, - } - // 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 fmt.Errorf("cannot marshal JSON for slirp4netns: %w", err) - } - if _, err := fmt.Fprintf(conn, "%s\n", data); err != nil { - return fmt.Errorf("cannot write to control socket %s: %w", apiSocket, err) - } - //nolint:errcheck // This cast should never fail, if it does we get a interface - // conversion panic and a stack trace on how we ended up here which is more - // valuable than returning a human friendly error test as we don't know how it - // happened. - if err := conn.(*net.UnixConn).CloseWrite(); err != nil { - return fmt.Errorf("cannot shutdown the socket %s: %w", apiSocket, err) - } - buf := make([]byte, 2048) - readLength, err := conn.Read(buf) - if err != nil { - return fmt.Errorf("cannot read from control socket %s: %w", apiSocket, err) - } - // if there is no 'error' key in the received JSON data, then the operation was - // successful. - var y map[string]any - if err := json.Unmarshal(buf[0:readLength], &y); err != nil { - return fmt.Errorf("parsing error status from slirp4netns: %w", err) - } - if e, found := y["error"]; found { - return fmt.Errorf("from slirp4netns while setting up port redirection: %v", e) - } - return nil -} - -func GetRootlessPortChildIP(slirpSubnet *net.IPNet, netStatus map[string]types.StatusBlock) string { - if slirpSubnet != nil { - slirp4netnsIP, err := GetIP(slirpSubnet) - if err != nil { - return "" - } - return slirp4netnsIP.String() - } - - var ipv6 net.IP - for _, status := range netStatus { - for _, netInt := range status.Interfaces { - for _, netAddress := range netInt.Subnets { - ipv4 := netAddress.IPNet.IP.To4() - if ipv4 != nil { - return ipv4.String() - } - ipv6 = netAddress.IPNet.IP - } - } - } - if ipv6 != nil { - return ipv6.String() - } - return "" -} - -// closeQuiet closes a file and logs any error. Should only be used within -// a defer. -func closeQuiet(f *os.File) { - if err := f.Close(); err != nil { - logrus.Errorf("Unable to close file %s: %q", f.Name(), err) - } -} diff --git a/common/pkg/config/config.go b/common/pkg/config/config.go index 8bc23deba1..a4f2fd2342 100644 --- a/common/pkg/config/config.go +++ b/common/pkg/config/config.go @@ -403,13 +403,6 @@ type EngineConfig struct { // containers and pods will be visible. The default namespace is "". Namespace string `toml:"namespace,omitempty"` - // NetworkCmdPath is the path to the slirp4netns binary. - NetworkCmdPath string `toml:"network_cmd_path,omitempty"` - - // NetworkCmdOptions is the default options to pass to the slirp4netns binary. - // For example "allow_host_loopback=true" - NetworkCmdOptions attributedstring.Slice `toml:"network_cmd_options,omitempty"` - // NoPivotRoot sets whether to set no-pivot-root in the OCI runtime. NoPivotRoot bool `toml:"no_pivot_root,omitempty"` @@ -620,7 +613,7 @@ type NetworkConfig struct { DefaultSubnetPools []SubnetPool `toml:"default_subnet_pools,omitempty"` // DefaultRootlessNetworkCmd is used to set the default rootless network - // program, either "slirp4nents" (default) or "pasta". + // program, currently only "pasta". DefaultRootlessNetworkCmd string `toml:"default_rootless_network_cmd,omitempty"` // NetworkConfigDir is where network configuration files are stored. diff --git a/common/pkg/config/config_local_test.go b/common/pkg/config/config_local_test.go index 81c37d6858..1f5b8f5edb 100644 --- a/common/pkg/config/config_local_test.go +++ b/common/pkg/config/config_local_test.go @@ -147,11 +147,6 @@ var _ = Describe("Config Local", func() { config, err := newLocked(&Options{}, &paths{}) gomega.Expect(err).ToNot(gomega.HaveOccurred()) gomega.Expect(config.Network.DefaultRootlessNetworkCmd).To(gomega.Equal("pasta")) - // When - config2, err := newLocked(&Options{}, &paths{etc: "testdata/containers_default.conf"}) - // Then - gomega.Expect(err).ToNot(gomega.HaveOccurred()) - gomega.Expect(config2.Network.DefaultRootlessNetworkCmd).To(gomega.Equal("slirp4netns")) }) It("should fail on invalid device mode", func() { diff --git a/common/pkg/config/config_test.go b/common/pkg/config/config_test.go index ed8d753d8b..fb66b90fc7 100644 --- a/common/pkg/config/config_test.go +++ b/common/pkg/config/config_test.go @@ -328,7 +328,6 @@ image_copy_tmp_dir="storage"` gomega.Expect(defaultConfig.Engine.OCIRuntimes).To(gomega.Equal(OCIRuntimeMap)) gomega.Expect(defaultConfig.Engine.PlatformToOCIRuntime).To(gomega.Equal(PlatformToOCIRuntimeMap)) gomega.Expect(defaultConfig.Containers.HTTPProxy).To(gomega.BeFalse()) - gomega.Expect(defaultConfig.Engine.NetworkCmdOptions.Get()).To(gomega.BeEmpty()) gomega.Expect(defaultConfig.Engine.HelperBinariesDir.Get()).To(gomega.Equal(helperDirs)) gomega.Expect(defaultConfig.Engine.ServiceTimeout).To(gomega.BeEquivalentTo(300)) gomega.Expect(defaultConfig.Engine.InfraImage).To(gomega.BeEquivalentTo("registry.k8s.io/pause:3.4.1")) diff --git a/common/pkg/config/containers.conf b/common/pkg/config/containers.conf index 2e392d048e..21395abab0 100644 --- a/common/pkg/config/containers.conf +++ b/common/pkg/config/containers.conf @@ -216,12 +216,12 @@ default_sysctls = [ # #log_driver = "k8s-file" -# Default path for container logs to be stored in. When empty, logs will be stored +# Default path for container logs to be stored in. When empty, logs will be stored # in the container's default storage and removed when the container is removed. -# A subdirectory named with the container ID will be created under the specified +# A subdirectory named with the container ID will be created under the specified # path, and the log file will have the default name `ctr.log` within that directory. # This option can be overridden by the `--log-opt` flag. -# +# #log_path = "" # Maximum size allowed for the container log file. Negative numbers indicate @@ -420,8 +420,8 @@ default_sysctls = [ -# Configure which rootless network program to use by default. Valid options are -# `slirp4netns` and `pasta` (default). +# Configure which rootless network program to use by default. Only current valid option is +# `pasta` (default). # #default_rootless_network_cmd = "pasta" @@ -666,32 +666,6 @@ default_sysctls = [ # #namespace = "" -# Path to the slirp4netns binary -# -#network_cmd_path = "" - -# Default options to pass to the slirp4netns binary. -# Valid options values are: -# -# - allow_host_loopback=true|false: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). -# Default is false. -# - mtu=MTU: Specify the MTU to use for this network. (Default is `65520`). -# - cidr=CIDR: Specify ip range to use for this network. (Default is `10.0.2.0/24`). -# - enable_ipv6=true|false: Enable IPv6. Default is true. (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. -# Note: Rootlesskit changes the source IP address of incoming packets to a IP address in the container -# network namespace, usually `10.0.2.100`. If your application requires the real source IP address, -# e.g. web server logs, use the slirp4netns port handler. The rootlesskit port handler is also used for -# rootless containers when connected to user-defined networks. -# - port_handler=slirp4netns: Use the slirp4netns port forwarding, it is slower than rootlesskit but -# preserves the correct source IP address. This port handler cannot be used for user-defined networks. -# -#network_cmd_options = [] - # Whether to use chroot instead of pivot_root in the runtime # #no_pivot_root = false diff --git a/common/pkg/config/containers.conf-freebsd b/common/pkg/config/containers.conf-freebsd index bd999c339c..73c312e06f 100644 --- a/common/pkg/config/containers.conf-freebsd +++ b/common/pkg/config/containers.conf-freebsd @@ -169,12 +169,12 @@ default_sysctls = [ # #log_driver = "k8s-file" -# Default path for container logs to be stored in. When empty, logs will be stored +# Default path for container logs to be stored in. When empty, logs will be stored # in the container's default storage and removed when the container is removed. -# A subdirectory named with the container ID will be created under the specified +# A subdirectory named with the container ID will be created under the specified # path, and the log file will have the default name `ctr.log` within that directory. # This option can be overridden by the `--log-opt` flag. -# +# #log_path = "" # Maximum size allowed for the container log file. Negative numbers indicate @@ -499,32 +499,6 @@ default_sysctls = [ # #namespace = "" -# Path to the slirp4netns binary -# -#network_cmd_path = "" - -# Default options to pass to the slirp4netns binary. -# Valid options values are: -# -# - allow_host_loopback=true|false: Allow the slirp4netns to reach the host loopback IP (`10.0.2.2`). -# Default is false. -# - mtu=MTU: Specify the MTU to use for this network. (Default is `65520`). -# - cidr=CIDR: Specify ip range to use for this network. (Default is `10.0.2.0/24`). -# - enable_ipv6=true|false: Enable IPv6. Default is true. (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. -# Note: Rootlesskit changes the source IP address of incoming packets to a IP address in the container -# network namespace, usually `10.0.2.100`. If your application requires the real source IP address, -# e.g. web server logs, use the slirp4netns port handler. The rootlesskit port handler is also used for -# rootless containers when connected to user-defined networks. -# - port_handler=slirp4netns: Use the slirp4netns port forwarding, it is slower than rootlesskit but -# preserves the correct source IP address. This port handler cannot be used for user-defined networks. -# -#network_cmd_options = [] - # Whether to use chroot instead of pivot_root in the runtime # #no_pivot_root = false diff --git a/common/pkg/config/testdata/containers_default.conf b/common/pkg/config/testdata/containers_default.conf index 28cfb55e53..afd1d8da3e 100644 --- a/common/pkg/config/testdata/containers_default.conf +++ b/common/pkg/config/testdata/containers_default.conf @@ -126,8 +126,6 @@ network_config_dir = "/etc/cni/net.d/" default_subnet_pools = [{"base" = "10.89.0.0/16", "size" = 24}, {"base" = "10.90.0.0/15", "size" = 24}] -default_rootless_network_cmd = "slirp4netns" - # firewall driver to be used by default firewall_driver = "none" diff --git a/common/pkg/ssh/types.go b/common/pkg/ssh/types.go index 3f59fee8c8..071579d006 100644 --- a/common/pkg/ssh/types.go +++ b/common/pkg/ssh/types.go @@ -126,7 +126,6 @@ type HostInfo struct { // ServiceIsRemote is true when the podman/libpod service is remote to the client ServiceIsRemote bool `json:"serviceIsRemote"` Security SecurityInfo `json:"security"` - Slirp4NetNS SlirpInfo `json:"slirp4netns,omitempty"` SwapFree int64 `json:"swapFree"` SwapTotal int64 `json:"swapTotal"` Uptime string `json:"uptime"` @@ -139,13 +138,6 @@ type RemoteSocket struct { Exists bool `json:"exists,omitempty"` } -// SlirpInfo describes the slirp executable that is being used. -type SlirpInfo struct { - Executable string `json:"executable"` - Package string `json:"package"` - Version string `json:"version"` -} - // IDMappings describe the GID and UID mappings. type IDMappings struct { GIDMap []idtools.IDMap `json:"gidmap"` diff --git a/common/pkg/systemd/systemd_linux.go b/common/pkg/systemd/systemd_linux.go index 1d839636aa..d7b14de633 100644 --- a/common/pkg/systemd/systemd_linux.go +++ b/common/pkg/systemd/systemd_linux.go @@ -58,9 +58,9 @@ func moveProcessToScope(pid int, slice, scope string) error { return err } -// MoveRootlessNetnsSlirpProcessToUserSlice moves the slirp4netns process for the rootless netns +// MoveRootlessNetnsProcessToUserSlice moves the slirp4netns process for the rootless netns // into a different scope so that systemd does not kill it with a container. -func MoveRootlessNetnsSlirpProcessToUserSlice(pid int) error { +func MoveRootlessNetnsProcessToUserSlice(pid int) error { randBytes := make([]byte, 4) _, err := rand.Read(randBytes) if err != nil { diff --git a/common/rpm/containers-common.spec b/common/rpm/containers-common.spec index d478ab7dee..8648f07d79 100644 --- a/common/rpm/containers-common.spec +++ b/common/rpm/containers-common.spec @@ -70,7 +70,6 @@ Recommends: composefs Recommends: crun Requires: (crun if fedora-release-identity-server) Requires: netavark >= %{netavark_epoch}:1.10.3-1 -Suggests: slirp4netns Recommends: qemu-user-static Requires: (qemu-user-static-aarch64 if fedora-release-identity-server) Requires: (qemu-user-static-arm if fedora-release-identity-server)