1. 程式人生 > >Neutron 理解 (6): Neutron 是怎麼實現虛擬三層網路的 [How Neutron implements virtual L3 network]

Neutron 理解 (6): Neutron 是怎麼實現虛擬三層網路的 [How Neutron implements virtual L3 network]

學習 Neutron 系列文章:

    Neutron 對虛擬三層網路的實現是通過其 L3 Agent (neutron-l3-agent)。該 Agent 利用 Linux IP 棧、route 和 iptables 來實現內網內不同網路內的虛機之間的網路流量,以及虛機和外網之間網路流量的路由和轉發。為了在同一個Linux 系統上支援可能的 IP 地址空間重疊,它使用了 Linux network namespace 來提供隔離的轉發上下文。

1. 基礎知識

1.1 Linux network namespace

1.1.1 概念和操作

    在二層網路上,VLAN 可以將一個物理交換機分割成幾個獨立的虛擬交換機。類似地,在三層網路上,Linux network namespace(netns) 可以將一個物理三層網路分割成幾個獨立的虛擬三層網路。

   Network namespace (netns)從 Linux 2.6.24 版本開始新增,直到 2.6.29 新增完成。每個 netns  擁有獨立的 (virtual)network devices, IP addresses, IP routing tables, /proc/net directory, ports 等等。新建立的 netns 預設只包含 loopback device。除了這個裝置,每個 network device,不管是物理的還是虛擬的網絡卡還是網橋等,都只能存在於一個 netns。而且,連線物理硬體的物理裝置只能存在於 root netns。其它普通的網路裝置可以被建立和新增到某個 netns。

   使用 ip 命令來操作 netns。

#新增 network namespace
ip netnas add <network namespace name>
#Example:
ip netns add nstest

#列表所有 netns
ip netns list

#刪除某 netns
ip netns delete <network namespace name>

#在 network namespace 中執行命令
ip netns exec <network namespace name> <command>
#Example using the namespace from above:
ip netns exec nstest ip addr

#新增 virtual interfaces 到 network namespace
ip link add veth-a type veth peer name veth-b #建立一對虛擬網絡卡veth-a 和 veth-b,兩者由一根虛擬網線連線
#將 veth-b 新增到 network namespace
ip link set veth-b netns nstest

#設定 VI 的 IP 地址
#defaut namespace 中
ip addr add 10.0.0.1/24 dev veth-a
ip link set dev veth-a up

# namespace nstest 中
ip netns exec nstest ip addr add 10.0.0.2/24 dev veth-b
ip netns exec nstest ip link set dev veth-b up
#互通
# ping 10.1.1.1 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. 64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.087 ms # ip netns exec netns1 ping 10.1.1.2 PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data. 64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.054 ms
#檢視路由表和 iptbales
# ip netns exec netns1 route # ip netns exec netns1 iptables -L

1.1.2 namespace 間的通訊

(1)一種簡單的方式是使用 Linux veth pair 來實現兩個 network namespace 之間的通訊:

NewImage

(2)當有兩個以上的 network namespace 之間需要通訊時,需要使用一個虛機交換機,和兩個 veth pair。傳統的方式是 Linux bridge:

Connecting namespaces using a linux bridge and two veth pairs

你也可以使用 Open vSwitch:

Connecting namespaces using the openvswitch and two veth pairs

(3)再一種方式是使用 Open vSwitch 和 OVS internal ports:

Connecting namespaces using the openvswitch and two openvswitch ports

來源,可見詳細的配置命令。)

veth (irtual Ethernet interfaces) 裝置:這是一種成對出現的特殊網路裝置,它們象一根管道一樣連線在一起。VETH 裝置總是成對出現,送到一端請求傳送的資料總是從另一端以請求接受的形式出現。該裝置不能被使用者程式直接操作,但使用起來比較簡單。建立並配置正確後,向其一端輸入資料,VETH 會改變資料的方向並將其送入核心網路核心,完成資料的注入。在另一端能讀到此資料。

關於幾種方式的效能比較,這篇文章也給出了它的測試結論:

  • 使用 OVS patch ports:效能更好
  • 不要使用 Linux veth pairs:它會帶來明顯的效能下降

在 Neutron 中,可以使用配置項 ovs_use_veth 來配置是否使用 veth,預設為 false,表示預設使用 OVS internal port。

1.2 iptables

1.2.1 netfilter/iptables 基本概念

   netfilter/iptables(簡稱為iptables)組成 Linux 平臺下的包過濾防火牆。其中,iptables 是一個 linux 使用者空間(userspace)模組,位於/sbin/iptables,使用者可以使用它來操作防火牆表中的規則。真正實現防火牆功能的是 netfilter,它是一個 linux 核心模組,做實際的包過濾。實際上,除了 iptables 以外,還有很多類似的使用者空間工具。

  這篇文章 詳細介紹了 iptables/netfilter 的概念:

       netfilter包處理

  • Netfilter 是一套資料包過濾框架,在處理 IP 資料包時 hook 了5個關鍵鉤子。通過這5個關鍵點來實現各種功能,比如firewall/ips。
  • ip_tables 是真正的核心防火牆模組,通過把自己的函式注入到 Netfilter 的框架中來實現的防火牆功能.
  • Netfilter 提供了最基本的底層支撐,具體的功能實現只要註冊自己的函式就可以了,這樣保證了協議棧的純淨與可擴充套件性.通過上圖可以看出 netfilter 與 iptables是分離的.

