linux的虛擬網絡技術
文章目錄
前言
- 近幾年,Docker、Kubernetes等容器化技術和容器編排工具的興起使技術人員從應用部署和維護的泥淖中解脫出來,同時也改變了很多很多互聯網公司的技術架構。筆者近期也在學習Docker和Kubernetes,對這些新技術所帶來的便捷性和安全性非常着迷,其中尤其對容器化技術的網絡實現方式更爲好奇。今天就把近期的對Linux虛擬網絡技術的學習成果分享出來,希望能和大家一起交流學習。
1. Network namespace
- Network Namespace 是 Linux 內核提供的功能,是實現網絡虛擬化的重要功能,它能創建多個隔離的網絡空間,它們有獨自網絡棧信息。不管是虛擬機還是容器,運行的時候彷彿自己都在獨立的網絡中。而且不同Network Namespace的資源相互不可見,彼此之間無法通信。
2. ip netns命令
- 可以藉助
ip netns
命令來完成對 Network Namespace 的各種操作。ip netns
命令來自於iproute2
安裝包,一般系統會默認安裝,如果沒有的話,讀者自行安裝。 - 可以通過
ip netns
命令完成對Network Namespace 的相關操作,可以通過ip netns help
查看命令幫助信息:
[root@localhost ~]# 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
命令不會返回任何信息。
[root@localhost ~]# ip netns list
[root@localhost ~]#
- 創建network namespace
- 創建一個love的命名空間
[root@localhost ~]# ip netns add love
[root@localhost ~]# ip netns list
love
- 新創建的 Network Namespace 會出現在
/var/run/netns/
目錄下。如果相同名字的 namespace 已經存在,命令會報Cannot create namespace file "/var/run/netns/ns0": File exists
的錯誤。 - 對於每個 Network Namespace 來說,它會有自己獨立的網卡、路由表、ARP 表、iptables 等和網絡相關的資源。
- 操作network namespace
- 查看新創建 Network Namespace 的網卡信息
[root@localhost ~]# ip netns exec love 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
[root@localhost ~]# ip netns exec love ping 127.0.0.1
connect: Network is unreachable
- 啓用lo迴環網卡,並進行ping測試
[root@localhost ~]# ip netns exec love ip link set lo up
[root@localhost ~]# ip netns exec love ping 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.130 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.051 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.068 ms
^C
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 0.051/0.083/0.130/0.033 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
[root@localhost ~]# ip link add type veth
[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 00:0c:29:2d:60:55 brd ff:ff:ff:ff:ff:ff
inet 192.168.73.41/24 brd 192.168.73.255 scope global noprefixroute ens33
valid_lft forever preferred_lft forever
inet6 fe80::399f:dd76:3eb7:2c5b/64 scope link noprefixroute
valid_lft forever preferred_lft forever
3: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 52:54:00:82:d5:d2 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
valid_lft forever preferred_lft forever
4: virbr0-nic: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast master virbr0 state DOWN group default qlen 1000
link/ether 52:54:00:82:d5:d2 brd ff:ff:ff:ff:ff:ff
5: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 72:65:8f:0a:3e:f3 brd ff:ff:ff:ff:ff:ff
6: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether e2:42:9c:95:76:ea brd ff:ff:ff:ff:ff:ff
- 可以看到,此時系統中新增了一對veth pair,將veth0和veth1兩個虛擬網卡連接了起來,此時這對 veth pair 處於”未啓用“狀態。
- 如果我們想指定 veth pair 兩個端點的名稱,可以使用下面的命令:
ip link add vethfoo type veth peer name vethbar
3. 實現network namespace間的通信
- 下面我們利用veth pair實現兩個不同的 Network Namespace 之間的通信。剛纔我們已經創建了一個名爲love的 Network Namespace,下面再創建一個信息Network Namespace,命名爲you
[root@localhost ~]# ip netns list
you
love
- 然後我們將veth0加入到love,將veth1加入到you,如下所示:
[root@localhost ~]# ip link set veth0 netns love
[root@localhost ~]# ip link set veth1 netns you
- 然後我們分別爲這對veth pair配置上ip地址,並啓用它們:
[root@localhost ~]# ip netns exec love ip link set veth0 up
[root@localhost ~]# ip netns exec love ip addr add 192.168.73.51/24 dev veth0
[root@localhost ~]# ip netns exec you ip link set veth1 up
[root@localhost ~]# ip netns exec you ip addr add 192.168.73.52/24 dev veth1
- 查看veth pair的狀態
[root@localhost ~]# ip netns exec love ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
5: veth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 72:65:8f:0a:3e:f3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet 192.168.73.51/24 scope global veth0
valid_lft forever preferred_lft forever
inet6 fe80::7065:8fff:fe0a:3ef3/64 scope link
valid_lft forever preferred_lft forever
[root@localhost ~]# ip netns exec you 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
6: veth1@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether e2:42:9c:95:76:ea brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.73.52/24 scope global veth1
valid_lft forever preferred_lft forever
inet6 fe80::e042:9cff:fe95:76ea/64 scope link
valid_lft forever preferred_lft forever
- 從上面可以看出,我們已經成功啓用了這個veth pair,併爲每個veth設備分配了對應的ip地址。我們嘗試在
ns1
中訪問ns0
中的ip地址:
[root@localhost ~]# ip netns exec you ping -c 3 192.168.73.51
PING 192.168.73.51 (192.168.73.51) 56(84) bytes of data.
64 bytes from 192.168.73.51: icmp_seq=1 ttl=64 time=0.292 ms
64 bytes from 192.168.73.51: icmp_seq=2 ttl=64 time=0.056 ms
64 bytes from 192.168.73.51: icmp_seq=3 ttl=64 time=0.041 ms
--- 192.168.73.51 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.041/0.129/0.292/0.115 ms
4. veth 查看對端
- 一旦將veth pair的peer段放入另一個Network Namespace,我們在當前Namespace中就看不到它了。那麼,我們怎麼才能知道這個veth pair的對端在哪裏呢?
- 可以通過
ethtool
工具來查看(當Network Namespace很多時,操作會比較麻煩):
[root@localhost ~]# ip netns exec you ethtool -S veth1
NIC statistics:
peer_ifindex: 5
- 得知另一端的接口設備序列號是5,我們再到另一個命名空間中查看序列號5代表什麼設備:
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
5: veth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
link/ether 72:65:8f:0a:3e:f3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
5. 網橋
- veth pair打破了 Network Namespace 的限制,實現了不同 Network Namespace 之間的通信。但veth pair有一個明顯的缺陷,就是隻能實現兩個網絡接口之間的通信。
- 如果我們想實現多個網絡接口之間的通信,就可以使用下面介紹的網橋(Bridge)技術。
- 簡單來說,網橋就是把一臺機器上的若干個網絡接口“連接”起來。其結果是,其中一個網口收到的報文會被複制給其他網口併發送出去。以使得網口之間的報文能夠互相轉發。
5.1 網橋的工作原理
- 網橋對報文的轉發基於MAC地址。網橋能夠解析收發的報文,讀取目標MAC地址的信息,和自己記錄的MAC表結合,來決策報文的轉發目標網口。
- 爲了實現這些功能,網橋會學習源MAC地址,在轉發報文時,網橋只需要向特定的網口進行轉發,從而避免不必要的網絡交互。
- 如果它遇到一個自己從未學習到的地址,就無法知道這個報文應該向哪個網口轉發,就將報文廣播給所有的網口(報文來源的網口除外)。
5.2 網橋的實現
- Linux內核是通過一個虛擬的網橋設備(Net Device)來實現橋接的。這個虛擬設備可以綁定若干個以太網接口設備,從而將它們橋接起來。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-X3tEDe5h-1587100512783)(H:\markdown圖片\4.png)]
- 對於網絡協議棧的上層來說,只看得到 br0,上層協議棧需要發送的報文被送到 br0,網橋設備的處理代碼判斷報文該被轉發到 eth0 還是 eth1,或者兩者皆轉發;反過來,從eth0 或 eth1 接收到的報文被提交給網橋的處理代碼,在這裏會判斷報文應該被轉發、丟棄還是提交到協議棧上層。
- 而有時eth0、eth1 也可能會作爲報文的源地址或目的地址,直接參與報文的發送與接收,從而繞過網橋。
5.3 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
6. iptables/netfilter
- iptables是Linux實現的軟件防火牆,用戶可以通過iptables設置請求准入和拒絕規則,從而保護系統的安全。
- 我們也可以把iptables理解成一個客戶端代理,用戶通過iptables這個代理,將用戶安全設定執行到對應的安全框架中,這個“安全框架”纔是真正的防火牆,這個框架的名字叫
netfilter
。 - iptables其實是一個命令行工具,位於用戶空間。
- iptables/netfilter(以下簡稱iptables)組成了Linux平臺下的包過濾防火牆,可以完成封包過濾、封包重定向和網絡地址轉換(NAT)等功能。
7. 消息處理
- iptables不僅要處理本機接收到的消息,也要處理本機發出的消息。這些消息需要經過一系列的”關卡“才能被本機應用層接收,或者從本機發出,每個”關卡“擔負着不同的工作。這裏的”關卡“被稱爲”鏈“。
INPUT
:進來的數據包應用此規則鏈中的策規則;OUTPUT
:外出的數據包應用此規則鏈中的規則;FORWARD
:轉發數據包時應用此規則鏈中的規則;PREROUTING
:對數據包作路由選擇前應用此鏈中的規則(所有的數據包進來的時侯都先由這個鏈處理);POSTROUTING
:對數據包作路由選擇後應用此鏈中的規則(所有的數據包出來的時侯都先由這個鏈處理);
8. 規則表
-
從上面我們知道,iptables是按照規則來辦事的,這些規則就是網絡管理員預定義的條件。規則一般的定義爲:如果數據包頭符合這樣的條件,就這樣處理“。這些規則並不是嚴格按照添加順序排列在一張規則表中,而是按照功能進行分類,存儲在不同的表中,每個表存儲一類規則:
-
filter
- 主要用來過濾數據,用來控制讓哪些數據可以通過,哪些數據不能通過,它是最常用的表。
-
nat
- 用來處理網絡地址轉換的,控制要不要進行地址轉換,以及怎樣修改源地址或目的地址,從而影響數據包的路由,達到連通的目的。
-
mangle
- 主要用來修改IP數據包頭,比如修改TTL值,同時也用於給數據包添加一些標記,從而便於後續其它模塊對數據包進行處理(這裏的添加標記是指往內核skb結構中添加標記,而不是往真正的IP數據包上加東西)。
-
raw
- 在Netfilter裏面有一個叫做鏈接跟蹤的功能,主要用來追蹤所有的連接,而raw表裏的rule的功能是給數據包打標記,從而控制哪些數據包不做鏈接跟蹤處理,從而提高性能;
優先級最高
。
- 在Netfilter裏面有一個叫做鏈接跟蹤的功能,主要用來追蹤所有的連接,而raw表裏的rule的功能是給數據包打標記,從而控制哪些數據包不做鏈接跟蹤處理,從而提高性能;
9. 表和鏈的關係
- 表和鏈共同完成了iptables對數據包的處理。但並不是每個鏈都包含所有類型的表,所以,有些鏈是天生不具備某些功能的。就像我們去車站乘車的時候,”關卡A“只負責檢查身份證,”B關卡”只負責檢查行李,而“C關卡”功能比較齊全,即負責檢查身份證,又負責檢查行李。