Merge pull request #9423 from Luap99/rootless-cni-no-infra

rootless cni without infra container
This commit is contained in:
OpenShift Merge Robot
2021-04-05 17:12:14 +02:00
committed by GitHub
24 changed files with 1090 additions and 1238 deletions

View File

@ -440,7 +440,7 @@ apiv2_test_task:
time_script: '$SCRIPT_BASE/logcollector.sh time'
compose_test_task:
name: "compose test on $DISTRO_NV"
name: "compose test on $DISTRO_NV ($PRIV_NAME)"
alias: compose_test
only_if: *not_docs
skip: *tags
@ -450,6 +450,11 @@ compose_test_task:
env:
<<: *stdenvars
TEST_FLAVOR: compose
matrix:
- env:
PRIV_NAME: root
- env:
PRIV_NAME: rootless
clone_script: *noop # Comes from cache
gopath_cache: *ro_gopath_cache
setup_script: *setup

View File

@ -288,18 +288,6 @@ dotest() {
exec_container # does not return
fi;
# shellcheck disable=SC2154
if [[ "$PRIV_NAME" == "rootless" ]] && [[ "$UID" -eq 0 ]]; then
req_env_vars ROOTLESS_USER
msg "Re-executing runner through ssh as user '$ROOTLESS_USER'"
msg "************************************************************"
set -x
exec ssh $ROOTLESS_USER@localhost \
-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
-o CheckHostIP=no $GOSRC/$SCRIPT_BASE/runner.sh
# does not return
fi
# containers/automation sets this to 0 for its dbg() function
# but the e2e integration tests are also sensitive to it.
unset DEBUG
@ -340,6 +328,19 @@ msg "************************************************************"
((${SETUP_ENVIRONMENT:-0})) || \
die "Expecting setup_environment.sh to have completed successfully"
# shellcheck disable=SC2154
if [[ "$PRIV_NAME" == "rootless" ]] && [[ "$UID" -eq 0 ]]; then
req_env_vars ROOTLESS_USER
msg "Re-executing runner through ssh as user '$ROOTLESS_USER'"
msg "************************************************************"
set -x
exec ssh $ROOTLESS_USER@localhost \
-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no \
-o CheckHostIP=no $GOSRC/$SCRIPT_BASE/runner.sh
# Does not return!
fi
# else: not running rootless, do nothing special
cd "${GOSRC}/"
handler="_run_${TEST_FLAVOR}"

View File

@ -1,36 +0,0 @@
ARG GOLANG_VERSION=1.15
ARG ALPINE_VERSION=3.12
ARG CNI_VERSION=v0.8.0
ARG CNI_PLUGINS_VERSION=v0.8.7
ARG DNSNAME_VERSION=v1.1.1
FROM golang:${GOLANG_VERSION}-alpine${ALPINE_VERSION} AS golang-base
RUN apk add --no-cache git
FROM golang-base AS cnitool
RUN git clone https://github.com/containernetworking/cni /go/src/github.com/containernetworking/cni
WORKDIR /go/src/github.com/containernetworking/cni
ARG CNI_VERSION
RUN git checkout ${CNI_VERSION}
RUN go build -o /cnitool ./cnitool
FROM golang-base AS dnsname
RUN git clone https://github.com/containers/dnsname /go/src/github.com/containers/dnsname
WORKDIR /go/src/github.com/containers/dnsname
ARG DNSNAME_VERSION
RUN git checkout ${DNSNAME_VERSION}
RUN go build -o /dnsname ./plugins/meta/dnsname
FROM alpine:${ALPINE_VERSION}
RUN apk add --no-cache curl dnsmasq iptables ip6tables iproute2
ARG TARGETARCH
ARG CNI_PLUGINS_VERSION
RUN mkdir -p /opt/cni/bin && \
curl -fsSL https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/cni-plugins-linux-${TARGETARCH}-${CNI_PLUGINS_VERSION}.tgz | tar xz -C /opt/cni/bin
COPY --from=cnitool /cnitool /usr/local/bin
COPY --from=dnsname /dnsname /opt/cni/bin
COPY rootless-cni-infra /usr/local/bin
ENV CNI_PATH=/opt/cni/bin
CMD ["sleep", "infinity"]
ENV ROOTLESS_CNI_INFRA_VERSION=5

View File

@ -1,25 +0,0 @@
# rootless-cni-infra
Infra container for CNI-in-slirp4netns.
## How it works
When a CNI network is specified for `podman run` in rootless mode, Podman launches the `rootless-cni-infra` container to execute CNI plugins inside slirp4netns.
The infra container is created per user, by executing an equivalent of:
`podman run -d --name rootless-cni-infra --pid=host --privileged -v $HOME/.config/cni/net.d:/etc/cni/net.d rootless-cni-infra`.
The infra container is automatically deleted when no CNI network is in use.
Podman then allocates a CNI netns in the infra container, by executing an equivalent of:
`podman exec rootless-cni-infra rootless-cni-infra alloc $CONTAINER_ID $NETWORK_NAME $POD_NAME`.
The allocated netns is deallocated when the container is being removed, by executing an equivalent of:
`podman exec rootless-cni-infra rootless-cni-infra dealloc $CONTAINER_ID $NETWORK_NAME`.
The container images live on `quay.io/libpod/rootless-cni-infra`. The tags have the format `$version-$architecture`. Please make sure to increase the version number in the Containerfile (i.e., `ROOTLESS_CNI_INFRA_VERSION`) when applying changes to this directory. After committing the changes, upload the image(s) with the corresponding tag.
## Directory layout
* `/run/rootless-cni-infra/${CONTAINER_ID}/pid`: PID of the `sleep infinity` process that corresponds to the allocated netns
* `/run/rootless-cni-infra/${CONTAINER_ID}/attached/${NETWORK_NAME}`: CNI result
* `/run/rootless-cni-infra/${CONTAINER_ID}/attached-args/${NETWORK_NAME}`: CNI args

View File

@ -1,181 +0,0 @@
#!/bin/sh
set -eu
ARG0="$0"
BASE="/run/rootless-cni-infra"
wait_unshare_net() {
pid="$1"
# NOTE: busybox shell doesn't support the `for ((i=0; i < $MAX; i++)); do foo; done` statement
i=0
while :; do
if [ "$(readlink /proc/self/ns/net)" != "$(readlink /proc/${pid}/ns/net)" ]; then
break
fi
sleep 0.1
if [ $i -ge 10 ]; then
echo >&2 "/proc/${pid}/ns/net cannot be unshared"
exit 1
fi
i=$((i + 1))
done
}
# CLI subcommand: "alloc $CONTAINER_ID $NETWORK_NAME $POD_NAME $IP $MAC $CAP_ARGS"
cmd_entrypoint_alloc() {
if [ "$#" -ne 6 ]; then
echo >&2 "Usage: $ARG0 alloc CONTAINER_ID NETWORK_NAME POD_NAME IP MAC CAP_ARGS"
exit 1
fi
ID="$1"
NET="$2"
K8S_POD_NAME="$3"
IP="$4"
MAC="$5"
CAP_ARGS="$6"
dir="${BASE}/${ID}"
mkdir -p "${dir}/attached" "${dir}/attached-args"
pid=""
if [ -f "${dir}/pid" ]; then
pid=$(cat "${dir}/pid")
else
unshare -n sleep infinity &
pid="$!"
wait_unshare_net "${pid}"
echo "${pid}" >"${dir}/pid"
nsenter -t "${pid}" -n ip link set lo up
fi
CNI_ARGS="IgnoreUnknown=1;K8S_POD_NAME=${K8S_POD_NAME}"
if [ "$IP" ]; then
CNI_ARGS="$CNI_ARGS;IP=${IP}"
fi
if [ "$MAC" ]; then
CNI_ARGS="$CNI_ARGS;MAC=${MAC}"
fi
if [ "$CAP_ARGS" ]; then
CAP_ARGS="$CAP_ARGS"
fi
nwcount=$(find "${dir}/attached" -type f | wc -l)
CNI_IFNAME="eth${nwcount}"
export CNI_ARGS CNI_IFNAME CAP_ARGS
cnitool add "${NET}" "/proc/${pid}/ns/net" >"${dir}/attached/${NET}"
echo "${CNI_ARGS}" >"${dir}/attached-args/${NET}"
# return the result
ns="/proc/${pid}/ns/net"
echo "{\"ns\":\"${ns}\"}"
}
# CLI subcommand: "dealloc $CONTAINER_ID $NETWORK_NAME"
cmd_entrypoint_dealloc() {
if [ "$#" -ne 2 ]; then
echo >&2 "Usage: $ARG0 dealloc CONTAINER_ID NETWORK_NAME"
exit 1
fi
ID=$1
NET=$2
dir="${BASE}/${ID}"
if [ ! -f "${dir}/pid" ]; then
exit 0
fi
pid=$(cat "${dir}/pid")
if [ -f "${dir}/attached-args/${NET}" ]; then
CNI_ARGS=$(cat "${dir}/attached-args/${NET}")
export CNI_ARGS
fi
cnitool del "${NET}" "/proc/${pid}/ns/net"
rm -f "${dir}/attached/${NET}" "${dir}/attached-args/${NET}"
nwcount=$(find "${dir}/attached" -type f | wc -l)
if [ "${nwcount}" = 0 ]; then
kill -9 "${pid}"
rm -rf "${dir}"
fi
# return empty json
echo "{}"
}
# CLI subcommand: "is-idle"
cmd_entrypoint_is_idle() {
if [ ! -d ${BASE} ]; then
echo '{"idle": true}'
elif [ -z "$(ls -1 ${BASE})" ]; then
echo '{"idle": true}'
else
echo '{"idle": false}'
fi
}
# CLI subcommand: "print-cni-result $CONTAINER_ID $NETWORK_NAME"
cmd_entrypoint_print_cni_result() {
if [ "$#" -ne 2 ]; then
echo >&2 "Usage: $ARG0 print-cni-result CONTAINER_ID NETWORK_NAME"
exit 1
fi
ID=$1
NET=$2
# the result shall be CNI JSON
cat "${BASE}/${ID}/attached/${NET}"
}
# CLI subcommand: "print-netns-path $CONTAINER_ID"
cmd_entrypoint_print_netns_path() {
if [ "$#" -ne 1 ]; then
echo >&2 "Usage: $ARG0 print-netns-path CONTAINER_ID"
exit 1
fi
ID=$1
pid=$(cat "${BASE}/${ID}/pid")
path="/proc/${pid}/ns/net"
# return the result
echo "{\"path\":\"${path}\"}"
}
# CLI subcommand: "help"
cmd_entrypoint_help() {
echo "Usage: ${ARG0} COMMAND"
echo
echo "Rootless CNI Infra container"
echo
echo "Commands:"
echo " alloc Allocate a netns"
echo " dealloc Deallocate a netns"
echo " is-idle Print whether the infra container is idle"
echo " print-cni-result Print CNI result"
echo " print-netns-path Print netns path"
echo " help Print help"
echo " version Print version"
}
# CLI subcommand: "version"
cmd_entrypoint_version() {
echo "{\"version\": \"${ROOTLESS_CNI_INFRA_VERSION}\"}"
}
# parse args
command="${1:-}"
if [ -z "$command" ]; then
echo >&2 "No command was specified. Run \`${ARG0} help\` to see the usage."
exit 1
fi
command_func=$(echo "cmd_entrypoint_${command}" | sed -e "s/-/_/g")
if ! command -v "${command_func}" >/dev/null 2>&1; then
echo >&2 "Unknown command: ${command}. Run \`${ARG0} help\` to see the usage."
exit 1
fi
# start the command func
shift
"${command_func}" "$@"

View File

@ -966,9 +966,7 @@ func (c *Container) completeNetworkSetup() error {
if err := c.syncContainer(); err != nil {
return err
}
if rootless.IsRootless() {
return c.runtime.setupRootlessNetNS(c)
} else if c.config.NetMode.IsSlirp4netns() {
if c.config.NetMode.IsSlirp4netns() {
return c.runtime.setupSlirp4netns(c)
}
if err := c.runtime.setupNetNS(c); err != nil {

View File

@ -92,11 +92,7 @@ func (c *Container) prepare() error {
// Set up network namespace if not already set up
noNetNS := c.state.NetNS == nil
if c.config.CreateNetNS && noNetNS && !c.config.PostConfigureNetNS {
if rootless.IsRootless() && len(c.config.Networks) > 0 {
netNS, networkStatus, createNetNSErr = AllocRootlessCNI(context.Background(), c)
} else {
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
}
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
if createNetNSErr != nil {
return
}

View File

@ -11,7 +11,6 @@ import (
"github.com/containernetworking/cni/pkg/version"
"github.com/containers/common/pkg/config"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -223,9 +222,8 @@ func createBridge(name string, options entities.NetworkCreateOptions, runtimeCon
plugins = append(plugins, NewPortMapPlugin())
plugins = append(plugins, NewFirewallPlugin())
plugins = append(plugins, NewTuningPlugin())
// if we find the dnsname plugin or are rootless, we add configuration for it
// the rootless-cni-infra container has the dnsname plugin always installed
if (HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) || rootless.IsRootless()) && !options.DisableDNS {
// if we find the dnsname plugin we add configuration for it
if HasDNSNamePlugin(runtimeConfig.Network.CNIPluginDirs) && !options.DisableDNS {
if options.Internal {
logrus.Warnf("dnsname and --internal networks are incompatible. dnsname plugin not configured for network %s", name)
} else {

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,538 @@
// +build linux
package libpod
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/containers/podman/v3/pkg/errorhandling"
"github.com/containers/podman/v3/pkg/rootlessport"
"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 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"`
}
type slirp4netnsNetworkOptions struct {
cidr string
disableHostLoopback bool
enableIPv6 bool
isSlirpHostForward bool
noPivotRoot bool
mtu int
outboundAddr string
outboundAddr6 string
}
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) 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 = ctr.state.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
if err := cmd.Start(); err != nil {
return errors.Wrapf(err, "failed to start slirp4netns process")
}
defer func() {
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 havePortMapping {
if netOptions.isSlirpHostForward {
return r.setupRootlessPortMappingViaSlirp(ctr, cmd, apiSocket)
}
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
}
return 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) 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 := slirp4netnsIP
outer:
for _, r := range ctr.state.NetworkStatus {
for _, i := range r.IPs {
ipv4 := i.Address.IP.To4()
if ipv4 != nil {
childIP = ipv4.String()
break outer
}
}
}
cfg := rootlessport.Config{
Mappings: ctr.config.PortMappings,
NetNSPath: netnsPath,
ExitFD: 3,
ReadyFD: 4,
TmpDir: ctr.runtime.config.Engine.TmpDir,
ChildIP: childIP,
}
cfgJSON, err := json.Marshal(cfg)
if err != nil {
return err
}
cfgR := bytes.NewReader(cfgJSON)
var stdout bytes.Buffer
cmd := exec.Command(fmt.Sprintf("/proc/%d/exe", os.Getpid()))
cmd.Args = []string{rootlessport.ReexecKey}
// Leak one end of the pipe in rootlessport process, the other will be sent to conmon
if ctr.rootlessPortSyncR != nil {
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() {
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.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
}

View File

@ -1,372 +0,0 @@
// +build linux
package libpod
import (
"bytes"
"context"
"io"
"path/filepath"
"runtime"
cnitypes "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/image"
"github.com/containers/podman/v3/pkg/env"
"github.com/containers/podman/v3/pkg/util"
"github.com/containers/storage/pkg/lockfile"
"github.com/hashicorp/go-multierror"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Built from ../contrib/rootless-cni-infra.
var rootlessCNIInfraImage = map[string]string{
"amd64": "quay.io/libpod/rootless-cni-infra@sha256:adf352454666f7ce9ca3e1098448b5ee18f89c4516471ec99447ec9ece917f36", // 5-amd64
}
const (
rootlessCNIInfraContainerNamespace = "podman-system"
rootlessCNIInfraContainerName = "rootless-cni-infra"
)
// AllocRootlessCNI allocates a CNI netns inside the rootless CNI infra container.
// Locks "rootless-cni-infra.lck".
//
// When the infra container is not running, it is created.
//
// AllocRootlessCNI does not lock c. c should be already locked.
func AllocRootlessCNI(ctx context.Context, c *Container) (ns.NetNS, []*cnitypes.Result, error) {
networks, _, err := c.networks()
if err != nil {
return nil, nil, err
}
if len(networks) == 0 {
return nil, nil, errors.New("rootless CNI networking requires that the container has joined at least one CNI network")
}
l, err := getRootlessCNIInfraLock(c.runtime)
if err != nil {
return nil, nil, err
}
l.Lock()
defer l.Unlock()
infra, err := ensureRootlessCNIInfraContainerRunning(ctx, c.runtime)
if err != nil {
return nil, nil, err
}
k8sPodName := getCNIPodName(c) // passed to CNI as K8S_POD_NAME
ip := ""
if c.config.StaticIP != nil {
ip = c.config.StaticIP.String()
}
mac := ""
if c.config.StaticMAC != nil {
mac = c.config.StaticMAC.String()
}
aliases, err := c.runtime.state.GetAllNetworkAliases(c)
if err != nil {
return nil, nil, err
}
capArgs := ""
// add network aliases json encoded as capabilityArgs for cni
if len(aliases) > 0 {
capabilityArgs := make(map[string]interface{})
capabilityArgs["aliases"] = aliases
b, err := json.Marshal(capabilityArgs)
if err != nil {
return nil, nil, err
}
capArgs = string(b)
}
cniResults := make([]*cnitypes.Result, len(networks))
for i, nw := range networks {
cniRes, err := rootlessCNIInfraCallAlloc(infra, c.ID(), nw, k8sPodName, ip, mac, capArgs)
if err != nil {
return nil, nil, err
}
cniResults[i] = cniRes
}
nsObj, err := rootlessCNIInfraGetNS(infra, c.ID())
if err != nil {
return nil, nil, err
}
logrus.Debugf("rootless CNI: container %q will join %q", c.ID(), nsObj.Path())
return nsObj, cniResults, nil
}
// DeallocRootlessCNI deallocates a CNI netns inside the rootless CNI infra container.
// Locks "rootless-cni-infra.lck".
//
// When the infra container is no longer needed, it is removed.
//
// DeallocRootlessCNI does not lock c. c should be already locked.
func DeallocRootlessCNI(ctx context.Context, c *Container) error {
networks, _, err := c.networks()
if err != nil {
return err
}
if len(networks) == 0 {
return errors.New("rootless CNI networking requires that the container has joined at least one CNI network")
}
l, err := getRootlessCNIInfraLock(c.runtime)
if err != nil {
return err
}
l.Lock()
defer l.Unlock()
infra, _ := getRootlessCNIInfraContainer(c.runtime)
if infra == nil {
return nil
}
var errs *multierror.Error
for _, nw := range networks {
err := rootlessCNIInfraCallDealloc(infra, c.ID(), nw)
if err != nil {
errs = multierror.Append(errs, err)
}
}
if isIdle, err := rootlessCNIInfraIsIdle(infra); isIdle || err != nil {
if err != nil {
logrus.Warn(err)
}
logrus.Debugf("rootless CNI: removing infra container %q", infra.ID())
infra.lock.Lock()
defer infra.lock.Unlock()
if err := c.runtime.removeContainer(ctx, infra, true, false, true); err != nil {
return err
}
logrus.Debugf("rootless CNI: removed infra container %q", infra.ID())
}
return errs.ErrorOrNil()
}
func getRootlessCNIInfraLock(r *Runtime) (lockfile.Locker, error) {
fname := filepath.Join(r.config.Engine.TmpDir, "rootless-cni-infra.lck")
return lockfile.GetLockfile(fname)
}
// getCNIPodName return the pod name (hostname) used by CNI and the dnsname plugin.
// If we are in the pod network namespace use the pod name otherwise the container name
func getCNIPodName(c *Container) string {
if c.config.NetMode.IsPod() || c.IsInfra() {
pod, err := c.runtime.GetPod(c.PodID())
if err == nil {
return pod.Name()
}
}
return c.Name()
}
func rootlessCNIInfraCallAlloc(infra *Container, id, nw, k8sPodName, ip, mac, capArgs string) (*cnitypes.Result, error) {
logrus.Debugf("rootless CNI: alloc %q, %q, %q, %q, %q, %q", id, nw, k8sPodName, ip, mac, capArgs)
var err error
_, err = rootlessCNIInfraExec(infra, "alloc", id, nw, k8sPodName, ip, mac, capArgs)
if err != nil {
return nil, err
}
cniResStr, err := rootlessCNIInfraExec(infra, "print-cni-result", id, nw)
if err != nil {
return nil, err
}
var cniRes cnitypes.Result
if err := json.Unmarshal([]byte(cniResStr), &cniRes); err != nil {
return nil, errors.Wrapf(err, "unmarshaling as cnitypes.Result: %q", cniResStr)
}
return &cniRes, nil
}
func rootlessCNIInfraCallDealloc(infra *Container, id, nw string) error {
logrus.Debugf("rootless CNI: dealloc %q, %q", id, nw)
_, err := rootlessCNIInfraExec(infra, "dealloc", id, nw)
return err
}
func rootlessCNIInfraIsIdle(infra *Container) (bool, error) {
type isIdle struct {
Idle bool `json:"idle"`
}
resStr, err := rootlessCNIInfraExec(infra, "is-idle")
if err != nil {
return false, err
}
var res isIdle
if err := json.Unmarshal([]byte(resStr), &res); err != nil {
return false, errors.Wrapf(err, "unmarshaling as isIdle: %q", resStr)
}
return res.Idle, nil
}
func rootlessCNIInfraGetNS(infra *Container, id string) (ns.NetNS, error) {
type printNetnsPath struct {
Path string `json:"path"`
}
resStr, err := rootlessCNIInfraExec(infra, "print-netns-path", id)
if err != nil {
return nil, err
}
var res printNetnsPath
if err := json.Unmarshal([]byte(resStr), &res); err != nil {
return nil, errors.Wrapf(err, "unmarshaling as printNetnsPath: %q", resStr)
}
nsObj, err := ns.GetNS(res.Path)
if err != nil {
return nil, err
}
return nsObj, nil
}
func getRootlessCNIInfraContainer(r *Runtime) (*Container, error) {
containers, err := r.GetContainersWithoutLock(func(c *Container) bool {
return c.Namespace() == rootlessCNIInfraContainerNamespace &&
c.Name() == rootlessCNIInfraContainerName
})
if err != nil {
return nil, err
}
if len(containers) == 0 {
return nil, nil
}
return containers[0], nil
}
func ensureRootlessCNIInfraContainerRunning(ctx context.Context, r *Runtime) (*Container, error) {
c, err := getRootlessCNIInfraContainer(r)
if err != nil {
return nil, err
}
if c == nil {
return startRootlessCNIInfraContainer(ctx, r)
}
st, err := c.ContainerState()
if err != nil {
return nil, err
}
if st.State == define.ContainerStateRunning {
logrus.Debugf("rootless CNI: infra container %q is already running", c.ID())
return c, nil
}
logrus.Debugf("rootless CNI: infra container %q is %q, being started", c.ID(), st.State)
if err := c.initAndStart(ctx); err != nil {
return nil, err
}
logrus.Debugf("rootless CNI: infra container %q is running", c.ID())
return c, nil
}
func startRootlessCNIInfraContainer(ctx context.Context, r *Runtime) (*Container, error) {
imageName, ok := rootlessCNIInfraImage[runtime.GOARCH]
if !ok {
return nil, errors.Errorf("cannot find rootless-podman-network-sandbox image for %s", runtime.GOARCH)
}
logrus.Debugf("rootless CNI: ensuring image %q to exist", imageName)
newImage, err := r.ImageRuntime().New(ctx, imageName, "", "", nil, nil,
image.SigningOptions{}, nil, util.PullImageMissing, nil)
if err != nil {
return nil, err
}
logrus.Debugf("rootless CNI: image %q is ready", imageName)
g, err := generate.New("linux")
if err != nil {
return nil, err
}
g.SetupPrivileged(true)
// Set --pid=host for ease of propagating "/proc/PID/ns/net" string
if err := g.RemoveLinuxNamespace(string(spec.PIDNamespace)); err != nil {
return nil, err
}
g.RemoveMount("/proc")
procMount := spec.Mount{
Destination: "/proc",
Type: "bind",
Source: "/proc",
Options: []string{"rbind", "nosuid", "noexec", "nodev"},
}
g.AddMount(procMount)
// Mount CNI networks
etcCNINetD := spec.Mount{
Destination: "/etc/cni/net.d",
Type: "bind",
Source: r.config.Network.NetworkConfigDir,
Options: []string{"ro", "bind"},
}
g.AddMount(etcCNINetD)
inspectData, err := newImage.Inspect(ctx)
if err != nil {
return nil, err
}
imageEnv, err := env.ParseSlice(inspectData.Config.Env)
if err != nil {
return nil, err
}
for k, v := range imageEnv {
g.AddProcessEnv(k, v)
}
if len(inspectData.Config.Cmd) == 0 {
return nil, errors.Errorf("rootless CNI infra image %q has no command specified", imageName)
}
g.SetProcessArgs(inspectData.Config.Cmd)
var options []CtrCreateOption
options = append(options, WithRootFSFromImage(newImage.ID(), imageName, imageName))
options = append(options, WithCtrNamespace(rootlessCNIInfraContainerNamespace))
options = append(options, WithName(rootlessCNIInfraContainerName))
options = append(options, WithPrivileged(true))
options = append(options, WithSecLabels([]string{"disable"}))
options = append(options, WithRestartPolicy("always"))
options = append(options, WithNetNS(nil, false, "slirp4netns", nil))
c, err := r.NewContainer(ctx, g.Config, options...)
if err != nil {
return nil, err
}
logrus.Debugf("rootless CNI infra container %q is created, now being started", c.ID())
if err := c.initAndStart(ctx); err != nil {
return nil, err
}
logrus.Debugf("rootless CNI: infra container %q is running", c.ID())
return c, nil
}
func rootlessCNIInfraExec(c *Container, args ...string) (string, error) {
cmd := "rootless-cni-infra"
var (
outB bytes.Buffer
errB bytes.Buffer
streams define.AttachStreams
config ExecConfig
)
streams.OutputStream = &nopWriteCloser{Writer: &outB}
streams.ErrorStream = &nopWriteCloser{Writer: &errB}
streams.AttachOutput = true
streams.AttachError = true
config.Command = append([]string{cmd}, args...)
config.Privileged = true
logrus.Debugf("rootlessCNIInfraExec: c.ID()=%s, config=%+v, streams=%v, begin",
c.ID(), config, streams)
code, err := c.Exec(&config, &streams, nil)
logrus.Debugf("rootlessCNIInfraExec: c.ID()=%s, config=%+v, streams=%v, end (code=%d, err=%v)",
c.ID(), config, streams, code, err)
if err != nil {
return "", err
}
if code != 0 {
return "", errors.Errorf("command %s %v in container %s failed with status %d, stdout=%q, stderr=%q",
cmd, args, c.ID(), code, outB.String(), errB.String())
}
return outB.String(), nil
}
type nopWriteCloser struct {
io.Writer
}
func (nwc *nopWriteCloser) Close() error {
return nil
}

View File

@ -436,13 +436,11 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
}
// Set up the CNI net plugin
if !rootless.IsRootless() {
netPlugin, err := ocicni.InitCNI(runtime.config.Network.DefaultNetwork, runtime.config.Network.NetworkConfigDir, runtime.config.Network.CNIPluginDirs...)
if err != nil {
return errors.Wrapf(err, "error configuring CNI network plugin")
}
runtime.netPlugin = netPlugin
netPlugin, err := ocicni.InitCNI(runtime.config.Network.DefaultNetwork, runtime.config.Network.NetworkConfigDir, runtime.config.Network.CNIPluginDirs...)
if err != nil {
return errors.Wrapf(err, "error configuring CNI network plugin")
}
runtime.netPlugin = netPlugin
// We now need to see if the system has restarted
// We check for the presence of a file in our tmp directory to verify this

View File

@ -104,7 +104,7 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, rawIm
default:
// Since user namespace sharing is not implemented, we only need to check if it's rootless
netmode := "bridge"
if isRootless || p.config.InfraContainer.Slirp4netns {
if p.config.InfraContainer.Slirp4netns {
netmode = "slirp4netns"
if len(p.config.InfraContainer.NetworkOptions) != 0 {
options = append(options, WithNetworkOptions(p.config.InfraContainer.NetworkOptions))

View File

@ -35,9 +35,9 @@ import (
"golang.org/x/sys/unix"
)
// get NSRunDir returns the dir of where to create the netNS. When running
// GetNSRunDir returns the dir of where to create the netNS. When running
// rootless, it needs to be at a location writable by user.
func getNSRunDir() (string, error) {
func GetNSRunDir() (string, error) {
if rootless.IsRootless() {
rootlessDir, err := util.GetRuntimeDir()
if err != nil {
@ -51,16 +51,22 @@ func getNSRunDir() (string, error) {
// NewNS creates a new persistent (bind-mounted) network namespace and returns
// an object representing that namespace, without switching to it.
func NewNS() (ns.NetNS, error) {
nsRunDir, err := getNSRunDir()
if err != nil {
return nil, err
}
b := make([]byte, 16)
_, err = rand.Reader.Read(b)
_, err := rand.Reader.Read(b)
if err != nil {
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
}
nsName := fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
return NewNSWithName(nsName)
}
// NewNSWithName creates a new persistent (bind-mounted) network namespace and returns
// an object representing that namespace, without switching to it.
func NewNSWithName(name string) (ns.NetNS, error) {
nsRunDir, err := GetNSRunDir()
if err != nil {
return nil, err
}
// Create the directory for mounting network namespaces
// This needs to be a shared mountpoint in case it is mounted in to
@ -93,10 +99,8 @@ func NewNS() (ns.NetNS, error) {
}
}
nsName := fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
// create an empty file at the mount point
nsPath := path.Join(nsRunDir, nsName)
nsPath := path.Join(nsRunDir, name)
mountPointFd, err := os.Create(nsPath)
if err != nil {
return nil, err
@ -177,7 +181,7 @@ func NewNS() (ns.NetNS, error) {
// UnmountNS unmounts the NS held by the netns object
func UnmountNS(ns ns.NetNS) error {
nsRunDir, err := getNSRunDir()
nsRunDir, err := GetNSRunDir()
if err != nil {
return err
}

View File

@ -236,9 +236,6 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.
case specgen.Private:
fallthrough
case specgen.Bridge:
if postConfigureNetNS && rootless.IsRootless() {
return nil, errors.New("CNI networks not supported with user namespaces")
}
portMappings, err := createPortMappings(ctx, s, img)
if err != nil {
return nil, err

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/pkg/rootless"
"github.com/containers/podman/v3/pkg/specgen"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@ -94,8 +95,19 @@ func createPodOptions(p *specgen.PodSpecGenerator, rt *libpod.Runtime) ([]libpod
}
switch p.NetNS.NSMode {
case specgen.Bridge, specgen.Default, "":
logrus.Debugf("Pod using default network mode")
case specgen.Default, "":
if p.NoInfra {
logrus.Debugf("No networking because the infra container is missing")
break
}
if rootless.IsRootless() {
logrus.Debugf("Pod will use slirp4netns")
options = append(options, libpod.WithPodSlirp4netns(p.NetworkOptions))
} else {
logrus.Debugf("Pod using bridge network mode")
}
case specgen.Bridge:
logrus.Debugf("Pod using bridge network mode")
case specgen.Host:
logrus.Debugf("Pod will use host networking")
options = append(options, libpod.WithPodHostNetwork())

View File

@ -1,4 +1,5 @@
# -*- bash -*-
test_port 5000 = "Podman rulez!"
podman container inspect -l --format '{{.Config.Labels}}' | grep "the_best"
podman container inspect -l --format '{{.Config.Labels}}'
like "$output" "io.podman:the_best" "$testname : Container label is set"

View File

@ -13,7 +13,8 @@ TEST_ROOTDIR=$(realpath $(dirname $0))
# Podman executable
PODMAN_BIN=$(realpath $TEST_ROOTDIR/../../bin)/podman
# Local path to docker socket (we will add the unix:/ prefix when we need it)
# Local path to docker socket with unix prefix
# The path will be changed for rootless users
DOCKER_SOCK=/var/run/docker.sock
# END stuff you can but probably shouldn't customize
@ -40,6 +41,13 @@ echo 0 >$failures_file
###############################################################################
# BEGIN infrastructure code - the helper functions used in tests themselves
#################
# is_rootless # Check if we run as normal user
#################
function is_rootless() {
[ "$(id -u)" -ne 0 ]
}
#########
# die # Exit error with a message to stderr
#########
@ -155,7 +163,7 @@ function test_port() {
local op="$2" # '=' or '~'
local expect="$3" # what to expect from curl output
local actual=$(curl --retry 5 --retry-connrefused -s http://127.0.0.1:$port/)
local actual=$(curl --retry 10 --retry-all-errors -s http://127.0.0.1:$port/)
local curl_rc=$?
if [ $curl_rc -ne 0 ]; then
_show_ok 0 "$testname - curl failed with status $curl_rc"
@ -179,7 +187,12 @@ function start_service() {
test -x $PODMAN_BIN || die "Not found: $PODMAN_BIN"
# FIXME: use ${testname} subdir but we can't: 50-char limit in runroot
rm -rf $WORKDIR/{root,runroot,cni}
if ! is_rootless; then
rm -rf $WORKDIR/{root,runroot,cni}
else
$PODMAN_BIN unshare rm -rf $WORKDIR/{root,runroot,cni}
fi
rm -f $DOCKER_SOCK
mkdir --mode 0755 $WORKDIR/{root,runroot,cni}
chcon --reference=/var/lib/containers $WORKDIR/root
cp /etc/cni/net.d/*podman*conflist $WORKDIR/cni/
@ -190,7 +203,7 @@ function start_service() {
--cgroup-manager=systemd \
--cni-config-dir $WORKDIR/cni \
system service \
--time 0 unix:/$DOCKER_SOCK \
--time 0 unix://$DOCKER_SOCK \
&> $WORKDIR/server.log &
service_pid=$!
@ -211,10 +224,11 @@ function start_service() {
############
function podman() {
echo "\$ podman $*" >>$WORKDIR/output.log
$PODMAN_BIN \
output=$($PODMAN_BIN \
--root $WORKDIR/root \
--runroot $WORKDIR/runroot \
"$@" >>$WORKDIR/output.log 2>&1
"$@")
echo -n "$output" >>$WORKDIR/output.log
}
###################
@ -239,6 +253,14 @@ done
###############################################################################
# BEGIN entry handler (subtest invoker)
# When rootless use a socket path accessible by the rootless user
if is_rootless; then
DOCKER_SOCK="$WORKDIR/docker.sock"
DOCKER_HOST="unix://$DOCKER_SOCK"
# export DOCKER_HOST docker-compose will use it
export DOCKER_HOST
fi
# Identify the tests to run. If called with args, use those as globs.
tests_to_run=()
if [ -n "$*" ]; then
@ -308,7 +330,7 @@ for t in ${tests_to_run[@]}; do
fi
# Done. Clean up.
docker-compose down &> $logfile
docker-compose down &>> $logfile
rc=$?
if [[ $rc -eq 0 ]]; then
_show_ok 1 "$testname - down"
@ -322,7 +344,11 @@ for t in ${tests_to_run[@]}; do
wait $service_pid
# FIXME: otherwise we get EBUSY
umount $WORKDIR/root/overlay &>/dev/null
if ! is_rootless; then
umount $WORKDIR/root/overlay &>/dev/null
else
$PODMAN_BIN unshare umount $WORKDIR/root/overlay &>/dev/null
fi
# FIXME: run 'podman ps'?
# rm -rf $WORKDIR/${testname}
@ -336,9 +362,13 @@ done
test_count=$(<$testcounter_file)
failure_count=$(<$failures_file)
#if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then
# rm -rf $WORKDIR
#fi
if [ -z "$PODMAN_TESTS_KEEP_WORKDIR" ]; then
if ! is_rootless; then
rm -rf $WORKDIR
else
$PODMAN_BIN unshare rm -rf $WORKDIR
fi
fi
echo "1..${test_count}"

View File

@ -0,0 +1,8 @@
two networks
===============
This test checks that we can create containers with more than one network.
Validation
------------
* podman container inspect two_networks_con1_1 --format '{{len .NetworkSettings.Networks}}' shows 2

View File

@ -0,0 +1,11 @@
version: '3'
services:
con1:
image: alpine
command: top
networks:
- net1
- net2
networks:
net1:
net2:

View File

@ -0,0 +1,7 @@
# -*- bash -*-
podman container inspect two_networks_con1_1 --format '{{len .NetworkSettings.Networks}}'
is "$output" "2" "$testname : Container is connected to both networks"
podman container inspect two_networks_con1_1 --format '{{.NetworkSettings.Networks}}'
like "$output" "two_networks_net1" "$testname : First network name exists"
like "$output" "two_networks_net2" "$testname : Second network name exists"

View File

@ -33,14 +33,12 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("bad network name in disconnect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootful")
dis := podmanTest.Podman([]string{"network", "disconnect", "foobar", "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("bad container name in network disconnect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootful")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
@ -72,7 +70,6 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("podman network disconnect", func() {
SkipIfRootless("network connect and disconnect are only rootful")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
@ -102,14 +99,12 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("bad network name in connect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootful")
dis := podmanTest.Podman([]string{"network", "connect", "foobar", "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("bad container name in network connect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootful")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
@ -141,7 +136,6 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("podman connect on a container that already is connected to the network should error", func() {
SkipIfRootless("network connect and disconnect are only rootful")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
@ -159,7 +153,6 @@ var _ = Describe("Podman network connect and disconnect", func() {
It("podman network connect", func() {
SkipIfRemote("This requires a pending PR to be merged before it will work")
SkipIfRootless("network connect and disconnect are only rootful")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
@ -203,18 +196,23 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("podman network connect when not running", func() {
SkipIfRootless("network connect and disconnect are only rootful")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
netName1 := "connect1" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName1})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
defer podmanTest.removeCNINetwork(netName1)
ctr := podmanTest.Podman([]string{"create", "--name", "test", ALPINE, "top"})
netName2 := "connect2" + stringid.GenerateNonCryptoID()
session = podmanTest.Podman([]string{"network", "create", netName2})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName2)
ctr := podmanTest.Podman([]string{"create", "--name", "test", "--network", netName1, ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr.ExitCode()).To(BeZero())
dis := podmanTest.Podman([]string{"network", "connect", netName, "test"})
dis := podmanTest.Podman([]string{"network", "connect", netName2, "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).To(BeZero())
@ -286,7 +284,6 @@ var _ = Describe("Podman network connect and disconnect", func() {
})
It("podman network disconnect when not running", func() {
SkipIfRootless("network connect and disconnect are only rootful")
netName1 := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName1})
session.WaitWithDefaultTimeout()

View File

@ -641,22 +641,26 @@ var _ = Describe("Podman run networking", func() {
Expect(run.OutputToString()).To(ContainSubstring(ipAddr))
})
It("podman rootless fails custom CNI network with --uidmap", func() {
SkipIfNotRootless("The configuration works with rootless")
It("podman cni network works across user ns", func() {
netName := stringid.GenerateNonCryptoID()
create := podmanTest.Podman([]string{"network", "create", netName})
create.WaitWithDefaultTimeout()
Expect(create.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
run := podmanTest.Podman([]string{"run", "--rm", "--net", netName, "--uidmap", "0:1:4096", ALPINE, "true"})
name := "nc-server"
run := podmanTest.Podman([]string{"run", "-d", "--name", name, "--net", netName, ALPINE, "nc", "-l", "-p", "8080"})
run.WaitWithDefaultTimeout()
Expect(run.ExitCode()).To(Equal(125))
Expect(run.ExitCode()).To(Equal(0))
remove := podmanTest.Podman([]string{"network", "rm", netName})
remove.WaitWithDefaultTimeout()
Expect(remove.ExitCode()).To(BeZero())
run = podmanTest.Podman([]string{"run", "--rm", "--net", netName, "--uidmap", "0:1:4096", ALPINE, "sh", "-c", fmt.Sprintf("echo podman | nc -w 1 %s.dns.podman 8080", name)})
run.WaitWithDefaultTimeout()
Expect(run.ExitCode()).To(Equal(0))
log := podmanTest.Podman([]string{"logs", name})
log.WaitWithDefaultTimeout()
Expect(log.ExitCode()).To(Equal(0))
Expect(log.OutputToString()).To(Equal("podman"))
})
It("podman run with new:pod and static-ip", func() {
@ -762,7 +766,7 @@ var _ = Describe("Podman run networking", func() {
Expect(session.ExitCode()).To(Equal(1))
Expect(session.ErrorToString()).To(ContainSubstring("can't resolve 'con1'"))
session = podmanTest.Podman([]string{"run", "--name", "con4", "--network", net, ALPINE, "nslookup", pod2})
session = podmanTest.Podman([]string{"run", "--name", "con4", "--network", net, ALPINE, "nslookup", pod2 + ".dns.podman"})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
})

View File

@ -143,13 +143,6 @@ load helpers
run_podman network rm $mynetname
run_podman 1 network rm $mynetname
# rootless CNI leaves behind an image pulled by SHA, hence with no tag.
# Remove it if present; we can only remove it by ID.
run_podman images --format '{{.Id}}' rootless-cni-infra
if [ -n "$output" ]; then
run_podman rmi $output
fi
}
@test "podman network reload" {