資料包處理過程:

  1. 資料包從左邊進入IP協議棧,進行 IP 校驗以後,資料包被第一個鉤子函式 PRE_ROUTING 處理。
  2. 然後就進入路由模組,由其決定該資料包是轉發出去還是送給本機。
  3. 若該資料包是送給本機的,則要經過鉤子函式 LOCAL_IN 處理後傳遞給本機的上層協議;若該資料包應該被轉發,則它將被鉤子函式 FORWARD 處理,然後還要經鉤子函式 POST_ROUTING 處理後才能傳輸到網路。
  4. 本機程序產生的資料包要先經過鉤子函式 LOCAL_OUT 處理後,再進行路由選擇處理,然後經過鉤子函式POST_ROUTING處理後再發送到網路。

netfilter 使用表(table)和 鏈(chain)來組織網路包的處理規則(rule)。它預設定義了以下表和鏈:

iptables的表與鏈

表功能鏈功能
raw
PREROUTING         
OUTPUT

RAW 擁有最高的優先順序,它使用PREROUTING和OUTPUT兩個鏈,因此 RAW 可以覆蓋所有包。在raw表中支援一個特殊的目標:TRACE,使核心記錄下每條匹配該包的對應iptables規則資訊。使用raw表內的TRACE target 即可實現對iptables規則的跟蹤除錯。比如:

# iptables -t raw -A OUTPUT -p icmp -j TRACE
# ipt ables -t raw -A PREROUTING -p icmp -j TRACE

Filter 包過濾
FORWARD
過濾目的地址和源地址都不是本機的包
INPUT
過濾目的地址是本機的包
OUTPUT
過濾源地址是本機的包
Nat 網路地址轉換               
PREROUTING
在路由前做地址轉換,使得目的地址能夠匹配上防火牆的路由表,常用於轉換目的地址。
POSTROUTING
在路由後做地址轉換。這意味著不需要在路由前修改目的地址。常用語轉換源地址。
OUTPUT

對防火牆產生的包做地址轉換(很少量地用於 SOHO 環境中)

Mangle TCP 頭修改
PREROUTING
POSTROUTING
OUTPUT
INPUT
FORWARD
在路由器修改 TCP 包的 QoS(很少量地用在 SOHO 環境中)

每個註冊的 Hook 函式依次呼叫各表的鏈的規則來處理網路包:

 

來源:Ethernet bridging hooks )

  • PREROUTING Hook 依次呼叫 Managle 和 Nat 的 PREOUTING 鏈中的規則來處理網路包
  • LOCAL_IN Hook 依次呼叫  MANGLE 和 Filter 的 INPUT 鏈中的規則來過濾網路包
  • LOCAL_OUT Hook 依次呼叫 Mangle,Nat,Filter 表的 Output 鏈中的規則來過濾網路包
  • FORWARD Hook 依次呼叫 Mangle 和 Filter 表的 FORWARD 鏈中的規則來過濾網路包
  • POST_ROUTING Hook 依次呼叫 Managle 和 Nat 表的 POSTROUTING 鏈中的規則來處理網路包

對 Neutron Virtual Router 所使用的 filter 表來說,它的三個鏈 INPUT, FORWARD, 和 OUTPUT 是分開的。一個數據包,根據其源和目的地址,只能被其中的某一個處理。 

  • 如果資料包的目的地址是本機,那它被 INPUT 處理。
  • 如果資料包的源地址是本機,那它被 OUTPUT 處理。
  • 如果源地址和目的地址都是別的機器,那它被 FORWARD 鏈處理。

圖中的 ”路由判斷“ 即判斷包的目的地址。如果目的地址不是本機的地址,那它就是需要被路由的包;如果目的地址是本機的,那麼被filter 的 INPUT 處理後,被主機的某個程式處理。該程式如果需要發回響應包的話,其源地址肯定是本機的,所有它會被 filter 的 OUTPUT 鏈處理。該包不一定會出網絡卡,因為可能會走 loopback,它又會回到本機,重新走封包進入的過程。 

1.2.2 iptables

   iptables 是一個 CLI 型別的 Linux 使用者空間工具,它使得系統管理員能夠配置netfile 表(tables)中的鏈和規則。Linux 使用不同的核心模組和應用來管理不同的網路協議iptable 適用於 ipv4,ip6tables 適用於 ipv6,arptables 適用於 ARP,ebtables 適用於網路幀。iptales 需要管理員許可權。

   規則(rules)其實就是網路管理員預定義的條件,規則一般的定義為“如果資料包頭符合這樣的條件,就這樣處理這個資料包”。規則儲存在核心空間的資訊包過濾表中,這些規則分別指定了源地址、目的地址、傳輸協議(如TCP、UDP、ICMP)和服務型別(如HTTP、FTP和SMTP)等。當資料包與規則匹配時,iptables就根據規則所定義的方法來處理這些資料包,如放行(accept)、拒絕(reject)和丟棄(drop)等。配置防火牆的主要工作就是新增、修改和刪除這些規則。

操作 iptables 服務:

# /etc/init.d/iptables start/stop/restart

iptables 各命令選項:

iptables的使用

  • -p, --protocol protocol: tcp, udp, udplite, icmp, esp, ah, sctp 之一
  • -s, --source address[/mask] a network name, a hostname, a network IP address (with /mask), or a plain IP address.
  • -d, --destination address[/mask][,...]:同 -s
  • -j, --jump target:match 後的 target。
  • -g, --goto chain
  • [!] -i, --in-interface name:連線進來的 interface 名稱。!表示否。
  • [!] -o, --out-interface name

其中 -j:

