mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00

Some field names are confusing. Change them so that they make more sense to the reader. Since these fields are only in the main branch we can safely rename them without worrying about backwards compatibility. Note we have to change the field names in netavark too. Signed-off-by: Paul Holzinger <pholzing@redhat.com>
707 lines
23 KiB
Go
707 lines
23 KiB
Go
// +build linux
|
|
|
|
package libpod
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containernetworking/plugins/pkg/ns"
|
|
"github.com/containers/podman/v3/libpod/network/types"
|
|
"github.com/containers/podman/v3/pkg/errorhandling"
|
|
"github.com/containers/podman/v3/pkg/rootless"
|
|
"github.com/containers/podman/v3/pkg/rootlessport"
|
|
"github.com/containers/podman/v3/pkg/servicereaper"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
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 slirp4netnsNetworkOptions struct {
|
|
cidr string
|
|
disableHostLoopback bool
|
|
enableIPv6 bool
|
|
isSlirpHostForward bool
|
|
noPivotRoot bool
|
|
mtu int
|
|
outboundAddr string
|
|
outboundAddr6 string
|
|
}
|
|
|
|
const ipv6ConfDefaultAcceptDadSysctl = "/proc/sys/net/ipv6/conf/default/accept_dad"
|
|
|
|
func checkSlirpFlags(path string) (*slirpFeatures, error) {
|
|
cmd := exec.Command(path, "--help")
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "slirp4netns %q", out)
|
|
}
|
|
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 parseSlirp4netnsNetworkOptions(r *Runtime, extraOptions []string) (*slirp4netnsNetworkOptions, error) {
|
|
slirpOptions := append(r.config.Engine.NetworkCmdOptions, extraOptions...)
|
|
slirp4netnsOpts := &slirp4netnsNetworkOptions{
|
|
// overwrite defaults
|
|
disableHostLoopback: true,
|
|
mtu: slirp4netnsMTU,
|
|
noPivotRoot: r.config.Engine.NoPivotRoot,
|
|
}
|
|
for _, o := range slirpOptions {
|
|
parts := strings.SplitN(o, "=", 2)
|
|
if len(parts) < 2 {
|
|
return nil, errors.Errorf("unknown option for slirp4netns: %q", o)
|
|
}
|
|
option, value := parts[0], parts[1]
|
|
switch option {
|
|
case "cidr":
|
|
ipv4, _, err := net.ParseCIDR(value)
|
|
if err != nil || ipv4.To4() == nil {
|
|
return nil, errors.Errorf("invalid cidr %q", value)
|
|
}
|
|
slirp4netnsOpts.cidr = value
|
|
case "port_handler":
|
|
switch value {
|
|
case "slirp4netns":
|
|
slirp4netnsOpts.isSlirpHostForward = true
|
|
case "rootlesskit":
|
|
slirp4netnsOpts.isSlirpHostForward = false
|
|
default:
|
|
return nil, errors.Errorf("unknown port_handler for slirp4netns: %q", value)
|
|
}
|
|
case "allow_host_loopback":
|
|
switch value {
|
|
case "true":
|
|
slirp4netnsOpts.disableHostLoopback = false
|
|
case "false":
|
|
slirp4netnsOpts.disableHostLoopback = true
|
|
default:
|
|
return nil, errors.Errorf("invalid value of allow_host_loopback for slirp4netns: %q", value)
|
|
}
|
|
case "enable_ipv6":
|
|
switch value {
|
|
case "true":
|
|
slirp4netnsOpts.enableIPv6 = true
|
|
case "false":
|
|
slirp4netnsOpts.enableIPv6 = false
|
|
default:
|
|
return nil, 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 nil, errors.Errorf("invalid outbound_addr %q", value)
|
|
}
|
|
}
|
|
slirp4netnsOpts.outboundAddr = value
|
|
case "outbound_addr6":
|
|
ipv6 := net.ParseIP(value)
|
|
if ipv6 == nil || ipv6.To4() != nil {
|
|
_, err := net.InterfaceByName(value)
|
|
if err != nil {
|
|
return nil, errors.Errorf("invalid outbound_addr6: %q", value)
|
|
}
|
|
}
|
|
slirp4netnsOpts.outboundAddr6 = value
|
|
case "mtu":
|
|
var err error
|
|
slirp4netnsOpts.mtu, err = strconv.Atoi(value)
|
|
if slirp4netnsOpts.mtu < 68 || err != nil {
|
|
return nil, errors.Errorf("invalid mtu %q", value)
|
|
}
|
|
default:
|
|
return nil, errors.Errorf("unknown option for slirp4netns: %q", o)
|
|
}
|
|
}
|
|
return slirp4netnsOpts, nil
|
|
}
|
|
|
|
func createBasicSlirp4netnsCmdArgs(options *slirp4netnsNetworkOptions, 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, fmt.Sprintf("--mtu=%d", 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.Errorf("cidr not supported")
|
|
}
|
|
cmdArgs = append(cmdArgs, fmt.Sprintf("--cidr=%s", options.cidr))
|
|
}
|
|
|
|
if options.enableIPv6 {
|
|
if !features.HasIPv6 {
|
|
return nil, errors.Errorf("enable_ipv6 not supported")
|
|
}
|
|
cmdArgs = append(cmdArgs, "--enable-ipv6")
|
|
}
|
|
|
|
if options.outboundAddr != "" {
|
|
if !features.HasOutboundAddr {
|
|
return nil, errors.Errorf("outbound_addr not supported")
|
|
}
|
|
cmdArgs = append(cmdArgs, fmt.Sprintf("--outbound-addr=%s", options.outboundAddr))
|
|
}
|
|
|
|
if options.outboundAddr6 != "" {
|
|
if !features.HasOutboundAddr || !features.HasIPv6 {
|
|
return nil, errors.Errorf("outbound_addr6 not supported")
|
|
}
|
|
if !options.enableIPv6 {
|
|
return nil, errors.Errorf("enable_ipv6=true is required for outbound_addr6")
|
|
}
|
|
cmdArgs = append(cmdArgs, fmt.Sprintf("--outbound-addr6=%s", options.outboundAddr6))
|
|
}
|
|
|
|
return cmdArgs, nil
|
|
}
|
|
|
|
// setupSlirp4netns can be called in rootful as well as in rootless
|
|
func (r *Runtime) setupSlirp4netns(ctr *Container, netns ns.NetNS) error {
|
|
path := r.config.Engine.NetworkCmdPath
|
|
if path == "" {
|
|
var err error
|
|
path, err = exec.LookPath("slirp4netns")
|
|
if err != nil {
|
|
logrus.Errorf("Could not find slirp4netns, the network namespace won't be configured: %v", err)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
syncR, syncW, err := os.Pipe()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to open pipe")
|
|
}
|
|
defer errorhandling.CloseQuiet(syncR)
|
|
defer errorhandling.CloseQuiet(syncW)
|
|
|
|
havePortMapping := len(ctr.config.PortMappings) > 0
|
|
logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("slirp4netns-%s.log", ctr.config.ID))
|
|
|
|
ctrNetworkSlipOpts := []string{}
|
|
if ctr.config.NetworkOptions != nil {
|
|
ctrNetworkSlipOpts = append(ctrNetworkSlipOpts, ctr.config.NetworkOptions["slirp4netns"]...)
|
|
}
|
|
netOptions, err := parseSlirp4netnsNetworkOptions(r, ctrNetworkSlipOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
slirpFeatures, err := checkSlirpFlags(path)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error checking slirp4netns binary %s: %q", path, err)
|
|
}
|
|
cmdArgs, err := createBasicSlirp4netnsCmdArgs(netOptions, slirpFeatures)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 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
|
|
// -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", "-e", "3", "-r", "4")
|
|
|
|
var apiSocket string
|
|
if havePortMapping && netOptions.isSlirpHostForward {
|
|
apiSocket = filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("%s.net", ctr.config.ID))
|
|
cmdArgs = append(cmdArgs, "--api-socket", apiSocket)
|
|
}
|
|
netnsPath := ""
|
|
if !ctr.config.PostConfigureNetNS {
|
|
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to create rootless network sync pipe")
|
|
}
|
|
netnsPath = netns.Path()
|
|
cmdArgs = append(cmdArgs, "--netns-type=path", netnsPath, "tap0")
|
|
} else {
|
|
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR)
|
|
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW)
|
|
netnsPath = fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID)
|
|
// we don't use --netns-path here (unavailable for slirp4netns < v0.4)
|
|
cmdArgs = append(cmdArgs, fmt.Sprintf("%d", ctr.state.PID), "tap0")
|
|
}
|
|
|
|
cmd := exec.Command(path, cmdArgs...)
|
|
logrus.Debugf("slirp4netns command: %s", strings.Join(cmd.Args, " "))
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Setpgid: true,
|
|
}
|
|
|
|
// 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, ctr.rootlessSlirpSyncR, syncW)
|
|
|
|
logFile, err := os.Create(logPath)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to open slirp4netns log file %s", logPath)
|
|
}
|
|
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 errors.Wrapf(err, "delete file %s", logPath)
|
|
}
|
|
cmd.Stdout = logFile
|
|
cmd.Stderr = logFile
|
|
|
|
var slirpReadyChan (chan struct{})
|
|
|
|
if netOptions.enableIPv6 {
|
|
slirpReadyChan = make(chan struct{})
|
|
defer close(slirpReadyChan)
|
|
go func() {
|
|
err := ns.WithNetNSPath(netnsPath, func(_ ns.NetNS) error {
|
|
// Duplicate Address Detection slows the ipv6 setup down for 1-2 seconds.
|
|
// Since slirp4netns is run it is 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 := ioutil.ReadFile(ipv6ConfDefaultAcceptDadSysctl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, []byte("0"), 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// wait for slirp to finish setup
|
|
<-slirpReadyChan
|
|
return ioutil.WriteFile(ipv6ConfDefaultAcceptDadSysctl, orgValue, 0644)
|
|
})
|
|
if err != nil {
|
|
logrus.Warnf("failed to set net.ipv6.conf.default.accept_dad sysctl: %v", err)
|
|
}
|
|
}()
|
|
}
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
return errors.Wrapf(err, "failed to start slirp4netns process")
|
|
}
|
|
defer func() {
|
|
servicereaper.AddPID(cmd.Process.Pid)
|
|
if err := cmd.Process.Release(); err != nil {
|
|
logrus.Errorf("Unable to release command process: %q", err)
|
|
}
|
|
}()
|
|
|
|
if err := waitForSync(syncR, cmd, logFile, 1*time.Second); err != nil {
|
|
return err
|
|
}
|
|
if slirpReadyChan != nil {
|
|
slirpReadyChan <- struct{}{}
|
|
}
|
|
|
|
// Set a default slirp subnet. Parsing a string with the net helper is easier than building the struct myself
|
|
_, ctr.slirp4netnsSubnet, _ = net.ParseCIDR(defaultSlirp4netnsSubnet)
|
|
|
|
// 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 errors.Errorf("invalid cidr %q", netOptions.cidr)
|
|
}
|
|
ctr.slirp4netnsSubnet = ipv4network
|
|
}
|
|
|
|
if havePortMapping {
|
|
if netOptions.isSlirpHostForward {
|
|
return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket)
|
|
}
|
|
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath, nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get expected 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 GetSlirp4netnsIP(subnet *net.IPNet) (*net.IP, error) {
|
|
_, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet)
|
|
if subnet != nil {
|
|
slirpSubnet = subnet
|
|
}
|
|
expectedIP, err := addToIP(slirpSubnet, uint32(100))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error calculating expected ip for slirp4netns")
|
|
}
|
|
return expectedIP, nil
|
|
}
|
|
|
|
// Get expected slirp Gateway ipv4 address based on subnet
|
|
// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description
|
|
func GetSlirp4netnsGateway(subnet *net.IPNet) (*net.IP, error) {
|
|
_, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet)
|
|
if subnet != nil {
|
|
slirpSubnet = subnet
|
|
}
|
|
expectedGatewayIP, err := addToIP(slirpSubnet, uint32(2))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error calculating expected gateway ip for slirp4netns")
|
|
}
|
|
return expectedGatewayIP, nil
|
|
}
|
|
|
|
// Get expected slirp DNS ipv4 address based on subnet
|
|
// Reference: https://github.com/rootless-containers/slirp4netns/blob/master/slirp4netns.1.md#description
|
|
func GetSlirp4netnsDNS(subnet *net.IPNet) (*net.IP, error) {
|
|
_, slirpSubnet, _ := net.ParseCIDR(defaultSlirp4netnsSubnet)
|
|
if subnet != nil {
|
|
slirpSubnet = subnet
|
|
}
|
|
expectedDNSIP, err := addToIP(slirpSubnet, uint32(3))
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error calculating expected dns ip for slirp4netns")
|
|
}
|
|
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, errors.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, errors.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 errors.Wrapf(err, "error setting %s pipe timeout", prog)
|
|
}
|
|
// FIXME: return err as soon as proc exits, without waiting for timeout
|
|
if _, err := syncR.Read(b); err == nil {
|
|
break
|
|
} else {
|
|
if os.IsTimeout(err) {
|
|
// 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 errors.Wrapf(err, "failed to read %s process status", prog)
|
|
}
|
|
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 := ioutil.ReadAll(logFile)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "%s failed", prog)
|
|
}
|
|
return errors.Errorf("%s failed: %q", prog, logContent)
|
|
}
|
|
if status.Signaled() {
|
|
return errors.Errorf("%s killed by signal", prog)
|
|
}
|
|
continue
|
|
}
|
|
return errors.Wrapf(err, "failed to read from %s sync pipe", prog)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Runtime) setupRootlessPortMappingViaRLK(ctr *Container, netnsPath string, netStatus map[string]types.StatusBlock) error {
|
|
syncR, syncW, err := os.Pipe()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to open pipe")
|
|
}
|
|
defer errorhandling.CloseQuiet(syncR)
|
|
defer errorhandling.CloseQuiet(syncW)
|
|
|
|
logPath := filepath.Join(ctr.runtime.config.Engine.TmpDir, fmt.Sprintf("rootlessport-%s.log", ctr.config.ID))
|
|
logFile, err := os.Create(logPath)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to open rootlessport log file %s", logPath)
|
|
}
|
|
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 errors.Wrapf(err, "delete file %s", logPath)
|
|
}
|
|
|
|
if !ctr.config.PostConfigureNetNS {
|
|
ctr.rootlessPortSyncR, ctr.rootlessPortSyncW, err = os.Pipe()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to create rootless port sync pipe")
|
|
}
|
|
}
|
|
|
|
childIP := getRootlessPortChildIP(ctr, netStatus)
|
|
cfg := rootlessport.Config{
|
|
Mappings: ctr.convertPortMappings(),
|
|
NetNSPath: netnsPath,
|
|
ExitFD: 3,
|
|
ReadyFD: 4,
|
|
TmpDir: ctr.runtime.config.Engine.TmpDir,
|
|
ChildIP: childIP,
|
|
ContainerID: ctr.config.ID,
|
|
RootlessCNI: ctr.config.NetMode.IsBridge() && rootless.IsRootless(),
|
|
}
|
|
cfgJSON, err := json.Marshal(cfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfgR := bytes.NewReader(cfgJSON)
|
|
var stdout bytes.Buffer
|
|
path, err := r.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
|
|
defer errorhandling.CloseQuiet(ctr.rootlessPortSyncR)
|
|
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessPortSyncR, 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 errors.Wrapf(err, "failed to start rootlessport process")
|
|
}
|
|
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.Errorf("rootlessport " + strings.TrimSuffix(stdoutStr, "\n"))
|
|
}
|
|
return err
|
|
}
|
|
logrus.Debug("rootlessport is ready")
|
|
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.convertPortMappings() {
|
|
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
|
|
}
|
|
|
|
func getRootlessPortChildIP(c *Container, netStatus map[string]types.StatusBlock) string {
|
|
if c.config.NetMode.IsSlirp4netns() {
|
|
slirp4netnsIP, err := GetSlirp4netnsIP(c.slirp4netnsSubnet)
|
|
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 ""
|
|
}
|
|
|
|
// reloadRootlessRLKPortMapping will trigger a reload for the port mappings in the rootlessport process.
|
|
// This should only be called by network connect/disconnect and only as rootless.
|
|
func (c *Container) reloadRootlessRLKPortMapping() error {
|
|
if len(c.config.PortMappings) == 0 {
|
|
return nil
|
|
}
|
|
childIP := getRootlessPortChildIP(c, c.state.NetworkStatus)
|
|
logrus.Debugf("reloading rootless ports for container %s, childIP is %s", c.config.ID, childIP)
|
|
|
|
conn, err := openUnixSocket(filepath.Join(c.runtime.config.Engine.TmpDir, "rp", c.config.ID))
|
|
if err != nil {
|
|
return errors.Wrap(err, "could not reload rootless port mappings, port forwarding may no longer work correctly")
|
|
}
|
|
defer conn.Close()
|
|
enc := json.NewEncoder(conn)
|
|
err = enc.Encode(childIP)
|
|
if err != nil {
|
|
return errors.Wrap(err, "port reloading failed")
|
|
}
|
|
b, err := ioutil.ReadAll(conn)
|
|
if err != nil {
|
|
return errors.Wrap(err, "port reloading failed")
|
|
}
|
|
data := string(b)
|
|
if data != "OK" {
|
|
return errors.Errorf("port reloading failed: %s", data)
|
|
}
|
|
return nil
|
|
}
|