天天讲路由,那 Linux 路由到底咋实现的!?
作者 | 張彥飛allen
來源 | 開發(fā)內(nèi)功修煉
容器是一種新的虛擬化技術(shù),每一個容器都是一個邏輯上獨立的網(wǎng)絡(luò)環(huán)境。Linux 上提供了軟件虛擬出來的二層交換機(jī) Bridge 可以解決同一個宿主機(jī)上多個容器之間互連的問題,但這是不夠的。二層交換無法解決容器和宿主機(jī)外部網(wǎng)絡(luò)的互通。
容器肯定是需要和宿主機(jī)以外的外部網(wǎng)絡(luò)互通才具備實用價值的。比如在 Kubernets 中,就要求所有的 pod 之間都可以互通。相當(dāng)于在原先物理機(jī)所組成的網(wǎng)絡(luò)之上,要再建一個互通的虛擬網(wǎng)絡(luò)出來。這就是 Overlay 網(wǎng)絡(luò)的概念,用一個簡單的示例圖表示如下。
回想在傳統(tǒng)物理物理網(wǎng)絡(luò)中,不同子網(wǎng)之間的服務(wù)器是如何互聯(lián)起來的呢,沒錯,就是在三層工作的路由器,也叫網(wǎng)關(guān)。路由器使得數(shù)據(jù)包可以從一個子網(wǎng)中傳輸?shù)搅硪粋€子網(wǎng)中,進(jìn)而實現(xiàn)更大范圍的網(wǎng)絡(luò)互通。如下圖所示,一臺路由器將 192.168.0.x 和 192.168.1.x 兩個子網(wǎng)連接了起來。
在容器虛擬化網(wǎng)絡(luò)中,自然也需要這么一個角色,將容器和宿主機(jī)以外的網(wǎng)絡(luò)連接起來。其實 Linux 天生就具備路由的功能,只是在云原生時代,它的路由功能再一次找到了用武之地。在容器和外部網(wǎng)絡(luò)通信的過程中,Linux 就又承擔(dān)起路由器的角色,實現(xiàn)容器數(shù)據(jù)包的正確轉(zhuǎn)發(fā)和投遞。
在各種基于容器的云原生技術(shù)盛行的今天,再次回頭深刻理解路由工作原理顯得非常有必要,而且也非常的有價值。今天,我們就再來強(qiáng)化一下 Linux 上的路由知識!
一、什么時候需要路由
先來聊聊 Linux 在什么情況下需要路由過程。其實在發(fā)送數(shù)據(jù)時和接收數(shù)據(jù)時都會涉及到路由選擇,為什么?我們挨個來看。
1.1 發(fā)送數(shù)據(jù)時選路
Linux 之所以在發(fā)送數(shù)據(jù)包的時候需要進(jìn)行路由選擇,這是因為服務(wù)器上是可能會有多張網(wǎng)卡設(shè)備存在的。數(shù)據(jù)包在發(fā)送的時候,一路通過用戶態(tài)、TCP 層到了 IP 層的時候,就要進(jìn)行路由選擇,以決定使用哪張網(wǎng)卡設(shè)備把數(shù)據(jù)包送出去。
來大致過一下路由相關(guān)源碼源碼。網(wǎng)絡(luò)層發(fā)送的入口函數(shù)是 ip_queue_xmit。
在 ip_queue_xmit 里我們開頭就看到了路由項查找, ip_route_output_ports 這個函數(shù)中完成路由選擇。路由選擇就是到路由表中進(jìn)行匹配,然后決定使用哪個網(wǎng)卡發(fā)送出去。
Linux 中最多可以有 255 張路由表,其中默認(rèn)情況下有 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 接收數(shù)據(jù)時選路
沒錯,接收數(shù)據(jù)包的時候也需要進(jìn)行路由選擇。這是因為 Linux 可能會像路由器一樣工作,將收到的數(shù)據(jù)包通過合適的網(wǎng)卡將其轉(zhuǎn)發(fā)出去。
Linux 在 IP 層的接收入口 ip_rcv 執(zhí)行后調(diào)用到 ip_rcv_finish。在這里展開路由選擇。如果發(fā)現(xiàn)確實就是本設(shè)備的網(wǎng)絡(luò)包,那么就通過 ip_local_deliver 送到更上層的 TCP 層進(jìn)行處理。
如果路由后發(fā)現(xiàn)非本設(shè)備的網(wǎng)絡(luò)包,那就進(jìn)入到 ip_forward 進(jìn)行轉(zhuǎn)發(fā),最后通過 ip_output 發(fā)送出去。
具體的代碼如下。
//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 就是在進(jìn)行路由查找。
//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 路由小結(jié)
路由在內(nèi)核協(xié)議棧中的位置可以用如下一張圖來表示。
網(wǎng)絡(luò)包在發(fā)送的時候,需要從本機(jī)的多個網(wǎng)卡設(shè)備中選擇一個合適的發(fā)送出去。網(wǎng)絡(luò)包在接收的時候,也需要進(jìn)行路由選擇,如果是屬于本設(shè)備的包就往上層送到網(wǎng)絡(luò)層、傳輸層直到 socket 的接收緩存區(qū)中。如果不是本設(shè)備上的包,就選擇合適的設(shè)備將其轉(zhuǎn)發(fā)出去。
二、Linux 的路由實現(xiàn)
2.1 路由表
路由表(routing table)在內(nèi)核源碼中的另外一個叫法是轉(zhuǎn)發(fā)信息庫(Forwarding Information Base,FIB)。所以你在源碼中看到的 fib 開頭的定義基本上就是和路由表相關(guān)的功能。
其中路由表本身是用 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 來組織和管理。它是放在網(wǎng)絡(luò)命名空間 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... }在默認(rèn)情況下,Linux 只有 local 和 main 兩個路由表。如果內(nèi)核編譯時支持策略路由,那么管理員最多可以配置 ?255 個獨立的路由表。
如果你的服務(wù)器上創(chuàng)建了多個網(wǎng)絡(luò)命名空間的話,那么就會存在多套路由表。以除了默認(rèn)命名網(wǎng)絡(luò)空間外,又創(chuàng)了了一個新網(wǎng)絡(luò)命名空間的情況為例,路由表在整個內(nèi)核數(shù)據(jù)結(jié)構(gòu)中的關(guān)聯(lián)關(guān)系總結(jié)如下圖所示。
2.2 路由查找
在上面的小節(jié)中我們看到,發(fā)送過程調(diào)用 ip_route_output_ports 來查找路由,接收過程調(diào)用 ip_route_input_slow 來查找。但其實這兩個函數(shù)都又最終會調(diào)用到 fib_lookup 這個核心函數(shù),源碼如下。
//file:?net/ipv4/route.c struct?rtable?*__ip_route_output_key(struct?net?*net,?struct?flowi4?*fl4) {...//?進(jìn)入?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) {...//?進(jìn)入?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; }這個函數(shù)就是依次到 local 和 main 表中進(jìn)行匹配,匹配到后就返回,不會繼續(xù)往下匹配。從上面可以看到 local 表的優(yōu)先級要高于 main 表,如果 local 表中找到了規(guī)則,則路由過程就結(jié)束了。
這也就是很多同學(xué)說為什么 ping 本機(jī)的時候在 eth0 上抓不到包的根本原因。所有命中 local 表的包都會被送往 loopback 設(shè)置,不會過 eth0。
三、路由的使用方法
3.1 開啟轉(zhuǎn)發(fā)路由
在默認(rèn)情況下,Linux 上的轉(zhuǎn)發(fā)功能是關(guān)閉的,這時候 Linux 發(fā)現(xiàn)收到的網(wǎng)絡(luò)包不屬于自己就會將其丟棄。
但在某些場景下,例如對于容器網(wǎng)絡(luò)來說,Linux 需要轉(zhuǎn)發(fā)本機(jī)上其它網(wǎng)絡(luò)命名空間中過來的數(shù)據(jù)包,需要手工開啟轉(zhuǎn)發(fā)。如下這兩種方法都可以。
#?sysctl?-w?net.ipv4.ip_forward=1 #?sysctl?net.ipv4.conf.all.forwarding=1開啟后,Linux 就能像路由器一樣對不屬于本機(jī)(嚴(yán)格地說是本網(wǎng)絡(luò)命名空間)的 IP 數(shù)據(jù)包進(jìn)行路由轉(zhuǎn)發(fā)了。
3.2 查看路由表
在默認(rèn)情況下,Linux 只有 local 和 main 兩個路由表。如果內(nèi)核編譯時支持策略路由,那么管理員最多可以配置 ?255 個獨立的路由表。在 centos 上可以通過以下方式查看是否開啟了 CONFIG_IP_MULTIPLE_TABLES 多路由表支持。
#?cat?/boot/config-3.10.0-693.el7.x86_64? CONFIG_IP_MULTIPLE_TABLES=y ...所有的路由表按照從 0 - 255 進(jìn)行編號,每個編號都有一個別名。編號和別名的對應(yīng)關(guān)系在 /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,也可以是一個網(wǎng)段,和 Genmask 一起表示。
Gateway:網(wǎng)關(guān)地址,如果是 0.0.0.0 表示不需要經(jīng)過網(wǎng)關(guān)。
Flags: U 表示有效,G 表示連接路由,H 這條規(guī)則是主機(jī)路由,而不是網(wǎng)絡(luò)路由。
Iface:網(wǎng)卡設(shè)備,使用哪個網(wǎng)卡將包送過去。
上述結(jié)果中輸出的第一條路由規(guī)則表示這臺機(jī)器下,確切地說這個網(wǎng)絡(luò)環(huán)境下,所有目標(biāo)為 10.0.0.0/8(Genmask 255.0.0.0 表示前 8 位為子網(wǎng)掩碼) 網(wǎng)段的網(wǎng)絡(luò)包都要通過 eth0 設(shè)備送到 10...254 這個網(wǎng)關(guān),由它再幫助轉(zhuǎn)發(fā)。
第二條路由規(guī)則表示,如果目的地址是 10...0/21(Genmask 255.255.248.0 表示前 21 位為子網(wǎng)掩碼)則直接通過 eth0 發(fā)出即可,不需要經(jīng)過網(wǎng)關(guān)就可通信。
3.3 修改路由表
默認(rèn)的 local 路由表是內(nèi)核根據(jù)當(dāng)前機(jī)器的網(wǎng)卡設(shè)備配置自動生成的,不需要手工維護(hù)。對于main 的路由表配置我們一般只需要使用 route add 命令就可以了,刪除使用 route del。
修改主機(jī)路由
#?route?add?-host?192.168.0.100?dev?eth0?//直連不用網(wǎng)關(guān) #?route?add?-host?192.168.1.100?dev?eth0?gw?192.168.0.254?//下一跳網(wǎng)關(guān)修改網(wǎng)絡(luò)路由
#?route?add?-net?192.168.1.0/24?dev?eth0?//直連不用網(wǎng)關(guān) #?route?add?-net?192.168.1.0/24?dev?eth0?gw?10.162.132.110?//下一跳網(wǎng)關(guān)也可以指定一條默認(rèn)規(guī)則,不命中其它規(guī)則的時候會執(zhí)行到這條。
#?route?add?default?gw?192.168.0.1?eth0對于其它編號的路由表想要修改的話,就需要使用 ip route 命令了。這里不過多展開,只用 main 表舉一個例子,有更多使用需求的同學(xué)請自行搜索。
#?ip?route?add?192.168.5.0/24?via?10.*.*.110?dev?eth0?table?main3.4 路由規(guī)則測試
在配置了一系列路由規(guī)則后,為了快速校驗是否符合預(yù)期,可以通過 ip route get 命令來確認(rèn)。
#?ip?route?get?192.168.2.25 192.168.2.25?via?10.*.*.110?dev?eth0?src?10.*.*.161cache本文總結(jié)
在現(xiàn)如今各種網(wǎng)絡(luò)虛擬化技術(shù)里,到處都能看著對路由功能的靈活應(yīng)用。所以我們今天專門深入研究了一下 Linux 路由工作原理。
在 Linux 內(nèi)核中,對于發(fā)送過程和接收過程都會涉及路由選擇,其中接收過程的路由選擇是為了判斷是該本地接收還是將它轉(zhuǎn)發(fā)出去。
默認(rèn)有 local 和 main 兩個路由表,不過如果安裝的 linux 開啟了 CONFIG_IP_MULTIPLE_TABLES 選項的話,最多能支持 255 張路由表。
路由選擇過程其實不復(fù)雜,就是根據(jù)各個路由表的配置找到合適的網(wǎng)卡設(shè)備,以及下一跳的地址,然后把包轉(zhuǎn)發(fā)出去就算是完事。
通過合適地配置路由規(guī)則,容器中的網(wǎng)絡(luò)環(huán)境和外部的通信不再是難事。通過大量地干預(yù)路由規(guī)則就可以實現(xiàn)虛擬網(wǎng)絡(luò)互通。
往期推薦
如果讓你來設(shè)計網(wǎng)絡(luò)
寫時復(fù)制就這么幾行代碼,還是不會?
留不住客戶?該從你的系統(tǒng)上找找原因了!
明明還有大量內(nèi)存,為啥報錯“無法分配內(nèi)存”?
點分享
點收藏
點點贊
點在看
總結(jié)
以上是生活随笔為你收集整理的天天讲路由,那 Linux 路由到底咋实现的!?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2017双11技术揭秘—TDDL/DRD
- 下一篇: 00后确实卷,公司新来的卷王,我们这帮老