當資料包進入後,會依次比照 iptables 中的每條規則,直到有一條規則可以對該報文進行匹配,這時該報文將被執行"ACCEPT","DORP","REJECT" 或者其它動作,除 REDIRECT 外,執行完後停止跟餘下的 iptables 規則匹配。

  • -ACCEPT: 將封包放行,進行完此處理動作後,將不再比對其它規則,直接跳往下一個規則鏈。
  • -REJECT:   攔阻該封包,並傳送封包通知對方。
  • -DROP: 丟棄封包不予處理,進行完此處理動作後,將不再比對其它規則,直接中斷過濾程式。
  • -DNAT:DNAT 改寫封包目的地 IP 為某特定 IP 或 IP 範圍,可以指定 port 對應的範圍,進行完此處理動作後,將會直接跳往下一個規則鏈。
  • -REDIRECT:    將封包重新導向到另一個埠(PNAT),進行完此處理動作後,將會繼續比對其它規則。
  • -SNAT: 改寫封包來源 IP 為某特定 IP 或 IP 範圍,可以指定 port 對應的範圍,進行完此處理動作後,將直接跳往下一個規則鏈。
  • -RETURN:中斷當前鏈,返回呼叫鏈或者預設的policy。

一些例子:

  • iptables -A INPUT -s 10.10.10.10 -j DROP #丟棄從 10.10.10.10 主機來的所有包
  • iptables -A INPUT -s 10.10.10.0/24 -j DROP #丟棄從 10.10.10.0/24 網段進來所有包
  • iptables -A INPUT -p tcp --dport ssh -s 10.10.10.10 -j DROP # 如果協議是 tcp,目標埠是 ssh 埠,源IP 為 10.10.10.10,那麼丟棄它
  • iptables -A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT #接受從 virbr0 進來的所有目標埠 53 的 udp 包
  • iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT #接受 RELEASED 和 ESTABLISHED  狀態的連線。Linux 3.7 以後,--state 被替換成了 --conntrack
  • iptables -A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED,ESTABLISHED -j ACCEPT #轉發時接受這些包
  • iptables -A FORWARD -p icmp -j ACCEPT #轉發時接受所有 ICMP 路由包。
  • iptables -A INPUT -i lo -j ACCEPT #使用 -i 過濾從 lo 裝置進來的包
  • iptables -A INPUT -i eth0 -j ACCEPT #使用 -i  過濾從網絡卡 eth0 進來的包。不指定網絡卡的話表示所有網絡卡。

封包過濾實現的是針對安全方面的策略,通常我們遵循“凡是沒有明確允許的都是禁止的”這樣的原則來設計安全策略:首先禁止所有的東西,然後根據需要再開啟必要的部分。

關於 iptables 的詳細的說明,可以參考 這裡,以及 這裡 和 這裡,以及 這裡

Neutron 主要用到 filter 表和 nat 表,其中, filter 用來實現安全組(Security Group)和 防火牆(FWaas);nat 主要用來實現 router。

1.2.3 NAT 的實現

可以使用 iptables nat 表來實現網路地址轉換(NAT)。NAT 包括 SNAT (源地址轉換)和 DNAT (目的地址轉換)。兩者的區別在於做地址轉換是在路由前還是路由後:

(1)SNAT:路由 - 轉換 - 發出

資料經過時, 源地址發生改變,目的地址不變。SNAT 的具體資料流向:

  1. 封包先經過 PREROUTING,檢查目的 IP 是不是本網段的地址。是的話,走路徑A。
  2. 如果不是,則開始查詢路由表,查詢到相應路由條目後(查詢路由的過程在 PREROUTING 和 FORWARD 之間),經過 FORWARD 鏈進行轉發,再通過 postrouting 時進行NAT轉換。

從這裡可以看出,SNAT轉換的步驟在 POSTROUTING 鏈上實現, PREROUTING 只是用來做路由選擇。因此,要做 SNAT 的話,需要新增 POSTROUTING 規則,使用 “-j SNAT -to-source”。比如:

iptables -t nat -A POSTROUTING -s 192.168.252.0/24 -j SNAT -to-source 100.100.100.1

(2)DNAT:轉換 - 路由- 發出

DNAT 的功能正好和 SNAT 相反,源地址不變,目的地址發生改變。DNAT 可以用作 PNAT,可以將一個 IP 的埠轉換成另一個IP的另外一個埠號,經常用於內網伺服器對映到公網,用來隱藏伺服器的真實地址。DNAT 的具體資料流向:

  1. 在 DNAT 中,NAT 是在 PREROUTING 上做的。在資料進入主機後,路由選擇過程是在 PREROUTING 和 FORWARD 之間的,所以應該先做地址轉換之後,再進行路由選擇,而後經過 FORWARD,最後從 POSTROUTING 出去。
  2. 因此,要做 DNAT,需要新增 PREROUTING 規則,使用 “-j DNAT --to-destination”。比如:
iptables -t nat -A PREROUTING -d 100.100.100.1 -p tcp --dport 80 -j DNAT --to-destination 192.168.252.1

有一類比較特殊的 DNAT 是使用 “-j REDIRECT” 做埠號轉換:

## Send incoming port-80 web traffic to our squid (transparent) proxy
# iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 80-j REDIRECT --to-port 3128

關於 SNAT 和 DNAT 的更多解釋可以參考 這裡 和 這裡

1.3 route (Linux 路由表)

Linux 系統的 route 命令用於顯示和操作 IP 路由表,它的主要作用是建立一個靜態路由來指定一個主機或者一個網路通過一個網路介面,如eth0。

route [-CFvnee]

