Skip to content

Commit

Permalink
Merge pull request #7460 from AkihiroSuda/allow-rootless-cni
Browse files Browse the repository at this point in the history
rootless: support `podman network create` (CNI-in-slirp4netns)
  • Loading branch information
openshift-merge-robot authored Sep 10, 2020
2 parents 8d78605 + f82abc7 commit 2f0e803
Show file tree
Hide file tree
Showing 21 changed files with 605 additions and 48 deletions.
3 changes: 0 additions & 3 deletions cmd/podman/networks/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ var (
RunE: networkCreate,
Args: cobra.MaximumNArgs(1),
Example: `podman network create podman1`,
Annotations: map[string]string{
registry.ParentNSRequired: "",
},
}
)

Expand Down
3 changes: 0 additions & 3 deletions cmd/podman/networks/inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ var (
RunE: networkInspect,
Example: `podman network inspect podman`,
Args: cobra.MinimumNArgs(1),
Annotations: map[string]string{
registry.ParentNSRequired: "",
},
}
)

Expand Down
3 changes: 0 additions & 3 deletions cmd/podman/networks/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ var (
Long: networklistDescription,
RunE: networkList,
Example: `podman network list`,
Annotations: map[string]string{
registry.ParentNSRequired: "",
},
}
)

Expand Down
3 changes: 0 additions & 3 deletions cmd/podman/networks/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ var (
RunE: networkRm,
Example: `podman network rm podman`,
Args: cobra.MinimumNArgs(1),
Annotations: map[string]string{
registry.ParentNSRequired: "",
},
}
)

