読者です 読者をやめる 読者になる 読者になる

TenForward

技術ブログ。はてなダイアリーから移転しました

UTS Namespace (名前空間) を追っかける

Linux Container

コンテナが盛り上がってるので、エラい人たちが結構色々な情報を色々な所に書いてくれるので、簡単に知識が手に入るようになってきましたね。というわけで、私のようななんちゃってな人でも気軽に処理を追っかけることができるようになってきましたから、久々にカーネルのコードを読んでみました、というメモ。(ちゃんとした知識を手に入れるにはもっと偉い人の資料を参考に自分でおっかけてみましょう :-)

Namespace の中でも一番単純そうな UTS Namespace。そうです、あのホスト名とかドメイン名とか、あの辺りを Namespace ごとに保持できるシンプルな Namespace です。これならシロートな私でも大丈夫そう。

nsproxy

タスクごとの Namespace を保持するために、プロセスなんかの色々な情報を保持する task_struct 構造体というのが include/linux/sched.h にあります。この中に nsproxy というメンバがいますね。これが Namespace 情報を保持します。

struct task_struct {
	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
    : (snip)
/* namespaces */
	struct nsproxy *nsproxy;
    : (snip)
};

特定のプロセスの nsproxy にアクセスするには include/linux/nsproxy.h の task_nsproxy って関数を使うようです (この関数の定義部分に使い方書いてますよ。

static inline struct nsproxy *task_nsproxy(struct task_struct *tsk)
{
	return rcu_dereference(tsk->nsproxy);
}

nsproxy 構造体は同じく include/linux/nsproxy.h で以下のように定義されており、5 つの Namespace があります。

struct nsproxy {
	atomic_t count;
	struct uts_namespace *uts_ns;
	struct ipc_namespace *ipc_ns;
	struct mnt_namespace *mnt_ns;
	struct pid_namespace *pid_ns_for_children;
	struct net 	     *net_ns;
};
extern struct nsproxy init_nsproxy;

Namespace の初期化

Namespace はそれぞれ初期の空間を持っています。普通、名前空間を分けずに使う時のデフォルトの名前空間ですね。これが上記の init_nsproxy ってやつみたいで、kernel/nsproxy.c で定義されていて、5 つの Namespace それぞれに初期のやつを割り当ててますね。UTS Namespace は init_uts_ns という変数のようですね。これは後で見ます。

struct nsproxy init_nsproxy = {
	.count			= ATOMIC_INIT(1),
	.uts_ns			= &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
	.ipc_ns			= &init_ipc_ns,
#endif
	.mnt_ns			= NULL,
	.pid_ns_for_children	= &init_pid_ns,
#ifdef CONFIG_NET
	.net_ns			= &init_net,
#endif
};

init_nsproxy は include/linux/init_task.h 内の INIT_TASK マクロで代入されていて、

#define INIT_TASK(tsk)	\
{									\
	.state		= 0,						\
    : (snip)
	.nsproxy	= &init_nsproxy,				\
}

この INIT_TASK マクロは は init/init_task.c で

/* Initial task structure */
struct task_struct init_task = INIT_TASK(init_task);
EXPORT_SYMBOL(init_task);

と使われているので、task_struct 構造体である変数 init_task に設定されます。(この先 init_task がどのように割り当てられるのかはちょっと見たけどわかりませんでした ^^;)

UTS Namespace

さて、nsproxy のメンバ uts_ns として定義される uts_namespace ですが、include/linux/utsname.h で定義されています。

struct uts_namespace {
	struct kref kref;
	struct new_utsname name;
	struct user_namespace *user_ns;
	unsigned int proc_inum;
};

ちなみに new_utsname 構造体は include/linux/utsname.h で定義されており、

struct new_utsname {
	char sysname[__NEW_UTS_LEN + 1];
	char nodename[__NEW_UTS_LEN + 1];
	char release[__NEW_UTS_LEN + 1];
	char version[__NEW_UTS_LEN + 1];
	char machine[__NEW_UTS_LEN + 1];
	char domainname[__NEW_UTS_LEN + 1];
};

さて、struct uts_namespace の定義の直後に以下のような行があります。

extern struct uts_namespace init_uts_ns;

先ほど出てきた init_uts_ns が登場です。文字通り初期状態のようで、init/version.c で以下のように設定されます。

struct uts_namespace init_uts_ns = {
	.kref = {
		.refcount	= ATOMIC_INIT(2),
	},
	.name = {
		.sysname	= UTS_SYSNAME,
		.nodename	= UTS_NODENAME,
		.release	= UTS_RELEASE,
		.version	= UTS_VERSION,
		.machine	= UTS_MACHINE,
		.domainname	= UTS_DOMAINNAME,
	},
	.user_ns = &init_user_ns,
	.proc_inum = PROC_UTS_INIT_INO,
};
EXPORT_SYMBOL_GPL(init_uts_ns);

ちなみに UTS_* は include/linux/uts.h で定義されていて

#ifndef UTS_SYSNAME
#define UTS_SYSNAME "Linux"
#endif

#ifndef UTS_NODENAME
#define UTS_NODENAME CONFIG_DEFAULT_HOSTNAME /* set by sethostname() */
#endif

#ifndef UTS_DOMAINNAME
#define UTS_DOMAINNAME "(none)"	/* set by setdomainname() */
#endif

CONFIG_DEFAULT_HOSTNAME は make menuconfig とかすると、"General Setup" 内に定義する所がある。arch によっては "(none)" 以外の何かがデフォルトで設定されたり。UTS_RELEASE とかもカーネルの config で設定できますね。

gethostname システムコール

ホスト名を取得するシステムコール gethostname を見てみましょう。kernel/sys.c で定義されていますね。

SYSCALL_DEFINE2(gethostname, char __user *, name, int, len)
{
	int i, errno;
	struct new_utsname *u;

	if (len < 0)
		return -EINVAL;
	down_read(&uts_sem);
	u = utsname();
	i = 1 + strlen(u->nodename);
	if (i > len)
		i = len;
	errno = 0;
	if (copy_to_user(name, u->nodename, i))
		errno = -EFAULT;
	up_read(&uts_sem);
	return errno;
}

取得してるのは utsname() ですね。

static inline struct new_utsname *utsname(void)
{
	return &current->nsproxy->uts_ns->name;
}

nsproxy のメンバ uts_ns のメンバの name を返してますね。current は例えば arch/x86/include/asm/current.h とかの

static __always_inline struct task_struct *get_current(void)
{
	return this_cpu_read_stable(current_task);
}

#define current get_current()

この辺り? で現在のタスクを取得してるってことですかね〜。

sethostname システムコール

sethostname も同じですよー。gethostname と同じく kernel/sys.c で定義されています。

SYSCALL_DEFINE2(sethostname, char __user *, name, int, len)
{
	int errno;
	char tmp[__NEW_UTS_LEN];

	if (!ns_capable(current->nsproxy->uts_ns->user_ns, CAP_SYS_ADMIN))
		return -EPERM;

	if (len < 0 || len > __NEW_UTS_LEN)
		return -EINVAL;
	down_write(&uts_sem);
	errno = -EFAULT;
	if (!copy_from_user(tmp, name, len)) {
		struct new_utsname *u = utsname();

		memcpy(u->nodename, tmp, len);
		memset(u->nodename + len, 0, sizeof(u->nodename) - len);
		errno = 0;
		uts_proc_notify(UTS_PROC_HOSTNAME);
	}
	up_write(&uts_sem);
	return errno;
}
  1. ns_capable でケーパビリティのチェックをしてから…
  2. 同じく utsname() で現在のタスクの new_utsname 構造体を取得して…
  3. その nodename に memcpy していますね。

clone(2) (2014-08-12 追記)

肝心の CLONE_NEWUTS で新しい名前空間を作る所ですが、おお、こんな分かりやすいブログが!! 勉強させてもらいます。ありがとうございます。

UTS Namespace に限らないお話ですね。以上の masami256 さんのブログエントリを見ながら軽く追ってみました。(私には前提知識がないのでもう少し最初から)

fork, vfork, clone の実態は do_fork ですね (多分)。kernel/fork.c にあります。マクロで適当な引数をあらかじめ与えて do_fork を呼んでる感じですね (多分)。

#ifdef CONFIG_CLONE_BACKWARDS
  : (snip)
#else
SYSCALL_DEFINE5(clone, unsigned long, clone_flags, unsigned long, newsp,
		 int __user *, parent_tidptr,
		 int __user *, child_tidptr,
		 int, tls_val)
#endif
{
	return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}

do_fork では copy_process でプロセスのコピーが行われるみたいですね。(kernel/fork.c)

long do_fork(unsigned long clone_flags,
	      unsigned long stack_start,
	      unsigned long stack_size,
	      int __user *parent_tidptr,
	      int __user *child_tidptr)
{
    : (snip)
	p = copy_process(clone_flags, stack_start, stack_size,
			 child_tidptr, NULL, trace);
    : (snip)
}

copy_process では clone_flags に応じて色々なチェックをしてから dup_task_struct が呼ばれたあとで、copy_namespace が呼ばれてますね。(kernel/fork.c)

static struct task_struct *copy_process(unsigned long clone_flags,
					unsigned long stack_start,
					unsigned long stack_size,
					int __user *child_tidptr,
					struct pid *pid,
					int trace)
{
    : (snip)
	p = dup_task_struct(current);
    : (snip)
	retval = copy_namespaces(clone_flags, p);
	if (retval)
		goto bad_fork_cleanup_mm;
    : (snip)
}

で、先の参考ブログエントリにもあった通り、kernel/nsproxy.c の copy_namespaces は以下のようなシンプルな関数です。(詳しくは参考ブログエントリ参照)

int copy_namespaces(unsigned long flags, struct task_struct *tsk)
{
	struct nsproxy *old_ns = tsk->nsproxy;
	struct user_namespace *user_ns = task_cred_xxx(tsk, user_ns);
	struct nsproxy *new_ns;
    : (snip)
	new_ns = create_new_namespaces(flags, tsk, user_ns, tsk->fs);
	if (IS_ERR(new_ns))
		return  PTR_ERR(new_ns);

	tsk->nsproxy = new_ns;
	return 0;
}

create_new_namespaces 内 (同じく kernel/nsproxy.c) では copy_utsname ってのがありますね。

static struct nsproxy *create_new_namespaces(unsigned long flags,
	struct task_struct *tsk, struct user_namespace *user_ns,
	struct fs_struct *new_fs)
{
	struct nsproxy *new_nsp;
	int err;

	new_nsp = create_nsproxy();
    : (snip)
	new_nsp->uts_ns = copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
	if (IS_ERR(new_nsp->uts_ns)) {
		err = PTR_ERR(new_nsp->uts_ns);
		goto out_uts;
	}

    : (snip)
	return new_nsp;
    : (snip)
out_uts:
	if (new_nsp->mnt_ns)
		put_mnt_ns(new_nsp->mnt_ns);
    : (snip)
}

kernel/utsname.c の copy_utsname は以下。clone_uts_ns ってのがありますね。

struct uts_namespace *copy_utsname(unsigned long flags,
	struct user_namespace *user_ns, struct uts_namespace *old_ns)
{
	struct uts_namespace *new_ns;

	BUG_ON(!old_ns);
	get_uts_ns(old_ns);

	if (!(flags & CLONE_NEWUTS))
		return old_ns;

	new_ns = clone_uts_ns(user_ns, old_ns);

	put_uts_ns(old_ns);
	return new_ns;
}

clone_uts_ns は同じ kernel/utsname.c にあって、

static struct uts_namespace *clone_uts_ns(struct user_namespace *user_ns,
					  struct uts_namespace *old_ns)
{
	struct uts_namespace *ns;
	int err;

	ns = create_uts_ns();
	if (!ns)
		return ERR_PTR(-ENOMEM);

	err = proc_alloc_inum(&ns->proc_inum);
	if (err) {
		kfree(ns);
		return ERR_PTR(err);
	}

	down_read(&uts_sem);
	memcpy(&ns->name, &old_ns->name, sizeof(ns->name));
	ns->user_ns = get_user_ns(user_ns);
	up_read(&uts_sem);
	return ns;
}

create_uts_ns ってのがありますね。これも同じ kernel/utsname.c にあって、

static struct uts_namespace *create_uts_ns(void)
{
	struct uts_namespace *uts_ns;

	uts_ns = kmalloc(sizeof(struct uts_namespace), GFP_KERNEL);
	if (uts_ns)
		kref_init(&uts_ns->kref);
	return uts_ns;
}

はい、新しい uts_namespace 構造体を確保して返してますね。create_uts_ns の後はコピー元の情報を初期情報としてコピーしてますね。