route [-v] [-A family] add [-net|-host] target [netmask Nm] [gw Gw] [metric N] [mss M] [window W] [irtt I] [reject] [mod] [dyn] [rein-
state] [[dev] If]

route [-v] [-A family] del [-net|-host] target [gw Gw] [netmask Nm] [metric N] [[dev] If]

例子:

  • route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0 #增加一條經過 eth0 到達 244.0.0.0 的路由
  • route add -net 224.0.0.0 netmask 240.0.0.0 reject #增加一條遮蔽的路由,目的地址為224.x.x.x將被拒絕。
  • route del -net 224.0.0.0 netmask 240.0.0.0
  • route del -net 224.0.0.0 netmask 240.0.0.0 reject
  • route del default gw 192.168.120.240
  • route add default gw 192.168.120.240

這個命令比較簡單,可以參考 這個

1.4 路由器的輔助(Secondary) IP

先來看一個 Virutal Router 的 interface 的 ip 配置: 
42: qg-3c8d6a68-97: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
    link/ether fa:16:3e:2e:5b:23 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.110/24 brd 192.168.1.255 scope global qg-3c8d6a68-97
       valid_lft forever preferred_lft forever
    inet 192.168.1.104/32 brd 192.168.1.104 scope global qg-3c8d6a68-97
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fe2e:5b23/64 scope link
       valid_lft forever preferred_lft forever

<BROADCAST,UP,LOWER_UP>:埠的各種狀態

  • UP: device is functioning (enabled 狀態,可通過 ip * up/down 設定。)
  • BROADCAST: device can send traffic to all hosts on the link (能夠發廣播)
  • MULTICAST: device can perform and receive multicast packets (能夠發多播)
  • ALLMULTI: device receives all multicast packets on the link (能夠接收多播)
  • PROMISC: device receives all traffic on the link (接收所有的traffic)
  • LOWER_UP:the state of the Ethernet link(表示線已接上) 

inet/brd/scope:IP 地址及子網掩碼,廣播地址,作用域

scope:

  • global:valid everywhere
  • site:valid only within this site (IPv6)
  • link:valid only on this device
  • host:valid only inside this host (machine)

    注意到這個interface有兩個靜態 IP 地址。第一個是主要的(primary)IP,第二個是輔助的( secondary) 的 IP。當一個網絡卡配置了靜態IP後,你可以新增secondary  IP 給它。這樣它就擁有了多個 IP 地址了。Secondary IP 不可以通過 DHCP 分配。它所有的IP 地址都關聯到它唯一的一個 MAC 地址上。那為什麼需要 secondary IP 地址呢? 路由器有個 Secondary IP 的概念,這個特性可以建立邏輯子網,也就是說在一個物理網口上連線兩個子網,比如這個網口接到一臺交換機上,如 果這個網口沒有配置Secondary IP的話,那麼這臺交換機只能連線一個網段的主機,比如 192.168.1.1/24,但是,如果它配置了Secondary IP,那麼就可以連線兩個網段的主機,比如 192.168.1.1/24 和 10.0.0.1/24。更詳細的解釋可以看這裡 和 這裡

命令:

#增加 secondary IP
ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 ip a add dev qg-3c8d6a68-97 192.168.1.105/32 brd 192.168.1.105

#刪除 secondar IP
ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 ip addr del 192.168.1.104/32 dev qg-3c8d6a68-97

1.5 Gratuitous ARP

  Gratuitous ARP也稱為 免費ARP,無故ARP。Gratuitous ARP不同於一般的ARP請求,它並非期待得到IP對應的MAC地址,而是當主機啟動的時候,將傳送一個Gratuitous arp請求,即請求自己的IP地址的MAC地址。它常用於三個用途:

  • Change of L2 address:通告自己改變了 MAC 地址。以 ARP Response 的形式傳送廣播,它通常只是為了把自己的ARP資訊通告/更新給區域網全體,這種Response不需要別人請求,是自己主動傳送的通告。報文結構如下當一個網路裝置的 MAC 地址發生改變時,傳送該裝置的 Gratuitous ARP,通知所在廣播域內的已經包含該 IP 地址在其 ARP 表中的機器去更新它的 ARP 條目。

  • Duplicate address detection:重複 MAC 地址檢測。以 ARP Request的形式傳送廣播,請求自己的MAC地址,目的是探測區域網中是否有跟自己IP地址相同的主機,也就是常說的IP衝突。傳送主機並不需要一定收到此請求的回答。如果收到一個回答,表示網路中存在與自身IP相同的主機。如果沒有收到應答,則表示本機所使用的IP與網路中其它主機並不衝突。

(注意:這兩種 ARP 幀雖然都是廣播發送的,但目的不同,從幀結構上來說,前者注重的是Target Internet Address,而後者注重的是Sender Hardware Address和Sender Inteernet Address。)

  • Virtual IP:用於一組伺服器做 failover 時通知周圍的機器新生效的 IP 地址的 MAC。

以上內容引用自 談談arp欺騙的那點破事,更多資訊,還可以參見 這篇文章 和 這篇文章

2. Neutron L3 Agent 的實現原理

    每個 L3 Agent 執行在一個 network namespace 中,每個 namespace 由 qrouter-<router-UUID>命名,比如 qrouter-e506f8fe-3260-4880-bd06-32246225aeae。網路節點如果不支援 Linux namespace 的話,只能執行一個 Virtual Router。也可以通過設定配置項 use_namespaces = True/False 來使用或者不使用 namespace。

   Neutron L3 Agent 負責路由(routing)、浮動 IP 分配(floatingip allocation), 地址轉換(SNAT/DNAT)和 Security Group 管理(Blueprint 在這裡。在後面的文章中打算和 Nova 中的 Security Group 一起分析)。

