Linux虛擬網路技術

程式設計師的貓發表於2020-08-28

Network Namespace

Network Namespace 是 Linux 核心提供的功能,是實現網路虛擬化的重要功能,它能建立多個隔離的網路空間,它們有獨自網路棧資訊。不管是虛擬機器還是容器,執行的時候彷彿自己都在獨立的網路中。而且不同Network Namespace的資源相互不可見,彼此之間無法通訊

Linux虛擬網路技術

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 中執行命令。

  1. 檢視新建立 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的實現。

Linux虛擬網路技術

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虛擬網路技術

網橋

網橋的實現

Linux核心是通過一個虛擬的網橋裝置(Net Device)來實現橋接的。這個虛擬裝置可以繫結若干個乙太網介面裝置,從而將它們橋接起來。如下圖所示:

Linux虛擬網路技術

網橋的位置

如上圖所示,網橋裝置 br0 繫結了 eth0 和 eth1。

對於網路協議棧的上層來說,只看得到 br0,上層協議棧需要傳送的報文被送到 br0,網橋裝置的處理程式碼判斷報文該被轉發到 eth0 還是 eth1,或者兩者皆轉發;反過來,從eth0 或 eth1 接收到的報文被提交給網橋的處理程式碼,在這裡會判斷報文應該被轉發、丟棄還是提交到協議棧上層。

而有時eth0、eth1 也可能會作為報文的源地址或目的地址,直接參與報文的傳送與接收,從而繞過網橋。

brctl

和網橋有關的操作可以使用命令 brctl,這個命令來自 bridge-utils 這個包。

  1. 建立網橋
# 建立網橋
brctl addbr br0 
  1. 刪除網橋
# 刪除網橋
brctl delbr br0
  1. 繫結網口
    建立一個邏輯網段之後,我們還需要為這個網段分配特定的埠。在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:對資料包作路由選擇後應用此鏈中的規則(所有的資料包出來的時侯都先由這個鏈處理);

資料包經過各個鏈的處理過程大致如下圖所示:

Linux虛擬網路技術

訊息處理鏈

規則表

從上面我們知道,iptables是按照規則來辦事的,這些規則就是網路管理員預定義的條件。規則一般的定義為:如果資料包頭符合這樣的條件,就這樣處理“。這些規則並不是嚴格按照新增順序排列在一張規則表中,而是按照功能進行分類,儲存在不同的表中,每個表儲存一類規則:

  • Filter
    主要用來過濾資料,用來控制讓哪些資料可以通過,哪些資料不能通過,它是最常用的表。
  • NAT
    用來處理網路地址轉換的,控制要不要進行地址轉換,以及怎樣修改源地址或目的地址,從而影響資料包的路由,達到連通的目的。
  • Mangle
    主要用來修改IP資料包頭,比如修改TTL值,同時也用於給資料包新增一些標記,從而便於後續其它模組對資料包進行處理(這裡的新增標記是指往核心skb結構中新增標記,而不是往真正的IP資料包上加東西)。
  • Raw
    在Netfilter裡面有一個叫做連結跟蹤的功能,主要用來追蹤所有的連線,而raw表裡的rule的功能是給資料包打標記,從而控制哪些資料包不做連結跟蹤處理,從而提高效能;優先順序最高

表和鏈的關係

表和鏈共同完成了iptables對資料包的處理。但並不是每個鏈都包含所有型別的表,所以,有些鏈是天生不具備某些功能的。就像我們去車站乘車的時候,”關卡A“只負責檢查身份證,”B關卡”只負責檢查行李,而“C關卡”功能比較齊全,即負責檢查身份證,又負責檢查行李。二者的關係如下圖所示:

Linux虛擬網路技術

表和鏈的關係

總結

今天我們共同學習了一些常見的Linux虛擬網路技術。其中,Linux通過Network Namespace實現了網路的隔離,使網路協議棧之間互不干擾;並通過veth pair和網橋實現了相同主機上多個Network Namespace之間的資料通訊;iptables則可以幫助我們實現網路安全和資料包的路由轉發功能,從而使主機和主機、容器與容器、容器和宿主機之間可以相互收發訊息。在這些技術的共同協作下,才有了現在安全、穩定的虛擬網路。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
你還差得遠吶!

相關文章