11

setnsC コードは正常に動作し、名前空間に正しく入りますが、Go コードは、名前空間に入る呼び出しから常に EINVAL を返すようmntです。.soGo1.2でいくつかの順列 ( cgo および external を使用した埋め込み C コードを含む) と現在のヒントを試しました1.3

コードをステップ実行するgdbと、両方のシーケンスがまったく同じ方法で呼び出さsetnslibcていることがわかります (または、私にはそう見えます)。

問題と思われるものを以下のコードに落とし込みました。私は何を間違っていますか?

設定

クイック ビジーボックス コンテナーを開始するためのシェル エイリアスがあります。

alias startbb='docker inspect --format "{{ .State.Pid }}" $(docker run -d busybox sleep 1000000)'

これを実行するstartbbと、コンテナが起動し、その PID が出力されます。

lxc-checkconfig出力:

Found kernel config file /boot/config-3.8.0-44-generic
--- Namespaces ---
Namespaces: enabled
Utsname namespace: enabled
Ipc namespace: enabled
Pid namespace: enabled
User namespace: missing
Network namespace: enabled
Multiple /dev/pts instances: enabled

--- Control groups ---
Cgroup: enabled
Cgroup clone_children flag: enabled
Cgroup device: enabled
Cgroup sched: enabled
Cgroup cpu account: enabled
Cgroup memory controller: missing
Cgroup cpuset: enabled

--- Misc ---
Veth pair device: enabled
Macvlan: enabled
Vlan: enabled
File capabilities: enabled

uname -a生成:

Linux gecko 3.8.0-44-generic #66~precise1-Ubuntu SMP Tue Jul 15 04:01:04 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux

ワーキング C コード

次の C コードは問題なく動作します。

#include <errno.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

main(int argc, char* argv[]) {
    int i;
    char nspath[1024];
    char *namespaces[] = { "ipc", "uts", "net", "pid", "mnt" };

    if (geteuid()) { fprintf(stderr, "%s\n", "abort: you want to run this as root"); exit(1); }

    if (argc != 2) { fprintf(stderr, "%s\n", "abort: you must provide a PID as the sole argument"); exit(2); }

    for (i=0; i<5; i++) {
        sprintf(nspath, "/proc/%s/ns/%s", argv[1], namespaces[i]);
        int fd = open(nspath, O_RDONLY);

        if (setns(fd, 0) == -1) { 
            fprintf(stderr, "setns on %s namespace failed: %s\n", namespaces[i], strerror(errno));
        } else {
            fprintf(stdout, "setns on %s namespace succeeded\n", namespaces[i]);
        }

        close(fd);
    }
}

でコンパイルした後gcc -o checkns checkns.cの出力sudo ./checkns <PID>は次のとおりです。

setns on ipc namespace succeeded
setns on uts namespace succeeded
setns on net namespace succeeded
setns on pid namespace succeeded
setns on mnt namespace succeeded

失敗した Go コード

逆に、次の Go コード (同一である必要があります) はうまく機能しません。

package main

import (
    "fmt"
    "os"
    "path/filepath"
    "syscall"
)

func main() {
    if syscall.Geteuid() != 0 {
        fmt.Println("abort: you want to run this as root")
        os.Exit(1)
    }

    if len(os.Args) != 2 {
        fmt.Println("abort: you must provide a PID as the sole argument")
        os.Exit(2)
    }

    namespaces := []string{"ipc", "uts", "net", "pid", "mnt"}

    for i := range namespaces {
        fd, _ := syscall.Open(filepath.Join("/proc", os.Args[1], "ns", namespaces[i]), syscall.O_RDONLY, 0644)
        err, _, msg := syscall.RawSyscall(308, uintptr(fd), 0, 0) // 308 == setns

        if err != 0 {
            fmt.Println("setns on", namespaces[i], "namespace failed:", msg)
        } else {
            fmt.Println("setns on", namespaces[i], "namespace succeeded")
        }

    }
}

代わりに、実行すると以下がsudo go run main.go <PID>生成されます。

setns on ipc namespace succeeded
setns on uts namespace succeeded
setns on net namespace succeeded
setns on pid namespace succeeded
setns on mnt namespace failed: invalid argument
4

1 に答える 1

8

( Go プロジェクトに問題が提出されています)

したがって、この質問に対する答えはsetns、シングル スレッド コンテキストから呼び出す必要があるということです。setns現在のスレッドを名前空間に参加させる必要があるため、これは理にかなっています。Go はマルチスレッドであるためsetns、Go ランタイム スレッドが開始する前に呼び出しを行う必要があります。

これは、呼び出しが実行されるスレッドがメインスレッドではないためだと思います-結果が期待どおりなくても(つまり、ゴルーチンがメインCスレッドに「ロック」されているため、以下で説明するコンストラクターのトリック)。syscall.RawSyscall runtime.LockOSThread

問題を提出した後に受け取った返信は、「cgoコンストラクターのトリック」を使用することを提案しました。この「トリック」に関する「適切な」ドキュメントは見つかりませんでしたがnsinit、Docker/Michael Crosby で使用されており、そのコードを 1 行ずつ調べましたが、この方法で実行しようとはしませんでした (以下を参照)。欲求不満のため)。

「トリック」は基本的にcgo、Go ランタイムを開始する前に C 関数を実行できることです。

これを行うには、__attribute__((constructor))Go の起動前に実行する関数を装飾するマクロを追加します。

/*
__attribute__((constructor)) void init() {
    // this code will execute before Go starts up
    // in runs in a single-threaded C context
    // before Go's threads start running
}
*/
import "C"

これをテンプレートとして使用して、次のcheckns.goように変更しました。

/*
#include <sched.h>
#include <stdio.h>
#include <fcntl.h>

__attribute__((constructor)) void enter_namespace(void) {
   setns(open("/proc/<PID>/ns/mnt", O_RDONLY, 0644), 0);
}
*/
import "C"

... rest of file is unchanged ...

このコードはPID機能しますが、コマンドライン入力から適切に読み取られないため、 をハードコードする必要がありますが、アイデアを示しています (PID上記のように開始されたコンテナーから を提供すると機能します)。

複数回呼び出したかったのでイライラしますsetnsが、この C コードは Go ランタイムが開始する前に実行されるため、利用可能な Go コードがありません。

更新:カーネル メーリング リストをあちこち回ると、これを文書化した会話へのリンクが提供されます。実際に公開されたマンページには見つからないようですが、setns(2)Eric Biederman によって確認された へのパッチからの引用は次のとおりです。

プロセスがマルチスレッドの場合、プロセスを新しいマウント名前空間に再関連付けできない場合があります。マウント名前空間を変更するには、呼び出し元が自身のユーザー名前空間で CAP_SYS_CHROOT と CAP_SYS_ADMIN の両方の機能を所有し、ターゲットのマウント名前空間で CAP_SYS_ADMIN を所有している必要があります。

于 2014-09-07T04:12:40.103 に答える