2.1 Router 作為浮動 IP 地址的ARP Proxy

    虛機的浮動 IP 其實不是真實網絡卡的 IP 地址,而是一個虛擬地址。那麼,使用浮動 IP 和虛機通訊的機器怎麼獲得 MAC 地址呢?Router 在這個過程中作為一個 ARP Proxy,其 IP 協議棧會向 ARP 廣播請求迴應浮動 IP 對應所在的外部埠的 MAC 地址。下面的例子中,該 router 掛接的子網內有兩個浮動 IP,L3 Agent 都將它們新增到 Router 的外部埠 qg-3c8d6a68-97 上: 

42: qg-3c8d6a68-97: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
    link/ether fa:16:3e:2e:5b:23 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.110/24 brd 192.168.1.255 scope global qg-3c8d6a68-97
       valid_lft forever preferred_lft forever
    inet 192.168.1.104/32 brd 192.168.1.104 scope global qg-3c8d6a68-97
       valid_lft forever preferred_lft forever
    inet 192.168.1.111/32 brd 192.168.1.111 scope global qg-3c8d6a68-97
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fe2e:5b23/64 scope link
       valid_lft forever preferred_lft forever

先插播一句。上圖可以看到 qg 下面有個3個 ip 地址,第一個是 VR 自己的 fip,後面兩個是後面虛機的 fip。也就是說,要給虛機分配 fip,必須先給 VR 分配 fip,因為這樣才能有 qg。    

這麼做的目的,由於外網中的機器和虛機浮動 IP 是同一個網段的,外網中的機器通過浮動 IP 訪問虛機之前,需要通過 ARP 獲取該浮動 IP 的 MAC 地址。浮動IP 其實是個虛機IP,沒有對應一個網路裝置,因此,Neutron 將它們新增到 external port 上,共享 external port 的 MAC 地址。這樣,在 router network namespace IP 協議棧收到 ARP 廣播後,就可以該 IP 對應 的 MAC 地址,然後外網中的虛機就會使用該 MAC 作為目的 MAC 地址直接向 router 的 external port 埠傳送網路幀。查詢外網機器的 arp table,即可看到 192.168.1.104 的 MAC 為 qg-3c8d6a68-97 的  MAC 地址。也就是說,外部埠上的所有 IP 的 MAC 地址都相同。這時候,其實 router 充當了一個 ARP Proxy 的角色。 

[email protected]:~$ arp
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.1.104            ether   fa:16:3e:2e:5b:23   C                     eth0
192.168.1.110            ether   fa:16:3e:2e:5b:23   C                     eth0
192.168.1.111            ether   fa:16:3e:2e:5b:23   C                     eth0

2.2 路由 (Routing)

一個 Virtual Router 連線幾個 subnet 就會有幾個 virtual interface。每個 interface 的地址是該 subnet 的 gateway 地址。比如:

[email protected]:/home/s1# ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 ip addr

33: qr-2aa928c9-e8: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default #IP 設為它連線的子網的 Gateway IP
link/ether fa:16:3e:90:e5:50 brd ff:ff:ff:ff:ff:ff
inet 91.1.180.1/24 brd 91.1.180.255 scope global qr-2aa928c9-e8
37: qr-a5c6ed86-c1: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether fa:16:3e:87:40:f3 brd ff:ff:ff:ff:ff:ff
inet 81.1.180.1/24 brd 81.1.180.255 scope global qr-a5c6ed86-c1
48: qg-3c8d6a68-97: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether fa:16:3e:2e:5b:23 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.110/24 brd 192.168.1.255 scope global qg-3c8d6a68-97

    L3 Agent 在啟動時設定如下的路由規則:

[email protected]:/home/s1# ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.1.1 0.0.0.0 UG 0 0 0 qg-3c8d6a68-97
81.1.180.0 0.0.0.0 255.255.255.0 U 0 0 0 qr-a5c6ed86-c1 #到哪個網段的traffic發到相應的 interface
91.1.180.0 0.0.0.0 255.255.255.0 U 0 0 0 qr-2aa928c9-e8
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 qg-3c8d6a68-97

   虛機的 IP 棧在發現數據包的目的虛機的 IP 地址不在自己網段的話,會將其發到 Router 上對應其 subnet 的 Virtual Interface。然後,Virtual Router 根據配置的路由規則和目的IP地址,將包轉發到目的埠發出。

2.3 源地址轉換 SNAT

2.3.1 Neutron 的 SNAT iptables chains

在沒有設定浮動 IP 時,當主機訪問外網時,需要將主機的固定 IP 轉換成外網網段的 gateway 的 IP 地址,以免暴露內部 IP 地址。其做法是 Neutron 在 iptables 表中增加了 POSTROUTING 鏈。

