Network Namespace
Network Namespace 是 Linux 核心提供的功能,是實現網路虛擬化的重要功能,它能建立多個隔離的網路空間,它們有獨自網路棧資訊。不管是虛擬機器還是容器,執行的時候彷彿自己都在獨立的網路中。而且不同Network Namespace的資源相互不可見,彼此之間無法通訊
ip netns命令
可以藉助ip netns
命令來完成對 Network Namespace 的各種操作。ip netns
命令來自於iproute2
安裝包,一般系統會預設安裝,如果沒有的話,讀者自行安裝。
注意:ip netns
命令修改網路配置時需要 sudo 許可權。
可以通過ip netns
命令完成對Network Namespace 的相關操作,可以通過ip netns help
檢視命令幫助資訊:
$ ip netns help
Usage: ip netns list
ip netns add NAME
ip netns set NAME NETNSID
ip [-all] netns delete [NAME]
ip netns identify [PID]
ip netns pids NAME
ip [-all] netns exec [NAME] cmd ...
ip netns monitor
ip netns list-id
預設情況下,Linux系統中是沒有任何 Network Namespace的,所以ip netns list
命令不會返回任何資訊。
建立Network Namespace
下面,我們通過命令建立一個名為ns0
的名稱空間:
$ ip netns add ns0
$ ip netns list
ns0
新建立的 Network Namespace 會出現在/var/run/netns/
目錄下。如果相同名字的 namespace 已經存在,命令會報Cannot create namespace file "/var/run/netns/ns0": File exists
的錯誤。
對於每個 Network Namespace 來說,它會有自己獨立的網路卡、路由表、ARP 表、iptables 等和網路相關的資源。
操作Network Namespace
ip
命令提供了ip netns exec
子命令可以在對應的 Network Namespace 中執行命令。
- 檢視新建立 Network Namespace 的網路卡資訊
$ ip netns exec ns0 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
可以看到,新建立的Network Namespace中會預設建立一個lo
迴環網路卡,此時網路卡處於關閉
狀態。此時,嘗試去 ping 該lo
迴環網路卡,會提示Network is unreachable
$ ip netns exec ns0 ping 127.0.0.1
connect: Network is unreachable
通過下面的命令啟用lo
迴環網路卡:
ip netns exec ns0 ip link set lo up
然後再次嘗試去 ping 該lo
迴環網路卡:
$ ip netns exec ns0 ping -c 3 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.048 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.031 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.029 ms
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.029/0.036/0.048/0.008 ms
轉移裝置
我們可以在不同的 Network Namespace 之間轉移裝置(如veth)。由於一個裝置只能屬於一個 Network Namespace ,所以轉移後在這個 Network Namespace 內就看不到這個裝置了。
其中,veth裝置屬於可轉移裝置,而很多其它裝置(如lo、vxlan、ppp、bridge等)是不可以轉移的。
veth pair
veth pair 全稱是 Virtual Ethernet Pair,是一個成對的埠,所有從這對埠一 端進入的資料包都將從另一端出來,反之也是一樣。
引入veth pair是為了在不同的 Network Namespace 直接進行通訊,利用它可以直接將兩個 Network Namespace 連線起來。
整個veth的實現非常簡單,有興趣的讀者可以參考原始碼drivers/net/veth.c
的實現。
veth pair
建立veth pair
$ sudo ip link add type veth
$ ip addr
61: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether e6:39:e1:e0:3a:a0 brd ff:ff:ff:ff:ff:ff
62: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether be:41:49:42:23:6a brd ff:ff:ff:ff:ff:ff
可以看到,此時系統中新增了一對veth pair,將veth0和veth1兩個虛擬網路卡連線了起來,此時這對 veth pair 處於”未啟用“狀態。
如果我們想指定 veth pair 兩個端點的名稱,可以使用下面的命令:
ip link add vethfoo type veth peer name vethbar
實現Network Namespace間通訊
下面我們利用veth pair實現兩個不同的 Network Namespace 之間的通訊。剛才我們已經建立了一個名為ns0
的 Network Namespace,下面再建立一個資訊Network Namespace,命名為ns1
$ ip netns add ns1
$ ip netns list
ns1
ns0
然後我們將veth0加入到ns0,將veth1加入到ns1,如下所示:
$ ip link set veth0 netns ns0
$ ip link set veth1 netns ns1
然後我們分別為這對veth pair配置上ip地址,並啟用它們:
$ ip netns exec ns0 iplink set veth0 up
$ ip netns exec ns0 ip addr add 10.0.1.1/24 dev veth0
$ ip netns exec ns1 iplink set veth1 up
$ ip netns exec ns1 ip addr add 10.0.1.2/24 dev veth1
檢視這對veth pair的狀態
$ ip netns exec ns0 ip addr
61: veth0@if62: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether e6:39:e1:e0:3a:a0 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 10.0.1.1/24 scope global veth0
valid_lft forever preferred_lft forever
inet6 fe80::e439:e1ff:fee0:3aa0/64 scope link
valid_lft forever preferred_lft forever
$ ip netns exec ns1 ip addr
62: veth1@if61: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether be:41:49:42:23:6a brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.1.2/24 scope global veth1
valid_lft forever preferred_lft forever
inet6 fe80::bc41:49ff:fe42:236a/64 scope link
valid_lft forever preferred_lft forever
從上面可以看出,我們已經成功啟用了這個veth pair,併為每個veth裝置分配了對應的ip地址。我們嘗試在ns1
中訪問ns0
中的ip地址:
$ ip netns exec ns1 ping -c 3 10.0.1.1
sudo: unable to resolve host zormshogu
PING 10.0.1.1 (10.0.1.1) 56(84) bytes of data.
64 bytes from 10.0.1.1: icmp_seq=1 ttl=64 time=0.091 ms
64 bytes from 10.0.1.1: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 10.0.1.1: icmp_seq=3 ttl=64 time=0.037 ms
--- 10.0.1.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.035/0.054/0.091/0.026 ms
可以看到,veth pair成功實現了兩個不同Network Namespace之間的網路互動。
veth檢視對端
一旦將veth pair的peer段放入另一個Network Namespace,我們在當前Namespace中就看不到它了。那麼,我們怎麼才能知道這個veth pair的對端在哪裡呢?
可以通過ethtool
工具來檢視(當Network Namespace很多時,操作會比較麻煩):
$ ip netns exec ns1 ethtool -S veth1
NIC statistics:
peer_ifindex: 5
得知另一端的介面裝置序列號是5,我們再到另一個名稱空間中檢視序列號5代表什麼裝置:
$ ip netns exec ns0 ip link | grep 5
veth0
網橋
veth pair打破了 Network Namespace 的限制,實現了不同 Network Namespace 之間的通訊。但veth pair有一個明顯的缺陷,就是隻能實現兩個網路介面之間的通訊。
如果我們想實現多個網路介面之間的通訊,就可以使用下面介紹的網橋(Bridge)技術。
簡單來說,網橋就是把一臺機器上的若干個網路介面“連線”起來。其結果是,其中一個網口收到的報文會被複制給其他網口併傳送出去。以使得網口之間的報文能夠互相轉發。
網橋的工作原理
網橋對報文的轉發基於MAC地址。網橋能夠解析收發的報文,讀取目標MAC地址的資訊,和自己記錄的MAC表結合,來決策報文的轉發目標網口。
為了實現這些功能,網橋會學習源MAC地址,在轉發報文時,網橋只需要向特定的網口進行轉發,從而避免不必要的網路互動。
如果它遇到一個自己從未學習到的地址,就無法知道這個報文應該向哪個網口轉發,就將報文廣播給所有的網口(報文來源的網口除外)。
網橋
網橋的實現
Linux核心是通過一個虛擬的網橋裝置(Net Device)來實現橋接的。這個虛擬裝置可以繫結若干個乙太網介面裝置,從而將它們橋接起來。如下圖所示:
網橋的位置
如上圖所示,網橋裝置 br0 繫結了 eth0 和 eth1。
對於網路協議棧的上層來說,只看得到 br0,上層協議棧需要傳送的報文被送到 br0,網橋裝置的處理程式碼判斷報文該被轉發到 eth0 還是 eth1,或者兩者皆轉發;反過來,從eth0 或 eth1 接收到的報文被提交給網橋的處理程式碼,在這裡會判斷報文應該被轉發、丟棄還是提交到協議棧上層。
而有時eth0、eth1 也可能會作為報文的源地址或目的地址,直接參與報文的傳送與接收,從而繞過網橋。
brctl
和網橋有關的操作可以使用命令 brctl,這個命令來自 bridge-utils 這個包。
- 建立網橋
# 建立網橋
brctl addbr br0
- 刪除網橋
# 刪除網橋
brctl delbr br0
- 繫結網口
建立一個邏輯網段之後,我們還需要為這個網段分配特定的埠。在Linux 中,一個埠實際上就是一個物理或虛擬網路卡。而每個網路卡的名稱則分別為eth0 ,eth1 ,eth2 。我們需要把每個網路卡一一和br0 這個網段聯絡起來,作為br0 中的一個埠。
# 讓eth0 成為br0 的一個埠
brctl addif br0 eth0
# 讓eth1 成為br0 的一個埠
brctl addif br0 eth1
# 讓eth2 成為br0 的一個埠
brctl addif br0 eth2
iptables/netfilter
iptables是Linux實現的軟體防火牆,使用者可以通過iptables設定請求准入和拒絕規則,從而保護系統的安全。
我們也可以把iptables理解成一個客戶端代理,使用者通過iptables這個代理,將使用者安全設定執行到對應的安全框架中,這個“安全框架”才是真正的防火牆,這個框架的名字叫netfilter
。
iptables其實是一個命令列工具,位於使用者空間。
iptables/netfilter(以下簡稱iptables)組成了Linux平臺下的包過濾防火牆,可以完成封包過濾、封包重定向和網路地址轉換(NAT)等功能。
訊息處理鏈
iptables不僅要處理本機接收到的訊息,也要處理本機發出的訊息。這些訊息需要經過一系列的”關卡“才能被本機應用層接收,或者從本機發出,每個”關卡“擔負著不同的工作。這裡的”關卡“被稱為”鏈“。
INPUT
:進來的資料包應用此規則鏈中的策規則;OUTPUT
:外出的資料包應用此規則鏈中的規則;FORWARD
:轉發資料包時應用此規則鏈中的規則;PREROUTING
:對資料包作路由選擇前應用此鏈中的規則(所有的資料包進來的時侯都先由這個鏈處理);POSTROUTING
:對資料包作路由選擇後應用此鏈中的規則(所有的資料包出來的時侯都先由這個鏈處理);
資料包經過各個鏈的處理過程大致如下圖所示:
訊息處理鏈
規則表
從上面我們知道,iptables是按照規則來辦事的,這些規則就是網路管理員預定義的條件。規則一般的定義為:如果資料包頭符合這樣的條件,就這樣處理“。這些規則並不是嚴格按照新增順序排列在一張規則表中,而是按照功能進行分類,儲存在不同的表中,每個表儲存一類規則:
- Filter
主要用來過濾資料,用來控制讓哪些資料可以通過,哪些資料不能通過,它是最常用的表。 - NAT
用來處理網路地址轉換的,控制要不要進行地址轉換,以及怎樣修改源地址或目的地址,從而影響資料包的路由,達到連通的目的。 - Mangle
主要用來修改IP資料包頭,比如修改TTL值,同時也用於給資料包新增一些標記,從而便於後續其它模組對資料包進行處理(這裡的新增標記是指往核心skb結構中新增標記,而不是往真正的IP資料包上加東西)。 - Raw
在Netfilter裡面有一個叫做連結跟蹤的功能,主要用來追蹤所有的連線,而raw表裡的rule的功能是給資料包打標記,從而控制哪些資料包不做連結跟蹤處理,從而提高效能;優先順序最高
。
表和鏈的關係
表和鏈共同完成了iptables對資料包的處理。但並不是每個鏈都包含所有型別的表,所以,有些鏈是天生不具備某些功能的。就像我們去車站乘車的時候,”關卡A“只負責檢查身份證,”B關卡”只負責檢查行李,而“C關卡”功能比較齊全,即負責檢查身份證,又負責檢查行李。二者的關係如下圖所示:
表和鏈的關係
總結
今天我們共同學習了一些常見的Linux虛擬網路技術。其中,Linux通過Network Namespace實現了網路的隔離,使網路協議棧之間互不干擾;並通過veth pair和網橋實現了相同主機上多個Network Namespace之間的資料通訊;iptables則可以幫助我們實現網路安全和資料包的路由轉發功能,從而使主機和主機、容器與容器、容器和宿主機之間可以相互收發訊息。在這些技術的共同協作下,才有了現在安全、穩定的虛擬網路。
本作品採用《CC 協議》,轉載必須註明作者和本文連結