首頁 > 軟體

介紹 Linux 的名稱空間

2020-06-16 18:04:58

背景

從Linux 2.6.24版的核心開始,Linux 就支援6種不同型別的名稱空間。它們的出現,使使用者建立的進程能夠與系統分離得更加徹底,從而不需要使用更多的底層虛擬化技術。

  • CLONE_NEWIPC: 進程間通訊(IPC)的名稱空間,可以將 SystemV 的 IPC 和 POSIX 的訊息佇列獨立出來。
  • CLONE_NEWPID: 進程名稱空間。空間內的PID 是獨立分配的,意思就是名稱空間內的虛擬 PID 可能會與名稱空間外的 PID 相衝突,於是名稱空間內的 PID 對映到名稱空間外時會使用另外一個 PID。比如說,名稱空間內第一個 PID 為1,而在名稱空間外就是該 PID 已被 init 進程所使用。
  • CLONE_NEWNET: 網路名稱空間,用於隔離網路資源(/proc/net、IP 地址、網絡卡、路由等)。後台進程可以執行在不同名稱空間內的相同埠上,使用者還可以虛擬出一塊網絡卡。
  • CLONE_NEWNS: 掛載名稱空間,進程執行時可以將掛載點與系統分離,使用這個功能時,我們可以達到 chroot 的功能,而在安全性方面比 chroot 更高。
  • CLONE_NEWUTS: UTS 名稱空間,主要目的是獨立出主機名和網路資訊服務(NIS)。
  • CLONE_NEWUSER: 使用者名稱空間,同進程 ID 一樣,使用者 ID 和組 ID 在名稱空間內外是不一樣的,並且在不同名稱空間內可以存在相同的 ID。

下面我們介紹一下進程名稱空間和網路名稱空間。

(題圖來自:fotothing.com)

進程名稱空間

本文用 C 語言介紹上述概念,因為演示進程名稱空間的時候需要用到 C 語言。下面的測試過程在 Debian 6 和 Debian 7 上執行。首先,在棧內分配一頁記憶體空間,並將指標指向記憶體頁的末尾。這裡我們使用 alloca() 函數來分配記憶體,不要用 malloc() 函數,它會把記憶體分配在堆上。

  1. void*mem = alloca(sysconf(_SC_PAGESIZE))+ sysconf(_SC_PAGESIZE);

然後使用 clone() 函數建立子進程,傳入我們的子棧空間地址 "mem",並指定名稱空間的標記。同時我們還指定“callee”作為子進程執行的函數。

  1. mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);

clone 之後我們要在父進程中等待子進程先退出,否則的話,父進程會繼續執行下去,並馬上進程結束,留下子進程變成孤兒進程:

  1. while(waitpid(mypid,&r,0)<0&& errno == EINTR)
  2. {
  3. continue;
  4. }

最後當子進程退出後,我們會回到 shell 介面,並返回子進程的退出碼。

  1. if(WIFEXITED(r))
  2. {
  3. return WEXITSTATUS(r);
  4. }
  5. return EXIT_FAILURE;

上文介紹的 callee 函數功能如下:

  1. staticint callee()
  2. {
  3. int ret;
  4. mount("proc","/proc","proc",0,"");
  5. setgid(u);
  6. setgroups(0, NULL);
  7. setuid(u);
  8. ret = execl("/bin/bash","/bin/bash", NULL);
  9. return ret;
  10. }

程式掛載了 /proc 檔案系統,設定使用者 ID 和組 ID,值都為“u”,然後執行 /bin/bash 程式,LXC 是一個作業系統級的虛擬化工具,使用 cgroups 和名稱空間來完成資源的分離。現在我們把所有程式碼放在一起,變數“u”的值設為65534,在 Debian 系統中,這是“nobody”和“nogroup”:

  1. #define _GNU_SOURCE
  2. #include<unistd.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<sys/types.h>
  6. #include<sys/wait.h>
  7. #include<sys/mount.h>
  8. #include<grp.h>
  9. #include<alloca.h>
  10. #include<errno.h>
  11. #include<sched.h>
  12. staticint callee();
  13. constint u =65534;
  14. int main(int argc,char*argv[])
  15. {
  16. int r;
  17. pid_t mypid;
  18. void*mem = alloca(sysconf(_SC_PAGESIZE))+ sysconf(_SC_PAGESIZE);
  19. mypid = clone(callee, mem, SIGCHLD | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | CLONE_FILES, NULL);
  20. while(waitpid(mypid,&r,0)<0&& errno == EINTR)
  21. {
  22. continue;
  23. }
  24. if(WIFEXITED(r))
  25. {
  26. return WEXITSTATUS(r);
  27. }
  28. return EXIT_FAILURE;
  29. }
  30. staticint callee()
  31. {
  32. int ret;
  33. mount("proc","/proc","proc",0,"");
  34. setgid(u);
  35. setgroups(0, NULL);
  36. setuid(u);
  37. ret = execl("/bin/bash","/bin/bash", NULL);
  38. return ret;
  39. }