[email protected]:/home/s1# ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N neutron-l3-agent-OUTPUT      #Neutorn 增加的 chain
-N neutron-l3-agent-POSTROUTING #Neutorn 增加的 SNAT chain
-N neutron-l3-agent-PREROUTING
-N neutron-l3-agent-float-snat  #Neutorn 增加的 SNAT chain
-N neutron-l3-agent-snat        #Neutorn 增加的 SNAT chain
-N neutron-postrouting-bottom   #Neutorn 增加的 SNAT chain
-A PREROUTING -j neutron-l3-agent-PREROUTING
-A OUTPUT -j neutron-l3-agent-OUTPUT
-A POSTROUTING -j neutron-l3-agent-POSTROUTING #(1)將 SNAT chain 轉到自定義的 neutron-l3-agent-POSTROUTING
-A POSTROUTING -j neutron-postrouting-bottom   #(3)將 SNAT chain 轉到自定義的 neutron-postrouting-bottom
-A neutron-l3-agent-POSTROUTING ! -i qg-3c8d6a68-97 ! -o qg-3c8d6a68-97 -m conntrack ! --ctstate DNAT -j ACCEPT #(2)如果出口或者入口不是 qg-3c8d6a68-97 並且狀態不是 DNAT 的都接受
-A neutron-l3-agent-snat -j neutron-l3-agent-float-snat
-A neutron-l3-agent-snat -s 91.1.180.0/24 -j SNAT --to-source 192.168.1.110 #(5)將 91.1.180.0/24 網段的包的目的 IP 轉為 192.168.1.110
-A neutron-l3-agent-snat -s 81.1.180.0/24 -j SNAT --to-source 192.168.1.110 #(5)將 91.1.180.0/24 網段的包的目的 IP 轉為 192.168.1.110
-A neutron-postrouting-bottom -j neutron-l3-agent-snat                      #(4)再轉到 neutron-l3-agent-snat

2.3.2 實驗:從虛機  81.1.180.12 ping 外網 192.168.1.15

1. 在 router 的連線 81.1.180.12 網段 interface 上: 

[email protected]:/home/s1# ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 tcpdump -envi qr-a5c6ed86-c1 -vvv
tcpdump: listening on qr-a5c6ed86-c1, link-type EN10MB (Ethernet), capture size 65535 bytes
^C17:42:48.904820 fa:16:3e:2b:3e:2a > fa:16:3e:87:40:f3, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 28892, offset 0, flags [DF], proto ICMP (1), length 84)
    81.1.180.12 > 192.168.1.15: ICMP echo request, id 32769, seq 0, length 64
17:42:48.906601 fa:16:3e:87:40:f3 > fa:16:3e:2b:3e:2a, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 63, id 24799, offset 0, flags [none], proto ICMP (1), length 84)
    192.168.1.15 > 81.1.180.12: ICMP echo reply, id 32769, seq 0, length 64
17:42:49.906238 fa:16:3e:2b:3e:2a > fa:16:3e:87:40:f3, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 28893, offset 0, flags [DF], proto ICMP (1), length 84)

2. 在 route 的連線 192.168.1.15 網段 interface 上

[email protected]:/home/s1# ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 tcpdump -envi qg-3c8d6a68-97 -vvv
tcpdump: listening on qg-3c8d6a68-97, link-type EN10MB (Ethernet), capture size 65535 bytes
^C17:44:47.661916 fa:16:3e:2e:5b:23 > 08:00:27:c7:cf:ca, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 63, id 28896, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.1.110 > 192.168.1.15: ICMP echo request, id 33281, seq 0, length 64
17:44:47.663300 08:00:27:c7:cf:ca > fa:16:3e:2e:5b:23, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 36308, offset 0, flags [none], proto ICMP (1), length 84)
    192.168.1.15 > 192.168.1.110: ICMP echo reply, id 33281, seq 0, length 64

可見在外網網段的 interface 收到資料包之前,SRC IP 已經被替換成了外網網段的 Gateway IP 了。 

2.3.3 關於 SNAT 的一點細節

SNAT 只能用於從內部網路發起的目的是外部網路的連線,而不能相反。

前面分析了內網網路包如何出去,但因為外網主機回覆的網路包的目的地址為SNAT 地址,不是真正內網主機的地址,因此回覆包如何回到源主機呢?SNAT 的實現會儲存一個連線對映表(connection table/mapping table),裡面的每條記錄儲存著該連線的內部(源)address:port和外面(目標)address:port 之間的對映關係。在做 SNAT 時,會往該表中插入一條記錄;當回覆的包回來以後,會查詢該表,找出之前的源也就是現在的目標地址,然後重新構建網路包發給真正的源主機。因為這是地址和埠的組合作為對映的鍵,如果重複了會怎麼辦呢?當表中有重複記錄時,SNAT 在插入記錄時把源埠修改為一個未使用的埠,來保證記錄的唯一性。具體可以參考 http://www.commercialventvac.com/finao/DNATs-and-SNATs.html

2.4 目的地址轉換 DNAT

    要使外網內的機器能訪問虛機,需要設定虛機的浮動IP。浮動 IP 在 Virtual Router 連線的 external network 的 subnet 內分配。注意浮動 IP 只有在 Virtual Router 上配置了 External network subnet gateway 才有意義。

2.4.1 浮動IP分配 

建立浮動IP: 

[email protected]:~# neutron floatingip-create Extnet
Created a new floatingip:
+---------------------+--------------------------------------+
| Field               | Value                                |
+---------------------+--------------------------------------+
| fixed_ip_address    |                                      |
| floating_ip_address | 10.8.127.11                          |
| floating_network_id | 9c9436d4-2b7c-4787-8535-9835e6d9ac8e |
| id                  | 7b4cee72-ffcd-4484-a5d8-371b23bb3cc3 |

關聯到一個 port: 

