學習 Neutron 系列文章:
(2)Neutron OpenvSwitch + VLAN 虛擬網絡
(3)Neutron OpenvSwitch + GRE/VxLAN 虛擬網絡
(4)Neutron OVS OpenFlow 流表 和 L2 Population
(9)Neutron FWaas 和 Nova Security Group
(10)Neutron VPNaas
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 之間的通信:
(2)當有兩個以上的 network namespace 之間需要通信時,需要使用一個虛機交換機,和兩個 veth pair。傳統的方式是 Linux bridge:
你也可以使用 Open vSwitch:
(3)再一種方式是使用 Open vSwitch 和 OVS internal 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 是一套數據包過濾框架,在處理 IP 數據包時 hook 了5個關鍵鈎子。通過這5個關鍵點來實現各種功能,比如firewall/ips。
- ip_tables 是真正的內核防火牆模塊,通過把自己的函數注入到 Netfilter 的框架中來實現的防火牆功能.
- Netfilter 提供了最基本的底層支撐,具體的功能實現只要注冊自己的函數就可以了,這樣保證了協議棧的純凈與可擴展性.通過上圖可以看出 netfilter 與 iptables是分離的.
數據包處理過程:
- 數據包從左邊進入IP協議棧,進行 IP 校驗以后,數據包被第一個鈎子函數 PRE_ROUTING 處理。
- 然后就進入路由模塊,由其決定該數據包是轉發出去還是送給本機。
- 若該數據包是送給本機的,則要經過鈎子函數 LOCAL_IN 處理后傳遞給本機的上層協議;若該數據包應該被轉發,則它將被鈎子函數 FORWARD 處理,然后還要經鈎子函數 POST_ROUTING 處理后才能傳輸到網絡。
- 本機進程產生的數據包要先經過鈎子函數 LOCAL_OUT 處理后,再進行路由選擇處理,然后經過鈎子函數POST_ROUTING處理后再發送到網絡。
netfilter 使用表(table)和 鏈(chain)來組織網絡包的處理規則(rule)。它默認定義了以下表和鏈:
表 | 表功能 | 鏈 | 鏈功能 |
---|---|---|---|
raw | PREROUTING |
RAW 擁有最高的優先級,它使用PREROUTING和OUTPUT兩個鏈,因此 RAW 可以覆蓋所有包。在raw表中支持一個特殊的目標:TRACE,使內核記錄下每條匹配該包的對應iptables規則信息。使用raw表內的TRACE target 即可實現對iptables規則的跟蹤調試。比如: # iptables -t raw -A OUTPUT -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 各命令選項:
(引用自 http://fishcried.com/2014-08-29/iptable/)
- -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 的具體數據流向:
- 封包先經過 PREROUTING,檢查目的 IP 是不是本網段的地址。是的話,走路徑A。
- 如果不是,則開始查詢路由表,查找到相應路由條目后(查找路由的過程在 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 的具體數據流向:
- 在 DNAT 中,NAT 是在 PREROUTING 上做的。在數據進入主機后,路由選擇過程是在 PREROUTING 和 FORWARD 之間的,所以應該先做地址轉換之后,再進行路由選擇,而后經過 FORWARD,最后從 POSTROUTING 出去。
- 因此,要做 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
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 的角色。
s1@controller:~$ 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 地址。比如:
root@network:/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 在啟動時設置如下的路由規則:
root@network:/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 鏈。
root@network:/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 上:
root@network:/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 上
root@network:/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:
root@sun:~# 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:
root@sun:~# 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"} |
root@sun:~# 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。
blueprint:Allow specific Floating IP Address
效果:Neutorn floatingip-create cli 上增加了一個參數 --floating-ip-address 用於指定創建的浮動IP地址:
root@controller:~/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:
root@network:/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上:
root@network:/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-3c8d6a68-97 -m conntrack ! --ctstate DNAT -j ACCEPT
#為每一個子網創建一條 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
(4)如果有 external gateway 的話,處理浮動 IP 的 SNAT/DNAT iptables (process_snat_dnat_for_fip)
#為每一個浮動 IP,以 192.168.1.104 為例 -A neutron-l3-agent-PREROUTING -d 192.168.1.104/32 -j DNAT --to-destination 91.1.180.14 #DNAT -A neutron-l3-agent-OUTPUT -d 192.168.1.104/32 -j DNAT --to-destination 91.1.180.14 #本機訪問 -A neutron-l3-agent-float-snat -s 91.1.180.14/32 -j SNAT --to-source 192.168.1.104 #SNAT
(5)將浮動IP 配置到 external gateway port (process_floating_ip_addresses -> add_floating_ip -> _add_fip_addr_to_device)
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
(6)除了上述操作,L3 Agent 還在創建/添加 router 的每一個端口(包括internal 的和 external 的)時發出 Gratuitous ARP,通知其所在廣播域內的其它機器去更新它的 ARP 表中該 IP 條目。比如:
'ip netns exec qrouter-b49a8032-a676-4ef9-aade-355592949aaa arping -A -I qg-e83aae8d-d2 -c 3 192.168.1.100'
其中,“-A” 表示不需要 ARP 返回包;“-I” 表示通過指定的 port 發出包,這樣就算只對有需要的subnet 發出包了;“-c 3” 表示嘗試3次。
3.3 浮動 IP 處理
在創建、綁定、去綁定或者刪除浮動IP時,neutron server 首先執行 DB 操作,然后調用 RPC (notify_routers_updated) 去通知指定的 Router:”create_floatingip“,”update_floatingip“,”delete_floatingip“ 等。
參考鏈接:
歡迎大家關注我的個人公眾號: