setns(2)を使う方のめも

今日はLinuxカーネルもくもく会 #3 でsetns(2)のユーザーランド側の挙動を見てみようと思って実験してみたのでメモ。
カーネル側の動作についてはTenForwardさんによる[Linux][Container] setns を UTS Namespace をネタにおっかけるが参考になります。

で、テスト用のコードを書いてみました。 まずこれはclone(2)でuts namespaceとpid namespaceを新規に作ってbashを起動するもの。

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>

#define STACK_SIZE 8192
char stack[STACK_SIZE];

int func(void *arg)
{
#define NEW_HOSTNAME "testhost"
    sethostname(NEW_HOSTNAME, strlen(NEW_HOSTNAME));

    execlp("/bin/bash", "bash", NULL);

    return 0;
}

int main(int argc, char **argv)
{
    pid_t pid;
    char name[256] = { 0 };

    gethostname(name, sizeof(name) - 1);
    printf("current host name is %s\n", name);

    pid = clone(func, stack + STACK_SIZE, CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL);
    if (pid > 0) {
        printf("child's pid %d\n", pid);
        waitpid(pid, NULL, 0);
    } else {
        perror("clone");
        exit(-1);
    }

    return 0;
}

こちらは上のコードで作ったプロセスの名前空間に入るためのコード。setnsでpidとuts名前空間を設定してます。

#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

void show_hostname(char *which)
{
    char hostname[256] = { 0 };

    gethostname(hostname, sizeof(hostname) - 1);

    printf("%s hostname: %s\n", which, hostname);

}

void show_pid(char *which)
{
    pid_t pid = getpid();
    printf("%s: pid %d\n", which, pid);
}

void enter_newns(char *ns, int pid)
{
    char *path;
    int fd;
    asprintf(&path, "/proc/%d/ns/%s", pid, ns);
    fd = open(path, O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(-1);
    }

    free(path);
    if (setns(fd, 0)) {
        perror("setns");
        exit(-1);
    }

    close(fd);

}

static char *namespaces[] = {
    "uts",
    "pid",
};

#define NAMESPACES_SIZE sizeof(namespaces) / sizeof(namespaces[0])

int main(int argc, char **argv)
{
    int pid, forked_pid;
    int i;

    if (argc != 2) {
        printf("usage %s pid\n", argv[0]);
        return -1;
    }

    pid = strtol(argv[1], NULL, 10);

    show_hostname("before");
    show_pid("before");

    for (i = 0; i < NAMESPACES_SIZE; i++) 
        enter_newns(namespaces[i], pid);

    forked_pid = fork();
    if (!forked_pid) {
        show_hostname("afetr child process");
        show_pid("afetr child process");
        _exit(0);
    } else if (forked_pid > 0) {
        waitpid(forked_pid, NULL, 0);
        show_hostname("after");
        show_pid("after");
    } else {
        perror("fork");
        exit(-1);
    }

    return 0;
}

ここからは実行結果。まずは名前空間を作る方。こちらはclone()のCLONE_NEWPIDによりpidが1から始まっているのとCLONE_NEWUTSとsethostname()によりuts名前空間の切り替えとホスト名の設定ができています。clone()を呼び出した方のプロセスからはpid1509が見えていて、clone()で作られたプロセスはpid1と認識してます。

masami@miko:~$ sudo ./create_newuts
current host name is miko
child's pid 1509
[root@testhost masami]# echo $$
1
[root@testhost masami]# 

こちらは名前空間に入る方。setns()の前と後でホスト名が変わっていますね。あと、これは動かしてみて初めて気づいたところですがsetns()で別のpid名前空間に入ったとしててもpidは変わっていませんが、ここでfork()をすると子プロセスはcreate_newutsのpid名前空間で管理されているのがわかります。

masami@miko:~$ sudo ./enter_ns 1509
before hostname: miko
before: pid 1516
afetr child process hostname: testhost
afetr child process: pid 2
after hostname: testhost
after: pid 1516

やっぱり動かしてみると動きがよくわかりますね。

Linuxプログラミングインタフェース

Linuxプログラミングインタフェース