[email protected]:~# neutron port-list | grep 192.168.10.26
| d74c703e-824a-41b1-b4b3-3cd4edfa22b3 |      | fa:16:3e:14:ff:6d | {"subnet_id": "ccc80588-2b0d-459b-82e9-972ff4291b79", "ip_address": "192.168.10.26"} |
[email protected]:~# neutron floatingip-associate 7b4cee72-ffcd-4484-a5d8-371b23bb3cc3 d74c703e-824a-41b1-b4b3-3cd4edfa22b3
+---------------------+--------------------------------------+
| Field               | Value                                |
+---------------------+--------------------------------------+
| fixed_ip_address    | 192.168.10.26                        |
| floating_ip_address | 10.8.127.11                          |

    每個浮動 IP 唯一對應一個 Router:浮動IP -> 關聯的 Port -> 所在的 Subnet -> 包含該 subnet 以及 external subnet 的 Router。建立浮動 IP 時,在 Neutron 完成資料庫操作來分配浮動IP後,它通過 RPC 來通知該浮動IP對應的 router 去設定該浮動IP對應的 iptables 規則。上面的例子中,固定IP 為 ‘192.168.10.26’ 的虛機可以在外網中使用浮動 IP  ‘10.8.127.11’ 來訪問了。

<更新 2015/11/27> Kilo 版本中,建立浮動 IP 的時候允許指定浮動IP 地址,這麼做就能夠對同一個虛機使用同一個浮動IP。

效果:Neutorn floatingip-create cli 上增加了一個引數 --floating-ip-address 用於指定建立的浮動IP地址:

[email protected]:~/s1# neutron floatingip-create --port-id 97f3ed61-7f9e-4f0a-91af-e95a572acd9c --floating-ip-address 9.115.251.105 5b4daf62-d992-453f-b74d-8c585365a604
Created a new floatingip:
+---------------------+--------------------------------------+
| Field               | Value                                |
+---------------------+--------------------------------------+
| fixed_ip_address    | 70.0.0.105                           |
| floating_ip_address | 9.115.251.105                        |
| floating_network_id | 5b4daf62-d992-453f-b74d-8c585365a604 |
| id                  | 77935422-e2cf-4dc6-85a3-26a58a493f70 |
| port_id             | 97f3ed61-7f9e-4f0a-91af-e95a572acd9c |
| router_id           | b94a203d-5317-4d0b-9833-5c65e01bd76f |
| status              | DOWN                                 |
| tenant_id           | dea8b51d28bf41599e63464828102759     |
+---------------------+--------------------------------------+

預設情況下,只有 admin 才能使用,不過,可以修改 policy.json 檔案將其向普通租戶開放。

2.4.2 Neturon DNAT Chains 

  外網訪問虛機時,目的 IP 地址為虛機的浮動 IP 地址,因此,必須由 iptables 將其轉化為固定 IP 地址,然後再將它路由到虛機。我們需要關注的是 iptables 的 nat 表的 PREOUTING chain:

[email protected]:/home/s1# ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N neutron-l3-agent-OUTPUT
-N neutron-l3-agent-PREROUTING               #neutron 增加的 DNAT chain
-A PREROUTING -j neutron-l3-agent-PREROUTING # DNAT 由 neutron 新增的 chain 負責處理
-A OUTPUT -j neutron-l3-agent-OUTPUT
-A neutron-l3-agent-OUTPUT -d 192.168.1.104/32 -j DNAT --to-destination #本機訪問浮動IP 修改為固定 IP
-A neutron-l3-agent-PREROUTING -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 9697 #將虛機訪問 metadata server 的 traffic 埠由 80 改到 9697(由配置項 metadata_port 設定,預設為 9697),那裡有 application 在監聽。具體內容很深,可以參考這篇文章
-A neutron-l3-agent-PREROUTING -d 192.168.1.104/32 -j DNAT --to-destination 91.1.180.14 #到浮動IP的traffic的目的IP 換成虛機的固定 IP

每個浮動 IP,增加三個規則:

-A neutron-l3-agent-PREROUTING -d <floatingip> -j DNAT --to-destination <fixedip> #從本機訪問虛機,Dst IP 由浮動IP該為訪問固定IP 
-A neutron-l3-agent-OUTPUT -d <floatingip> -j DNAT --to <fixedip>                 #從別的機器上訪問虛機,DST IP 由浮動IP改為固定IP
-A neutron-l3-agent-float-snat -s <fixedip> -j SNAT --to <floatingip>             #虛機訪問外網,將Src IP 由固定IP改為浮動IP

這裡可以看到當設定了浮動 IP 以後,SNAT 不在使用 External Gateway 的 IP 了,而是使用浮動 IP 。雖然 entires 依然存在,但是因為 neutron-l3-agent-float-snat 比 neutron-l3-agent-snat 靠前而得到執行。

-A neutron-l3-agent-float-snat -s 91.1.180.14/32 -j SNAT --to-source 192.168.1.104
-A neutron-l3-agent-snat -j neutron-l3-agent-float-snat
-A neutron-l3-agent-snat -s 91.1.180.0/24 -j SNAT --to-source 192.168.1.110
-A neutron-l3-agent-snat -s 81.1.180.0/24 -j SNAT --to-source 192.168.1.110
-A neutron-postrouting-bottom -j neutron-l3-agent-snat

2.4.3 實驗:從外網192.168.1.15 ping虛機 81.1.180.14 的浮動IP 192.168.1.104

1. 在route 的連線外網網段的interace 上: 

17:58:46.116944 08:00:27:c7:cf:ca > fa:16:3e:2e:5b:23, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 25176, offset 0, flags [DF], proto ICMP (1), length 84)
    192.168.1.15 > 192.168.1.104: ICMP echo request, id 24530, seq 4, length 64