執行以下命令來執行上面的程式碼:

  1. root@www.linuxidc.com:~/pen/tmp# gcc -O -o ns.c -Wall -Werror -ansi -c89 ns.c
  2. root@www.linuxidc.com:~/pen/tmp# ./ns
  3. nobody@w:~/pen/tmp$ id
  4. uid=65534(nobody) gid=65534(nogroup)
  5. nobody@w:~/pen/tmp$ ps auxw
  6. USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
  7. nobody 10.00.046201816 pts/1 S 21:210:00/bin/bash
  8. nobody 50.00.027841064 pts/1 R+21:210:00 ps auxw
  9. nobody@w:~/pen/tmp$

注意上面的結果,UID 和 GID 被設定成 nobody 和 nogroup 了,特別是 ps 工具只輸出兩個進程,它們的 ID 分別是1和5(LCTT注:這就是上文介紹 CLONE_NEWPID 時提到的功能,線上程所在的名稱空間內,進程 ID 可以為1,對映到名稱空間外是另外一個 PID;而名稱空間外的 ID 為1的進程一直是 init)。

網路名稱空間

接下來輪到使用 ip netns 來設定網路的名稱空間。第一步先確定當前系統沒有名稱空間:

  1. root@www.linuxidc.com:~# ip netns list
  2. Object"netns"is unknown,try"ip help".

如果報了上述錯誤,你需要更新你的系統核心,以及 ip 工具程式。這裡假設你的核心版高於2.6.24,ip 工具版本也差不多,高於2.6.24(LCTT注:ip 工具由 iproute 安裝包提供,此安裝包版本與核心版本相近)。更新好後,ip netns list 在沒有名稱空間存在的情況下不會輸出任務資訊。加個名為“ns1”的名稱空間看看:

  1. root@www.linuxidc.com:~# ip netns add ns1
  2. root@www.linuxidc.com:~# ip netns list
  3. ns1

列出網絡卡:

  1. root@www.linuxidc.com:~# ip link list
  2. 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  4. 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000
  5. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff

建立新的虛擬網絡卡,並加到名稱空間。虛擬網絡卡需要成對建立,互相關聯——就像交叉電纜一樣:

  1. root@www.linuxidc.com:~# ip link add veth0 type veth peer name veth1
  2. root@www.linuxidc.com:~# ip link list
  3. 1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT qlen 1000
  6. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
  7. 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
  8. link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff
  9. 4: veth0: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
  10. link/ether f2:f7:5e:e2:22:ac brd ff:ff:ff:ff:ff:ff

這個時候 ifconfig -a 命令也能顯示新新增的 veth0 和 veth1 兩塊網絡卡。

很好,現在將這兩份塊網絡卡加到名稱空間中去。注意一下,下面的 ip netns exec 命令用於將後面的命令在名稱空間中執行(LCTT注:下面的結果顯示了在 ns1 這個網路名稱空間中,只存在 lo 和 veth1 兩塊網絡卡):

  1. root@www.linuxidc.com:~# ip link set veth1 netns ns1
  2. root@www.linuxidc.com:~# ip netns exec ns1 ip link list
  3. 1: lo: mtu 65536 qdisc noop state DOWN mode DEFAULT
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. 3: veth1: mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
  6. link/ether d2:e9:52:18:19:ab brd ff:ff:ff:ff:ff:ff

這個時候 ifconfig -a 命令只能顯示 veth0,不能顯示 veth1,因為後者現在在 ns1 名稱空間中。

