iT邦幫忙

2024 iThome 鐵人賽

DAY 4
0
自我挑戰組

Linux Kernel 網路巡禮系列 第 4

網路命名空間的建立

  • 分享至 

  • xImage
  •  

昨天我們簡單介紹了 net 結構與 network namespace 的關係。在我們使用 ip 指令或 docker 等工具來建立新的 network namespace 之前,所有的進程都位於一個初始的 network namespace 中。今天我們要探討這個初始的 network namespace 是如何出現的。

Operations 機制

除了網路子系統本身,其他的 Kernel 模組,甚至是我們自己撰寫的 Kernel module,也可能需要在 network namespace 被建立或刪除時執行某些操作。為此,Linux Kernel 設計了一個機制來實現這樣的需求。

// include/net/net_namespace.h
struct pernet_operations {
	struct list_head list;

	int (*init)(struct net *net);
	void (*pre_exit)(struct net *net);
	void (*exit)(struct net *net);
	void (*exit_batch)(struct list_head *net_exit_list);
	unsigned int *id;
	size_t size;
};

Linux Kernel 定義了 pernet_operations 結構,裡面包含了 initexit 等函數指標,讓 Kernel 模組可以自行撰寫初始化和結束函數,並向網路子系統透過 register_pernet_subsysregister_pernet_device 來進行註冊。兩者的差異在於,subsys 的優先度較高,因此所有 subsysinit 函數會先被執行,接著才會執行 device 的函數。

pernet_operations 的定義中,我們可以看到熟悉的 list_head,這表示 pernet_operations 是以 list 的方式維護在網路子系統中的。

static int __init net_dev_init(void) // 網路設備子系統初始化
{
    ...
    if (register_pernet_device(&loopback_net_ops)) // 向網路命名空間子系統註冊 loopback_net_ops
		goto out;
    ...
}

// drivers/net/loopback.c
struct pernet_operations __net_initdata loopback_net_ops = {
	.init = loopback_net_init,
};

static __net_init int loopback_net_init(struct net *net)
{
    struct net_device *dev;
    ...
    dev = alloc_netdev(0, "lo", NET_NAME_PREDICTABLE, loopback_setup); // 建立loopback inteface
    ...
    dev_net_set(dev, net);
	err = register_netdev(dev);
	net->loopback_dev = dev;
}

這邊舉一個例子,當 network namespace 被建立時,網路子系統會透過註冊一個處理函數 loopback_net_ops 來建立 loopback 介面。

初始 Network Namespace

理解了 pernet_operations 的運作方式後,我們可以開始探索第一個 network namespace 的初始化過程。

第一個 network namespace 被稱為 init_net,它同樣也是對應到一個 net 結構。不同於其他透過動態分配方式產生的 network namespaceinit_net 是直接宣告在 Linux Kernel 的原始碼中。

// net/core/net_namespace.c#L48
struct net init_net;

接著我們就要來看init_net是怎麼被初始化的。開機後,當Linux被載入開始執行後,會執行start_kernel函數,該函數會依序呼叫各個子系統的方法來初始化Linux kernel。

// source/init/main.c
asmlinkage __visible __init __no_sanitize_address __noreturn __no_stack_protector
void start_kernel(void)
{
    ...
    sched_init();
    ...
    init_IRQ();
    softirq_init();    
    ...
    net_ns_init(); // 在這裡初始化網路命名空間
    ...
    signals_init();
}

那 net_ns_init 的工作當然就是要來初始化 init_net結構的數值拉。

// net/core/net_namespace.c
void __init net_ns_init(void) {
    ...
    if (setup_net(&init_net, &init_user_ns))
		panic("Could not setup the initial network namespace");
	...
	init_net_initialized = true;
    if (register_pernet_subsys(&net_ns_ops))
		panic("Could not register network namespace subsystems");
	...
}

可以看到 net_ns_init 函數呼叫了 setup_net,並將 init_net 作為參數。setup_net 是初始化 net 結構的通用函數,無論是 init_net 還是後續建立的 network namespace,都會使用該函數來進行初始化。完成後,init_net_initialized 會被設為 true。此外,net_ns_init 還註冊了一組由網路子系統自己定義的 pernet_operations,即 net_ns_ops

// net/core/net_namespace.c
static struct pernet_operations __net_initdata net_ns_ops = {
	.init = net_ns_net_init,
	.exit = net_ns_net_exit,
};

static __net_init int net_ns_net_init(struct net *net)
{
#ifdef CONFIG_NET_NS
	net->ns.ops = &netns_operations;
#endif
	return ns_alloc_inum(&net->ns);
}

昨天提到net->nsns_common 結構體,而 net->ns.inum 是該 namespace 的唯一識別碼。net_ns_ops 會負責初始化 net->ns,其中,透過 ns_alloc_inum 函數來設置 net->ns.inum

// net/core/net_namespace.c
/*
 * setup_net runs the initializers for the network namespace object.
 */
static __net_init int setup_net(struct net *net, struct user_namespace *user_ns
{
    const struct pernet_operations *ops, *saved_ops;
    ...
    list_for_each_entry(ops, &pernet_list, list) {
        error = ops_init(ops, net); // ops->init ? ops->init(ops, net) : 0
        if (error < 0)
            goto out_undo;
    }
    ...
}

接著我們看到 setup_net 函數,可以看到他使用了一個特殊的list_for_each_entry(ops, &pernet_list, list) macro,這個跟container_of相關,是專門針對 list_head 結構的 macro,功能就是遍歷一個list_head的list,然後把值拿出來用。

pernet_list 儲存了所有註冊的 pernet_operations,因此 setup_net 會依次取出每個 pernet_operations,並呼叫其 init 函數來完成初始化。

結論

今天我們探討了 Linux 網路子系統如何透過 pernet_operations 提供一個可擴充的 network namespace 初始化機制,以及第一個 network namespace——init_net 是如何生成的。


上一篇
網路命名空間介紹
下一篇
番外篇 (1) - 網路命名空間 ID
系列文
Linux Kernel 網路巡禮30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言