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

Net Namespace: copy_net_ns()めも

linux kern

Net Namespaceのcopy_net_ns()の処理を読みます。カーネルのバージョンはv4.5。

まずはデータ構造で、Net Namespaceを表現するデータ構造はstruct net。定義はinlude/net/net_namespace.hにあります。この構造体にuser_namespace構造体とかns_common構造体みたいな名前空間の機能が使うデータもあれば、IPv4のnetns_ipv4構造体、パケットフィルタリングのnetns_nftables構造体などネットワーク関連のデータも諸々あります。

では、本題のcopy_net_ns()を読んでいきます。

351 struct net *copy_net_ns(unsigned long flags,
352                         struct user_namespace *user_ns, struct net *old_net)
353 {
354         struct net *net;
355         int rv;
356 

最初はフラグのチェックで、clone(2)などでflagにCLONE_NEWNETが設定されていなければ現在の(clone(2)なら親プロセスの)Net Namespaceの参照カウントを1つ増やして完了。

357         if (!(flags & CLONE_NEWNET))
358                 return get_net(old_net);
359 

net_alloc()は名前通りな関数で、net構造体用のメモリを確保する。 細かく見るとnet_alloc_generic()という関数も呼んでいて、これはnet構造体のメンバ変数、net_generic構造体genのメモリも確保してます。

360         net = net_alloc();
361         if (!net)
362                 return ERR_PTR(-ENOMEM);
363 

get_user_ns()はUser Namespaceの参照カウントをインクリメント。

364         get_user_ns(user_ns);
365 

Net構造体を弄るためにロックを取得。

366         mutex_lock(&net_mutex);

setup_net()でnet構造体の初期化。初期化に成功したら(rv == 0)、net構造体を双方向リストの最後に追加します。

367         rv = setup_net(net, user_ns);
368         if (rv == 0) {
369                 rtnl_lock();
370                 list_add_tail_rcu(&net->list, &net_namespace_list);
371                 rtnl_unlock();
372         }

net_namespace_listnet/core/net_namespace.cで宣言されています。

 32 LIST_HEAD(net_namespace_list);
 33 EXPORT_SYMBOL_GPL(net_namespace_list);

最後にロックを解放して、初期化に失敗していたらUser Namespaceの参照カウントを減らし、作ったnet構造体を破棄してエラーを返して終了です。

373         mutex_unlock(&net_mutex);
374         if (rv < 0) {
375                 put_user_ns(user_ns);
376                 net_drop_ns(net);
377                 return ERR_PTR(rv);
378         }

初期化に成功していたら作成したnet構造体を返します。

379         return net;
380 }

次にsetup_net()を見ときます。といっても、関数の半分ほどはエラー処理なので全部は見ませんけども。あと、これと言った処理は特にありません。

272 static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
273 {
274         /* Must be called with net_mutex held */
275         const struct pernet_operations *ops, *saved_ops;
276         int error = 0;
277         LIST_HEAD(net_exit_list);
278 
279         atomic_set(&net->count, 1);
280         atomic_set(&net->passive, 1);
281         net->dev_base_seq = 1;
282         net->user_ns = user_ns;
283         idr_init(&net->netns_ids);
284         spin_lock_init(&net->nsid_lock);
285 

処理らしい処理ってこれくらいですね。

286         list_for_each_entry(ops, &pernet_list, list) {
287                 error = ops_init(ops, net);
288                 if (error < 0)
289                         goto out_undo;
290         }
291 out:
292         return error;

pernet_listはリストで、これもファイルの冒頭のほうで宣言されてます。

 24 /*
 25  *      Our network namespace constructor/destructor lists
 26  */
 27 
 28 static LIST_HEAD(pernet_list);
 29 static struct list_head *first_device = &pernet_list;

リストのデータはpernet_operations構造体です。

そして、ops_init()はこのような関数です。まず最初、98行目のif文で100行目に進む場合。これはnet_assign_generic()を呼んでますが、これは最初にnet_alloc()でメモリを確保したnet_generic構造体のgenのptrメンバ変数にdataを設定します。このときにgen->ptrに設定されているデータ数が多くて、配列を拡張する必要がある場合は配列の拡張処理が入ります。

 93 static int ops_init(const struct pernet_operations *ops, struct net *net)
 94 {
 95         int err = -ENOMEM;
 96         void *data = NULL;
 97 
 98         if (ops->id && ops->size) {
 99                 data = kzalloc(ops->size, GFP_KERNEL);
100                 if (!data)
101                         goto out;
102 
103                 err = net_assign_generic(net, *ops->id, data);
104                 if (err)
105                         goto cleanup;
106         }

次は見たままですね。pernet_operations構造体のinit()が設定されていたら、その関数を呼び出します。そして、エラーがなければ0を返して終了です。

107         err = 0;
108         if (ops->init)
109                 err = ops->init(net);
110         if (!err)
111                 return 0;

ここでpernet_operations構造体のinit()を設定しているところをいくつか適当に見てみます。これはIPv4のICMPのところです。net/ipv4/icmp.cでinit()とexit()を設定を設定しています。

1234 static struct pernet_operations __net_initdata icmp_sk_ops = {
1235        .init = icmp_sk_init,
1236        .exit = icmp_sk_exit,
1237 };

IPv4TCPnet/ipv4/tcp_ipv4.c)だとinit()、exit()、exit_batch()と3個の関数を設定しています。

2410 static struct pernet_operations __net_initdata tcp_sk_ops = {
2411        .init       = tcp_sk_init,
2412        .exit       = tcp_sk_exit,
2413        .exit_batch = tcp_sk_exit_batch,
2414 };

とまあ、こんな感じでプロトコル等に設定されているinit()を呼んでいきます。pernet_listの宣言のコメントに「Our network namespace constructor/destructor lists」とありますし、まさしくコンストラクタ・デストラクタですね。

改訂3版 サーバ/インフラエンジニア養成読本 (Software Design plus)

改訂3版 サーバ/インフラエンジニア養成読本 (Software Design plus)