Expand Down
35 changes: 35 additions & 0 deletions contrib/rootless-cni-infra/Containerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
ARG GOLANG_VERSION=1.15
ARG ALPINE_VERSION=3.12
ARG CNI_VERSION=v0.8.0
ARG CNI_PLUGINS_VERSION=v0.8.7
# Aug 20, 2020
ARG DNSNAME_VESION=78b4da7bbfc51c27366da630e1df1c4f2e8b1b5b

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"]
22 changes: 22 additions & 0 deletions contrib/rootless-cni-infra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 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`.

## 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
147 changes: 147 additions & 0 deletions contrib/rootless-cni-infra/rootless-cni-infra
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/bin/sh
set -eu

ARG0="$0"
VERSION="0.1.0"
BASE="/run/rootless-cni-infra"

# CLI subcommand: "alloc $CONTAINER_ID $NETWORK_NAME $POD_NAME"
cmd_entrypoint_alloc() {
if [ "$#" -ne 3 ]; then
echo >&2 "Usage: $ARG0 alloc CONTAINER_ID NETWORK_NAME POD_NAME"
exit 1
fi

ID="$1"
NET="$2"
K8S_POD_NAME="$3"

dir="${BASE}/${ID}"
mkdir -p "${dir}/attached"

pid=""
if [ -f "${dir}/pid" ]; then
pid=$(cat "${dir}/pid")
else
unshare -n sleep infinity &
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}"
nwcount=$(find "${dir}/attached" -type f | wc -l)
CNI_IFNAME="eth${nwcount}"
export CNI_ARGS CNI_IFNAME
cnitool add "${NET}" "/proc/${pid}/ns/net" >"${dir}/attached/${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")
cnitool del "${NET}" "/proc/${pid}/ns/net"
rm -f "${dir}/attached/${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\": \"${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}" "$@"
4 changes: 3 additions & 1 deletion libpod/container_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -957,8 +957,10 @@ func (c *Container) completeNetworkSetup() error {
if err := c.syncContainer(); err != nil {
return err
}
if c.config.NetMode.IsSlirp4netns() {
if rootless.IsRootless() {
return c.runtime.setupRootlessNetNS(c)
} else if c.config.NetMode.IsSlirp4netns() {
return c.runtime.setupSlirp4netns(c)
}
if err := c.runtime.setupNetNS(c); err != nil {
return err
Expand Down
14 changes: 11 additions & 3 deletions libpod/container_internal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ 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 {
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
if rootless.IsRootless() && len(c.config.Networks) > 0 {
netNS, networkStatus, createNetNSErr = AllocRootlessCNI(context.Background(), c)
} else {
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
}
if createNetNSErr != nil {
return
}
Expand All @@ -98,8 +102,12 @@ func (c *Container) prepare() error {
}

// handle rootless network namespace setup
if noNetNS && c.config.NetMode.IsSlirp4netns() && !c.config.PostConfigureNetNS {
createNetNSErr = c.runtime.setupRootlessNetNS(c)
if noNetNS && !c.config.PostConfigureNetNS {
if rootless.IsRootless() {
createNetNSErr = c.runtime.setupRootlessNetNS(c)
} else if c.config.NetMode.IsSlirp4netns() {
createNetNSErr = c.runtime.setupSlirp4netns(c)
}
}
}()
// Mount storage if not mounted
Expand Down
11 changes: 0 additions & 11 deletions libpod/container_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package libpod

import (
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/pkg/rootless"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
Expand Down Expand Up @@ -68,16 +67,6 @@ func (c *Container) validate() error {
}
}

// Rootless has some requirements, compared to networks.
if rootless.IsRootless() {
if len(c.config.Networks) > 0 {
return errors.Wrapf(define.ErrInvalidArg, "cannot join CNI networks if running rootless")
}

// TODO: Should we make sure network mode is set to Slirp if set
// at all?
}

// Can only set static IP or MAC is creating a network namespace.
if !c.config.CreateNetNS && (c.config.StaticIP != nil || c.config.StaticMAC != nil) {
return errors.Wrapf(define.ErrInvalidArg, "cannot set static IP or MAC address if not creating a network namespace")
Expand Down
24 changes: 23 additions & 1 deletion libpod/networking_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package libpod

import (
"bytes"
"context"
"crypto/rand"
"fmt"
"io"
Expand Down Expand Up @@ -208,6 +209,20 @@ func checkSlirpFlags(path string) (*slirpFeatures, error) {

// Configure the network namespace for a rootless container
func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
if ctr.config.NetMode.IsSlirp4netns() {
return r.setupSlirp4netns(ctr)
}
if len(ctr.config.Networks) > 0 {
// set up port forwarder for CNI-in-slirp4netns
netnsPath := ctr.state.NetNS.Path()
// TODO: support slirp4netns port forwarder as well
return r.setupRootlessPortMappingViaRLK(ctr, netnsPath)
}
return 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 == "" {
Expand Down Expand Up @@ -711,7 +726,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error {

logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())

// rootless containers do not use the CNI plugin
// rootless containers do not use the CNI plugin directly
if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() {
var requestedIP net.IP
if ctr.requestedIP != nil {
Expand All @@ -738,6 +753,13 @@ func (r *Runtime) teardownNetNS(ctr *Container) error {
}
}

// CNI-in-slirp4netns
if rootless.IsRootless() && len(ctr.config.Networks) != 0 {
if err := DeallocRootlessCNI(context.Background(), ctr); err != nil {
return errors.Wrapf(err, "error tearing down CNI-in-slirp4netns for container %s", ctr.ID())
}
}

// First unmount the namespace
if err := netns.UnmountNS(ctr.state.NetNS); err != nil {
return errors.Wrapf(err, "error unmounting network namespace for container %s", ctr.ID())
Expand Down
4 changes: 4 additions & 0 deletions libpod/networking_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error {
return define.ErrNotImplemented
}

func (r *Runtime) setupSlirp4netns(ctr *Container) error {
return define.ErrNotImplemented
}

func (r *Runtime) setupNetNS(ctr *Container) error {
return define.ErrNotImplemented
}
Expand Down
4 changes: 2 additions & 2 deletions libpod/oci_conmon_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
cmd.ExtraFiles = append(cmd.ExtraFiles, childSyncPipe, childStartPipe)
cmd.ExtraFiles = append(cmd.ExtraFiles, envFiles...)

if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() {
if r.reservePorts && !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() {
ports, err := bindPorts(ctr.config.PortMappings)
if err != nil {
return err
Expand All @@ -1098,7 +1098,7 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
cmd.ExtraFiles = append(cmd.ExtraFiles, ports...)
}

if ctr.config.NetMode.IsSlirp4netns() {
if ctr.config.NetMode.IsSlirp4netns() || rootless.IsRootless() {
if ctr.config.PostConfigureNetNS {
havePortMapping := len(ctr.Config().PortMappings) > 0
if havePortMapping {
Expand Down
Loading

0 comments on commit 2f0e803

Please sign in to comment.