From 0a39ad196cf4af601b0ea32b2c0e0490c9079377 Mon Sep 17 00:00:00 2001 From: Paul Holzinger Date: Mon, 29 Mar 2021 18:57:54 +0200 Subject: podman unshare: add --rootless-cni to join the ns Add a new --rootless-cni option to podman unshare to also join the rootless-cni network namespace. This is useful if you want to connect to a rootless container via IP address. This is only possible from the rootless-cni namespace and not from the host namespace. This option also helps to debug problems in the rootless-cni namespace. Signed-off-by: Paul Holzinger --- cmd/podman/system/unshare.go | 6 ++++-- docs/source/markdown/podman-unshare.1.md | 37 ++++++++++++++++++++++++++++++++ libpod/networking_linux.go | 27 +++++++++++++---------- pkg/domain/entities/engine_container.go | 2 +- pkg/domain/entities/system.go | 5 +++++ pkg/domain/infra/abi/system.go | 26 ++++++++++++++++------ pkg/domain/infra/tunnel/system.go | 2 +- test/e2e/unshare_test.go | 7 ++++++ 8 files changed, 90 insertions(+), 22 deletions(-) diff --git a/cmd/podman/system/unshare.go b/cmd/podman/system/unshare.go index 5e6ff569b..c07751532 100644 --- a/cmd/podman/system/unshare.go +++ b/cmd/podman/system/unshare.go @@ -12,9 +12,10 @@ import ( ) var ( + unshareOptions = entities.SystemUnshareOptions{} unshareDescription = "Runs a command in a modified user namespace." unshareCommand = &cobra.Command{ - Use: "unshare [COMMAND [ARG...]]", + Use: "unshare [options] [COMMAND [ARG...]]", DisableFlagsInUseLine: true, Short: "Run a command in a modified user namespace", Long: unshareDescription, @@ -33,6 +34,7 @@ func init() { }) flags := unshareCommand.Flags() flags.SetInterspersed(false) + flags.BoolVar(&unshareOptions.RootlessCNI, "rootless-cni", false, "Join the rootless network namespace used for CNI networking") } func unshare(cmd *cobra.Command, args []string) error { @@ -49,5 +51,5 @@ func unshare(cmd *cobra.Command, args []string) error { args = []string{shell} } - return registry.ContainerEngine().Unshare(registry.Context(), args) + return registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions) } diff --git a/docs/source/markdown/podman-unshare.1.md b/docs/source/markdown/podman-unshare.1.md index 239213981..4451ad79c 100644 --- a/docs/source/markdown/podman-unshare.1.md +++ b/docs/source/markdown/podman-unshare.1.md @@ -24,6 +24,19 @@ The unshare session defines two environment variables: - **CONTAINERS_GRAPHROOT**: the path to the persistent container's data. - **CONTAINERS_RUNROOT**: the path to the volatile container's data. +## OPTIONS + +#### **\-\-help**, **-h** + +Print usage statement + +#### **\-\-rootless-cni** + +Join the rootless network namespace used for CNI networking. It can be used to +connect to a rootless container via IP address (CNI networking). This is otherwise +not possible from the host network namespace. +_Note: Using this option with more than one unshare session can have unexpected results._ + ## EXAMPLE ``` @@ -35,6 +48,30 @@ $ podman unshare cat /proc/self/uid_map /proc/self/gid_map 1 10000 65536 0 1000 1 1 10000 65536 + +$ podman unshare --rootless-cni ip addr +1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 + link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 + inet 127.0.0.1/8 scope host lo + valid_lft forever preferred_lft forever + inet6 ::1/128 scope host + valid_lft forever preferred_lft forever +2: tap0: mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000 + link/ether 36:0e:4a:c7:45:7e brd ff:ff:ff:ff:ff:ff + inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0 + valid_lft forever preferred_lft forever + inet6 fe80::340e:4aff:fec7:457e/64 scope link + valid_lft forever preferred_lft forever +3: cni-podman2: mtu 1500 qdisc noqueue state UP group default qlen 1000 + link/ether 5e:3a:71:d2:b4:3a brd ff:ff:ff:ff:ff:ff + inet 10.89.1.1/24 brd 10.89.1.255 scope global cni-podman2 + valid_lft forever preferred_lft forever + inet6 fe80::5c3a:71ff:fed2:b43a/64 scope link + valid_lft forever preferred_lft forever +4: vethd4ba3a2f@if3: mtu 1500 qdisc noqueue master cni-podman2 state UP group default + link/ether 8a:c9:56:32:17:0c brd ff:ff:ff:ff:ff:ff link-netnsid 0 + inet6 fe80::88c9:56ff:fe32:170c/64 scope link + valid_lft forever preferred_lft forever ``` diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 3c4014c73..6e2c2880f 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -105,13 +105,13 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port return ctrNetwork } -type rootlessCNI struct { +type RootlessCNI struct { ns ns.NetNS dir string lock lockfile.Locker } -func (r *rootlessCNI) Do(toRun func() error) error { +func (r *RootlessCNI) Do(toRun func() error) error { err := r.ns.Do(func(_ ns.NetNS) error { // before we can run the given function // we have to setup all mounts correctly @@ -174,9 +174,14 @@ func (r *rootlessCNI) Do(toRun func() error) error { return err } -// cleanup the rootless cni namespace if needed +// Cleanup the rootless cni namespace if needed // check if we have running containers with the bridge network mode -func (r *rootlessCNI) cleanup(runtime *Runtime) error { +func (r *RootlessCNI) Cleanup(runtime *Runtime) error { + _, err := os.Stat(r.dir) + if os.IsNotExist(err) { + // the directory does not exists no need for cleanup + return nil + } r.lock.Lock() defer r.lock.Unlock() running := func(c *Container) bool { @@ -234,10 +239,10 @@ func (r *rootlessCNI) cleanup(runtime *Runtime) error { return nil } -// getRootlessCNINetNs returns the rootless cni object. If create is set to true +// GetRootlessCNINetNs returns the rootless cni object. If create is set to true // the rootless cni namespace will be created if it does not exists already. -func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) { - var rootlessCNINS *rootlessCNI +func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) { + var rootlessCNINS *RootlessCNI if rootless.IsRootless() { runDir, err := util.GetRuntimeDir() if err != nil { @@ -421,7 +426,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) { os.Setenv("PATH", path) } - rootlessCNINS = &rootlessCNI{ + rootlessCNINS = &RootlessCNI{ ns: ns, dir: cniDir, lock: lock, @@ -433,7 +438,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) { // setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni // networks. If rootless it will join/create the rootless cni namespace. func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) { - rootlessCNINS, err := r.getRootlessCNINetNs(true) + rootlessCNINS, err := r.GetRootlessCNINetNs(true) if err != nil { return nil, err } @@ -651,7 +656,7 @@ func (r *Runtime) closeNetNS(ctr *Container) error { // Tear down a container's CNI network configuration and joins the // rootless net ns as rootless user func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error { - rootlessCNINS, err := r.getRootlessCNINetNs(false) + rootlessCNINS, err := r.GetRootlessCNINetNs(false) if err != nil { return err } @@ -665,7 +670,7 @@ func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error { // execute the cni setup in the rootless net ns err = rootlessCNINS.Do(tearDownPod) if err == nil { - err = rootlessCNINS.cleanup(r) + err = rootlessCNINS.Cleanup(r) } } else { err = tearDownPod() diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index bcab617af..f695d32fd 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -88,7 +88,7 @@ type ContainerEngine interface { SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error) Shutdown(ctx context.Context) SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error) - Unshare(ctx context.Context, args []string) error + Unshare(ctx context.Context, args []string, options SystemUnshareOptions) error Version(ctx context.Context) (*SystemVersionReport, error) VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error) VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error) diff --git a/pkg/domain/entities/system.go b/pkg/domain/entities/system.go index 1a671d59e..31a6185dc 100644 --- a/pkg/domain/entities/system.go +++ b/pkg/domain/entities/system.go @@ -98,6 +98,11 @@ type SystemVersionReport struct { Server *define.Version `json:",omitempty"` } +// SystemUnshareOptions describes the options for the unshare command +type SystemUnshareOptions struct { + RootlessCNI bool +} + type ComponentVersion struct { types.Version } diff --git a/pkg/domain/infra/abi/system.go b/pkg/domain/infra/abi/system.go index a3e753384..f87f9e370 100644 --- a/pkg/domain/infra/abi/system.go +++ b/pkg/domain/infra/abi/system.go @@ -390,13 +390,25 @@ func unshareEnv(graphroot, runroot string) []string { fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot)) } -func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error { - cmd := exec.Command(args[0], args[1:]...) - cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() +func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error { + unshare := func() error { + cmd := exec.Command(args[0], args[1:]...) + cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + return cmd.Run() + } + + if options.RootlessCNI { + rootlesscni, err := ic.Libpod.GetRootlessCNINetNs(true) + if err != nil { + return err + } + defer rootlesscni.Cleanup(ic.Libpod) + return rootlesscni.Do(unshare) + } + return unshare() } func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) { diff --git a/pkg/domain/infra/tunnel/system.go b/pkg/domain/infra/tunnel/system.go index d2c5063c9..7400d3771 100644 --- a/pkg/domain/infra/tunnel/system.go +++ b/pkg/domain/infra/tunnel/system.go @@ -28,7 +28,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System return system.DiskUsage(ic.ClientCtx, nil) } -func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error { +func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error { return errors.New("unshare is not supported on remote clients") } diff --git a/test/e2e/unshare_test.go b/test/e2e/unshare_test.go index 515b3a42e..24ab98916 100644 --- a/test/e2e/unshare_test.go +++ b/test/e2e/unshare_test.go @@ -49,4 +49,11 @@ var _ = Describe("Podman unshare", func() { ok, _ := session.GrepString(userNS) Expect(ok).To(BeFalse()) }) + + It("podman unshare --rootles-cni", func() { + session := podmanTest.Podman([]string{"unshare", "--rootless-cni", "ip", "addr"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("tap0")) + }) }) -- cgit v1.2.3-54-g00ecf