iT邦幫忙

2024 iThome 鐵人賽

DAY 6
0
自我挑戰組

Linux Kernel 網路巡禮系列 第 6

探討 Linux Network Namespace 與 VFS、Proc 檔案系統之間的關聯 - 引言

  • 分享至 

  • xImage
  •  

從今天開始的幾天,我們將探索 Linux 中的 file descriptor、system call、虛擬檔案系統 (VFS)、proc 檔案系統,以及 Network namespace 之間的關係。

今天,我們會先從應用的角度來看看這些系統是如何被串聯在一起的,接著再深入介紹它們的實現細節。

Network Namespace 切換的例子

在第三天介紹網路命名空間時,我們就有提到一個例子。

int main() {
    int fd = open("/proc/123/ns/net", O_RDONLY | O_CLOEXEC);  // 打開Process 123 的 network namespace 檔案
    setns(fd, CLONE_NEWNET);  // 切換當前進程的 network namespace 為Process 123 的 namespace
    /* 執行其他操作 */
    return 0;
}

這個例子中,首先透過 open system call 打開 process 123 的 network namespace 檔案,然後拿到一個 file descriptor。接著,透過 setns system call 將當前 process 的 network namespace 切換到 process 123 所使用的 namespace。

這裡引出幾個問題:

  1. proc 檔案系統是如何輸出 process 的資訊?
  2. /proc/123/ns/net 這個檔案是如何與 Network namespace 關聯的?
  3. setns 如何從 file descriptor 知道要切換到哪個 Network namespace?
  4. 切換 process 的 Network namespace,實際上改變了哪些系統層面的設定?

ip netns exec 指令解析

另一個常見的例子是 ip netns exec 指令。我們通常會先使用 ip netns add 指令建立一個新的 Network namespace,然後使用 ip netns exec 指令進入該 namespace,執行特定命令。

ip netns add net1
ip netns exec net1 <command> ...

我們一樣透過追蹤 ip 指令的程式碼,了解 ip 指令是如何實現的 namespace 切換的。ip 指令是在 iproute2 這個專案中。

// iproute2: ip/ipnetns.c
static int netns_exec(int argc, char **argv)
{
    fork();
	do_switch(arg[0]);
	execvp(cmd, argv)
}

static int do_switch(void *arg)
{
	char *netns = arg;
	vrf_reset();
	return netns_switch(netns);
}

以上是簡化後的程式碼,netns_exec 的邏輯非常簡單,首先 fork 建立一個新的 process,接著透過 do_switch 函數將這個新 process 切換到指定的 Network namespace,然後再使用execvp函數執行命令。這時候 do_switch 函數接收的是我們取的namespace的名子 ("net1")。

接著我們來看看 do_switch 呼叫的 netns_switch 函數的具體實現:

// iproute2: lib/namespace.c
int netns_switch(char *name)
{
	char net_path[PATH_MAX];
	int netns_fd;

    /* 重點 */
	snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name);
	netns_fd = open(net_path, O_RDONLY | O_CLOEXEC);
    setns(netns_fd, CLONE_NEWNET);
	close(netns_fd);
	/* 重點 */

	unshare(CLONE_NEWNS);
    mount("", "/", "none", MS_SLAVE | MS_REC, NULL);
    umount2("/sys", MNT_DETACH);
    mount(name, "/sys", "sysfs", mountflags, NULL)
	bind_etc(name);
	return 0;
}

netns_switch 的邏輯比較複雜,除了切換 network namespace 以外,也還需要對檔案系統做一些處理,但我們就只關注 network namespace 切換的部分。和前面的範例一樣,netns_switch 打開了 /var/run/netns/<namespace name> 檔案,並使用 setns system call 切換到該 namespace

不過這時候的檔案就不是 /proc 下面的檔案了,而是 NETNS_RUN_DIR 下面的檔案,也就是 /var/run/netns 這個路徑。

經常使用 ip netns 指令的人都知道,/var/run/netnsip netns 保存建立的 network namespace 檔案的路徑,所以 ip netns exec net1 就是打開 /var/run/netns/net1

另外像是,如果要使用 ip netns exec 指令去訪問一個 docker containernetwork namespace,有兩種方式可以將 network namespace 檔案複製到 /var/run/netns 中,並進行操作:

  1. 複製 SandboxKey 檔案: 透過 docker inspect 找到 container 的 SandboxKey,其指向的檔案位於 /var/run/docker/netns/,我們可以將該檔案複製到 /var/run/netns 中進行操作。
> docker inspect xxxxx
{
...
    "SandboxKey": "/var/run/docker/netns/d0358da4a049",
...
}

> ln -sfT /var/run/docker/netns/d0358da4a049 /var/run/netns/d0358da4a049
> ip netns exec d0358da4a049 <command> ...

  1. 建立連結: 另一種方法是找到 container 執行的 process ID,然後將 /proc/$pid/ns/net link 到 /var/run/netns/ 中,這樣也可以透過 ip netns 指令進行操作。
pid=$(docker inspect -f '{{.State.Pid}}' ${container_id})
mkdir -p /var/run/netns/
ln -sfT /proc/$pid/ns/net /var/run/netns/$container_id

ip netns add 的實現

本質上來說,ip netns add 之後建立出來的 /var/run/netns/<name> 檔案,其實也是從 /proc 來的。我們可以追蹤一下 ip netns add 的 source code:

static int netns_add(int argc, char **argv, bool create)
{
    ...
	snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name);
	...
	unshare(CLONE_NEWNET)
    ...
    strcpy(proc_path, "/proc/self/ns/net");
	...
	mount(proc_path, netns_path, "none", MS_BIND, NULL);
	...
}

首先,netns_path 一樣被設置為 /var/run/netns/<name>,接著呼叫 unshare 函數。unshare system call 會建立一個新的 network namespace,同時自身這個 process 會進入新的這個 network namespace,所以 /proc/self/ns/net 會指向到新的 network namespace。接著透過 mount bind 的方式,將 /proc/self/ns/net 掛載到 /var/run/netns/<name>

因此,可以得知 ip netns add 產生的 namespace 檔案也是從 /proc 來的,這讓我們了解了 proc 在管理 network namespace 中的重要角色。

總結

透過這幾個範例,我們可以看到 Linux 系統如何透過 proc 檔案系統與利用 system call(例如 opensetnsunshare)來進行 network namespace 的切換與管理。

接下來的幾天,我們會繼續深入探討這些系統底層的運作機制。


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

尚未有邦友留言

立即登入留言