如果想刪除 veth0/veth1,可以執行下面的命令:

  1. ip netns exec ns1 ip link del veth1

我們可以為 veth0 分配 IP 地址:

  1. ifconfig veth0 192.168.5.5/24

在名稱空間內為 veth1 分配 IP 地址:

  1. ip netns exec ns1 ifconfig veth1 192.168.5.10/24 up

在名稱空間內外執行 ip addr list 命令:

  1. root@www.linuxidc.com:~# ip addr list
  2. 1: lo: mtu 65536 qdisc noqueue state UNKNOWN
  3. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  4. inet 127.0.0.1/8 scope host lo
  5. inet6 ::1/128 scope host
  6. valid_lft forever preferred_lft forever
  7. 2: eth0: mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000
  8. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
  9. inet 192.168.3.122/24 brd 192.168.3.255 scope global eth0
  10. inet6 fe80::20c:29ff:fe65:259e/64 scope link
  11. valid_lft forever preferred_lft forever
  12. 6: veth0: mtu 1500 qdisc pfifo_fast state UP qlen 1000
  13. link/ether 86:b2:c7:bd:c9:11 brd ff:ff:ff:ff:ff:ff
  14. inet 192.168.5.5/24 brd 192.168.5.255 scope global veth0
  15. inet6 fe80::84b2:c7ff:febd:c911/64 scope link
  16. valid_lft forever preferred_lft forever
  17. root@www.linuxidc.com:~# ip netns exec ns1 ip addr list
  18. 1: lo: mtu 65536 qdisc noop state DOWN
  19. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  20. 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000
  21. link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff
  22. inet 192.168.5.10/24 brd 192.168.5.255 scope global veth1
  23. inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link
  24. valid_lft forever preferred_lft forever

在名稱空間內外檢視路由表:

  1. root@www.linuxidc.com:~# ip route list
  2. default via 192.168.3.1 dev eth0 proto static
  3. 192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.122
  4. 192.168.5.0/24 dev veth0 proto kernel scope link src 192.168.5.5
  5. root@www.linuxidc.com:~# ip netns exec ns1 ip route list
  6. 192.168.5.0/24 dev veth1 proto kernel scope link src 192.168.5.10

最後,將虛擬網絡卡連到物理網絡卡上,我們需要用到橋接。這裡做的是將 veth0 橋接到 eth0,而 ns1 名稱空間內則使用 DHCP 自動獲取 IP 地址:

  1. root@www.linuxidc.com:~# brctl addbr br0
  2. root@w:~# brctl addif br0 eth0
  3. root@www.linuxidc.com:~# brctl addif br0 veth0
  4. root@www.linuxidc.com:~# ifconfig eth0 0.0.0.0
  5. root@www.linuxidc.com:~# ifconfig veth0 0.0.0.0
  6. root@www.linuxidc.com:~# dhclient br0
  7. root@w:~# ip addr list br0
  8. 7: br0: mtu 1500 qdisc noqueue state UP
  9. link/ether 00:0c:29:65:25:9e brd ff:ff:ff:ff:ff:ff
  10. inet 192.168.3.122/24 brd 192.168.3.255 scope global br0
  11. inet6 fe80::20c:29ff:fe65:259e/64 scope link
  12. valid_lft forever preferred_lft forever

為網橋 br0 分配的 IP 地址為192.168.3.122/24。接下來為名稱空間分配地址:

  1. root@www.linuxidc.com:~# ip netns exec ns1 dhclient veth1
  2. root@www.linuxidc.com:~# ip netns exec ns1 ip addr list
  3. 1: lo: mtu 65536 qdisc noop state DOWN
  4. link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
  5. 5: veth1: mtu 1500 qdisc pfifo_fast state UP qlen 1000
  6. link/ether 12:bd:b6:76:a6:eb brd ff:ff:ff:ff:ff:ff
  7. inet 192.168.3.248/24 brd 192.168.3.255 scope global veth1
  8. inet6 fe80::10bd:b6ff:fe76:a6eb/64 scope link
  9. valid_lft forever preferred_lft forever

現在, veth1 的 IP 被設定成 192.168.3.248/24 了。


IT145.com E-mail:sddin#qq.com