17:58:46.117910 fa:16:3e:2e:5b:23 > 08:00:27:c7:cf:ca, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 63, id 23128, offset 0, flags [none], proto ICMP (1), length 84)
    192.168.1.104 > 192.168.1.15: ICMP echo reply, id 24530, seq 4, length 64

 2. 在 router 的連線內網網段的interface上:

[email protected]:/home/s1# ip netns exec qrouter-e438bebe-6795-4b68-a613-ec0df38d3064 tcpdump -envi qr-2aa928c9-e8 -vvv
tcpdump: listening on qr-2aa928c9-e8, link-type EN10MB (Ethernet), capture size 65535 bytes
^C19:46:12.266739 fa:16:3e:90:e5:50 > fa:16:3e:f3:1e:c0, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 63, id 53299, offset 0, flags [DF], proto ICMP (1), length 84)
192.168.1.15 > 91.1.180.14: ICMP echo request, id 2831, seq 1, length 64
19:46:12.269143 fa:16:3e:f3:1e:c0 > fa:16:3e:90:e5:50, ethertype IPv4 (0x0800), length 98: (tos 0x0, ttl 64, id 23157, offset 0, flags [none], proto ICMP (1), length 84)
91.1.180.14 > 192.168.1.15: ICMP echo reply, id 2831, seq 1, length 64

2.5 L3 Agent iptables 完整流程實驗

該實驗中使用 “ iptables -t nat  -L -nv” 命令來檢視每個鏈上匹配到的資料包數目。

2.4.1 虛機 91.1.180.14 ping 另一個虛機 81.1.180.12

可見:

(1)DNAT 匹配到的是預設的 Policy

(2)SNAT 匹配到 “-A neutron-l3-agent-POSTROUTING ! -i qg-3c8d6a68-97 ! -o qg-3c8d6a68-97 -m conntrack ! --ctstate DNAT -j ACCEPT” 規則後就被 Accept 了。

2.5.1 虛機 91.1.180.14 ping 外網 192.168.1.4

可見:

(1)DNAT 匹配到的是預設的 Policy ACCEPT

(2)SNAT 匹配到 “-A neutron-l3-agent-float-snat -s 91.1.180.14/32 -j SNAT --to-source 192.168.1.104” 規則後做 SNAT。

2.5.2 外網 192.168.1.4 ping 虛機 192.168.1.104(91.1.180.14)

可見:

(1)DNAT 匹配到 “-A neutron-l3-agent-PREROUTING -d 192.168.1.104/32 -j DNAT --to-destination 91.1.180.14” 然後做 DNAT。

(2)SNAT 匹配到的是預設的 Policy ACCEPT。

  為什麼結果中顯示的 pacakge 數目只是1呢?參考網上文章,對於 SNAT 和 DNAT target 來說,如果一個包被匹配了,那麼和它屬於同一個流的所有的包都會被自動轉換,然後就可以被路由到正確的主機或網路,這麼說來,一個流中被匹配的包的數目就是1了。

總結:

(圖片來源。Neutron 程式碼分析也可以參考這篇文章。) 

3. Neutron L3 Agent 主要程式碼結構

L3 Agent 啟動後,它有若干個 Workers 去 MQ 中拿資料,然後將資料放進一個內部的 queue 中。它還會啟動一個迴圈執行緒去queue 中取資料。當發現有 router 相關的操作發生後,即呼叫 _process_routers_loop 方法去處理獲取的資料。

3.1 L3 Agent 啟動

Neutorn L3 Agent 的 binary 是 /usr/bin/neutron-l3-agent,其main 會初始化一個 class Service(n_rpc.Service) 類的例項。在該例項的 start 函式中,它會啟動兩個週期性任務:

(1).啟動 一個執行緒來執行迴圈函式 _process_routers_loop 來處理第二個週期性任務新增到  _queue 中的每一個 router 的 action,包括刪除、新增和更新。對每一個待處理的 router,最終會呼叫到 RouterInfo.process 方法。注意 router 的操作都是在其對應的 namespace 中進行的。在 namespace 建立的時候,執行 'sysctl -w net.ipv4.ip_forward=1' 來使得它能夠做 IP 路由轉發。

def process(self, agent):
        """Process updates to this router
        This method is the point where the agent requests that updates be   applied to this router.
        :param agent: Passes the agent in order to send RPC messages.
        """
        self._process_internal_ports()
        self.process_external(agent)
        # Process static routes for router
        self.routes_updated()

        # Update ex_gw_port and enable_snat on the router info cache
        self.ex_gw_port = self.get_ex_gw_port()
        self.snat_ports = self.router.get(
            l3_constants.SNAT_ROUTER_INTF_KEY, [])
        self.enable_snat = self.router.get('enable_snat')

(2)啟動一個週期性函式 periodic_sync_routers_task。它負責通過 RPC 獲取到 router 列表,然後將需要增加和刪除的 router 加入到 _queue 中。

3.2 Router 處理

L3 Agent 的核心是 Router 的處理。

(1)處理 external gateway (比如,external_gateway_added 增加 external gateway:獲取該 router 的所有浮動 IP,在agent_conf.external_network_bridge 所指定的外網物理 OVS bridge 上增加一個 tap 裝置,名稱為 “gq-*”,然後設定其 MAC 地址,MTU 等)

(2)修改路由表 (routes_updated)

(3)在有 external gateway 時,設定 SNAT iptables (_handle_router_snat_rules):先刪除當前所有 POSTROUTING 和 snat chains,然後再增加:

#增加 SNAT chain
-N neutron-l3-agent-float-snat
#為 external gateway
-A neutron-l3-agent-POSTROUTING ! -i qg-3c8d6a68-97 ! -o qg-3c