天天讲路由,那 Linux 路由到底咋实现的!?
作者 | 張彥飛allen
來源 | 開發內功修煉
容器是一種新的虛擬化技術,每一個容器都是一個邏輯上獨立的網絡環境。Linux 上提供了軟件虛擬出來的二層交換機 Bridge 可以解決同一個宿主機上多個容器之間互連的問題,但這是不夠的。二層交換無法解決容器和宿主機外部網絡的互通。
容器肯定是需要和宿主機以外的外部網絡互通才具備實用價值的。比如在 Kubernets 中,就要求所有的 pod 之間都可以互通。相當于在原先物理機所組成的網絡之上,要再建一個互通的虛擬網絡出來。這就是 Overlay 網絡的概念,用一個簡單的示例圖表示如下。
回想在傳統物理物理網絡中,不同子網之間的服務器是如何互聯起來的呢,沒錯,就是在三層工作的路由器,也叫網關。路由器使得數據包可以從一個子網中傳輸到另一個子網中,進而實現更大范圍的網絡互通。如下圖所示,一臺路由器將 192.168.0.x 和 192.168.1.x 兩個子網連接了起來。
在容器虛擬化網絡中,自然也需要這么一個角色,將容器和宿主機以外的網絡連接起來。其實 Linux 天生就具備路由的功能,只是在云原生時代,它的路由功能再一次找到了用武之地。在容器和外部網絡通信的過程中,Linux 就又承擔起路由器的角色,實現容器數據包的正確轉發和投遞。
在各種基于容器的云原生技術盛行的今天,再次回頭深刻理解路由工作原理顯得非常有必要,而且也非常的有價值。今天,我們就再來強化一下 Linux 上的路由知識!
一、什么時候需要路由
先來聊聊 Linux 在什么情況下需要路由過程。其實在發送數據時和接收數據時都會涉及到路由選擇,為什么?我們挨個來看。
1.1 發送數據時選路
Linux 之所以在發送數據包的時候需要進行路由選擇,這是因為服務器上是可能會有多張網卡設備存在的。數據包在發送的時候,一路通過用戶態、TCP 層到了 IP 層的時候,就要進行路由選擇,以決定使用哪張網卡設備把數據包送出去。
來大致過一下路由相關源碼源碼。網絡層發送的入口函數是 ip_queue_xmit。
在 ip_queue_xmit 里我們開頭就看到了路由項查找, ip_route_output_ports 這個函數中完成路由選擇。路由選擇就是到路由表中進行匹配,然后決定使用哪個網卡發送出去。
Linux 中最多可以有 255 張路由表,其中默認情況下有 local 和 main 兩張。使用 ip 命令可以查看路由表的具體配置。拿 local 路由表來舉例。
#ip?route?list?table?local local?10.143.x.y?dev?eth0?proto?kernel?scope?host?src?10.143.x.y local?127.0.0.1?dev?lo?proto?kernel?scope?host?src?127.0.0.11.2 接收數據時選路
沒錯,接收數據包的時候也需要進行路由選擇。這是因為 Linux 可能會像路由器一樣工作,將收到的數據包通過合適的網卡將其轉發出去。
Linux 在 IP 層的接收入口 ip_rcv 執行后調用到 ip_rcv_finish。在這里展開路由選擇。如果發現確實就是本設備的網絡包,那么就通過 ip_local_deliver 送到更上層的 TCP 層進行處理。
如果路由后發現非本設備的網絡包,那就進入到 ip_forward 進行轉發,最后通過 ip_output 發送出去。
具體的代碼如下。
//file:?net/ipv4/ip_input.c static?int?ip_rcv_finish(struct?sk_buff?*skb){...if?(!skb_dst(skb))?{int?err?=?ip_route_input_noref(skb,?iph->daddr,?iph->saddr,iph->tos,?skb->dev);...}...return?dst_input(skb); }其中 ip_route_input_noref 就是在進行路由查找。
//file:?net/ipv4/route.c int?ip_route_input_noref(struct?sk_buff?*skb,?__be32?daddr,?__be32?saddr,u8?tos,?struct?net_device?*dev) {...res?=?ip_route_input_slow(skb,?daddr,?saddr,?tos,?dev);return?res; }這里記著 ip_route_input_slow 就行了,后面我們再看。
1.3 linux 路由小結
路由在內核協議棧中的位置可以用如下一張圖來表示。
網絡包在發送的時候,需要從本機的多個網卡設備中選擇一個合適的發送出去。網絡包在接收的時候,也需要進行路由選擇,如果是屬于本設備的包就往上層送到網絡層、傳輸層直到 socket 的接收緩存區中。如果不是本設備上的包,就選擇合適的設備將其轉發出去。
二、Linux 的路由實現
2.1 路由表
路由表(routing table)在內核源碼中的另外一個叫法是轉發信息庫(Forwarding Information Base,FIB)。所以你在源碼中看到的 fib 開頭的定義基本上就是和路由表相關的功能。
其中路由表本身是用 struct fib_table 來表示的。
//file:?include/net/ip_fib.h struct?fib_table?{struct?hlist_node?tb_hlist;u32???tb_id;int???tb_default;int???tb_num_default;unsigned?long??tb_data[0]; };所有的路由表都通過一個 hash - fib_table_hash 來組織和管理。它是放在網絡命名空間 net 下的。這也就說明每個命名空間都有自己獨立的路由表。
//file:include/net/net_namespace.h struct?net?{struct?netns_ipv4?ipv4;... }//file:?include/net/netns/ipv4.h struct?netns_ipv4?{//?所有路由表?struct?hlist_head?*fib_table_hash;//?netfilter... }在默認情況下,Linux 只有 local 和 main 兩個路由表。如果內核編譯時支持策略路由,那么管理員最多可以配置 ?255 個獨立的路由表。
如果你的服務器上創建了多個網絡命名空間的話,那么就會存在多套路由表。以除了默認命名網絡空間外,又創了了一個新網絡命名空間的情況為例,路由表在整個內核數據結構中的關聯關系總結如下圖所示。
2.2 路由查找
在上面的小節中我們看到,發送過程調用 ip_route_output_ports 來查找路由,接收過程調用 ip_route_input_slow 來查找。但其實這兩個函數都又最終會調用到 fib_lookup 這個核心函數,源碼如下。
//file:?net/ipv4/route.c struct?rtable?*__ip_route_output_key(struct?net?*net,?struct?flowi4?*fl4) {...//?進入?fib_lookupif?(fib_lookup(net,?fl4,?&res))?{} }//file:?net/ipv4/route.c static?int?ip_route_input_slow(struct?sk_buff?*skb,?__be32?daddr,?__be32?saddr,u8?tos,?struct?net_device?*dev) {...//?進入?fib_lookuperr?=?fib_lookup(net,?&fl4,?&res); }我們來看下 fib_loopup 都干了啥。為了容易理解,我們只看一下不支持多路由表版本的 fib_lookup。
//file:?include/net/ip_fib.h static?inline?int?fib_lookup(struct?net?*net,?const?struct?flowi4?*flp,struct?fib_result?*res) {struct?fib_table?*table;table?=?fib_get_table(net,?RT_TABLE_LOCAL);if?(!fib_table_lookup(table,?flp,?res,?FIB_LOOKUP_NOREF))return?0;table?=?fib_get_table(net,?RT_TABLE_MAIN);if?(!fib_table_lookup(table,?flp,?res,?FIB_LOOKUP_NOREF))return?0;return?-ENETUNREACH; }這個函數就是依次到 local 和 main 表中進行匹配,匹配到后就返回,不會繼續往下匹配。從上面可以看到 local 表的優先級要高于 main 表,如果 local 表中找到了規則,則路由過程就結束了。
這也就是很多同學說為什么 ping 本機的時候在 eth0 上抓不到包的根本原因。所有命中 local 表的包都會被送往 loopback 設置,不會過 eth0。
三、路由的使用方法
3.1 開啟轉發路由
在默認情況下,Linux 上的轉發功能是關閉的,這時候 Linux 發現收到的網絡包不屬于自己就會將其丟棄。
但在某些場景下,例如對于容器網絡來說,Linux 需要轉發本機上其它網絡命名空間中過來的數據包,需要手工開啟轉發。如下這兩種方法都可以。
#?sysctl?-w?net.ipv4.ip_forward=1 #?sysctl?net.ipv4.conf.all.forwarding=1開啟后,Linux 就能像路由器一樣對不屬于本機(嚴格地說是本網絡命名空間)的 IP 數據包進行路由轉發了。
3.2 查看路由表
在默認情況下,Linux 只有 local 和 main 兩個路由表。如果內核編譯時支持策略路由,那么管理員最多可以配置 ?255 個獨立的路由表。在 centos 上可以通過以下方式查看是否開啟了 CONFIG_IP_MULTIPLE_TABLES 多路由表支持。
#?cat?/boot/config-3.10.0-693.el7.x86_64? CONFIG_IP_MULTIPLE_TABLES=y ...所有的路由表按照從 0 - 255 進行編號,每個編號都有一個別名。編號和別名的對應關系在 /etc/iproute2/rt_tables 這個文件里可以查到。
#?cat?/etc/iproute2/rt_tables 255?????local 254?????main 253?????default 0???????unspec 200?????eth0_table查看某個路由表的配置,通過使用 ip route list table {表名} 來查看。
#ip?route?list?table?local local?10.143.x.y?dev?eth0?proto?kernel?scope?host?src?10.143.x.y local?127.0.0.1?dev?lo?proto?kernel?scope?host?src?127.0.0.1如果是查看 main 路由表,也可以直接使用 route 命令
#?route?-n Kernel?IP?routing?table Destination?????Gateway?????????Genmask?????????Flags?Metric?Ref????Use?Iface 10.0.0.0????????10.*.*.254??????255.0.0.0???????UG????0??????0????????0?eth0 10.*.*.0????????0.0.0.0?????????255.255.248.0???U?????0??????0????????0?eth0上面字段中的含義如下
Destination:目的地址,可以是一個具體的 IP,也可以是一個網段,和 Genmask 一起表示。
Gateway:網關地址,如果是 0.0.0.0 表示不需要經過網關。
Flags: U 表示有效,G 表示連接路由,H 這條規則是主機路由,而不是網絡路由。
Iface:網卡設備,使用哪個網卡將包送過去。
上述結果中輸出的第一條路由規則表示這臺機器下,確切地說這個網絡環境下,所有目標為 10.0.0.0/8(Genmask 255.0.0.0 表示前 8 位為子網掩碼) 網段的網絡包都要通過 eth0 設備送到 10...254 這個網關,由它再幫助轉發。
第二條路由規則表示,如果目的地址是 10...0/21(Genmask 255.255.248.0 表示前 21 位為子網掩碼)則直接通過 eth0 發出即可,不需要經過網關就可通信。
3.3 修改路由表
默認的 local 路由表是內核根據當前機器的網卡設備配置自動生成的,不需要手工維護。對于main 的路由表配置我們一般只需要使用 route add 命令就可以了,刪除使用 route del。
修改主機路由
#?route?add?-host?192.168.0.100?dev?eth0?//直連不用網關 #?route?add?-host?192.168.1.100?dev?eth0?gw?192.168.0.254?//下一跳網關修改網絡路由
#?route?add?-net?192.168.1.0/24?dev?eth0?//直連不用網關 #?route?add?-net?192.168.1.0/24?dev?eth0?gw?10.162.132.110?//下一跳網關也可以指定一條默認規則,不命中其它規則的時候會執行到這條。
#?route?add?default?gw?192.168.0.1?eth0對于其它編號的路由表想要修改的話,就需要使用 ip route 命令了。這里不過多展開,只用 main 表舉一個例子,有更多使用需求的同學請自行搜索。
#?ip?route?add?192.168.5.0/24?via?10.*.*.110?dev?eth0?table?main3.4 路由規則測試
在配置了一系列路由規則后,為了快速校驗是否符合預期,可以通過 ip route get 命令來確認。
#?ip?route?get?192.168.2.25 192.168.2.25?via?10.*.*.110?dev?eth0?src?10.*.*.161cache本文總結
在現如今各種網絡虛擬化技術里,到處都能看著對路由功能的靈活應用。所以我們今天專門深入研究了一下 Linux 路由工作原理。
在 Linux 內核中,對于發送過程和接收過程都會涉及路由選擇,其中接收過程的路由選擇是為了判斷是該本地接收還是將它轉發出去。
默認有 local 和 main 兩個路由表,不過如果安裝的 linux 開啟了 CONFIG_IP_MULTIPLE_TABLES 選項的話,最多能支持 255 張路由表。
路由選擇過程其實不復雜,就是根據各個路由表的配置找到合適的網卡設備,以及下一跳的地址,然后把包轉發出去就算是完事。
通過合適地配置路由規則,容器中的網絡環境和外部的通信不再是難事。通過大量地干預路由規則就可以實現虛擬網絡互通。
往期推薦
如果讓你來設計網絡
寫時復制就這么幾行代碼,還是不會?
留不住客戶?該從你的系統上找找原因了!
明明還有大量內存,為啥報錯“無法分配內存”?
點分享
點收藏
點點贊
點在看
總結
以上是生活随笔為你收集整理的天天讲路由,那 Linux 路由到底咋实现的!?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2017双11技术揭秘—TDDL/DRD
- 下一篇: 看穿容器的外表,Linux容器实现原理演