1. 程式人生 > 實用技巧 >linux 路由精講

linux 路由精講

路由的設計遠比一般的理解要複雜的多。典型的路由條目包括了源IP,目的IP,閘道器IP,scope,dev和type六個要素。

閘道器IP就是在配置路由的時候指定的via後面的地址,在路由表中叫Gateway,這是說明這條路由的下一跳是這個IP地址。這個IP地址之所以出現,是因為目的地址不是當前自己出口可以直接可達的,需要經過閘道器路由到下個網路才能投遞。

也就是因此,如果這個via域配置為0.0.0.0,或者是用*表示,總之是代表一定的通配,那麼就意味著這個路由的目的地和自己在一個二層的網路,到達那個目的地並不需要閘道器轉發,只需要配置MAC地址從埠上發出去即可。這個傳送出去的過程顯然是去查ARP表,通過IP地址查詢目標的MAC地址。很容易理解閘道器在路有條目中的意義,如果到達一個目標地址是需要通過閘道器轉發出去的,via就要指定閘道器。大部分的個人區域網中,都會指定一個預設閘道器,目的IP填寫了0.0.0.0,也就是所有的目的地址(通常使用命令的時候,這個詞語叫做default),via後面填寫閘道器地址。這樣在其他的更精確的路由條目都不命中的情況下,就一定會命中這個預設路由條目。因為這個條目的目的IP設定是通配。使用ip命令設定這樣的預設路由是例如dpip route add default via 10.0.0.1。

假設一個路由條目指定了gateway,那麼決策還需要知道這個gateway到底是從哪個網口發出去可達的,這就是dev的作用。熙然到一個gateway必然要從一個裝置出去,而其他的地方並不能指定這個gateway和裝置的對應關係,於是就在路由表這裡就指定了。通過dev可以到達該gateway。

如果gateway不指定,也就是該路由在同一個二層,那麼仍然需要指定dev,因為即使是傳送出去,也需要查從哪裡傳送出去。因為在收到一個數據包的時候,進入系統的時候目的IP不是自己就需要根據目的IP來查詢路由,這個路由會決定這個目的IP是要轉發給哪個埠(通常通過目的IP和閘道器IP和dev來決定)。

Dev相對於對gateway的一個更小的約束。同樣起到約束作用的還有scope。Scope是一個更小程度的約束,指明瞭該路由在什麼場景下才有效。也是用於約束目的地址的。例如不指定閘道器的二層路由,通常對應的scope型別是scope link。scope link的意義就是說明在同一個二層。這個意義與閘道器不指定的效果是呼應的。

有四種scope,global是在任何的場景下都有效,link是在鏈路上才有效,這個鏈路是指同一個埠,也就是說接收和傳送都是走的同一個埠的時候,這條路由才會生效(也就是說在同一個二層)。Global則可以轉發,例如從一個埠收到的包,可以查詢global的路由條目,如果目的地址在另外一個網絡卡,那麼該路由條目可以匹配轉發的要求,進行路由轉發。Link的scope路由條目是不會轉發任何匹配的資料包到其他的硬體網口的。還有就是host,host表示這是一條本地路由,典型的是迴環埠,loopback裝置使用這種路由條目,該路由條目比link型別的還要嚴格,約定了都是本機內部的轉發,不可能轉發到外部。Site則是ipv6專用的路由scope。

源IP是一個路由條目的重要組成部分,這個源IP的意義在於一個補充作用。匹配還是根據目的IP進行匹配,但是由於在查詢路由條目的時候很可能源地址還沒有指定。典型的就是沒有進行bind的傳送情況,通常是隨機選擇埠和按照一定的規則源地址。這個一定的規則就是在這裡的路由條目的src域可以影響。也就是如果程序沒有bind一個源地址,將會使用這裡src域裡面的源地址作為資料包的源地址進行傳送。但是如果程序提前bind了,命中了這個條目,就仍然會使用程序bind的源地址作為資料包的源地址。所以說這裡的src只是一個建議的作用。

# ip route
default via 115.238.122.129 dev eth1 
115.238.122.128/25 dev eth1  proto kernel  scope link  src 115.238.122.163 
192.168.0.160/24 dev dpdk0.kni  proto kernel  scope link  src 192.168.0.163 
192.168.1.160/24 dev dpdk1.kni  proto kernel  scope link  src 192.168.1.163 

舉一個例子,從本機發出的目的地址是192.168.0.160/24網段的資料包將匹配第三條路由,如果在查詢路由表之前沒有設定bind,這個查詢路由表的操作就會把資料包的源地址設定為192.168.0.163 。如果設定了bind,就保留bind的結果(所以你可以很容易的在Linux的主機上偽造原地址傳送資料)。

src域在處理轉發的資料包的時候,由於資料包是從外部收到的,外部進來的資料包也會查詢路由表,也能命中同一個路由條目。但是由於外部進來的資料包已經有了明確的源地址,這裡的src源地址建議就不會起作用了。所以關鍵就是理解src只是一個源地址的一個建議的作用即可。

對於路由表,是一個匹配的過程。一個數據包去查詢匹配自己最能夠匹配哪條路由表,然後就使用該路由條目指定的路由方法進行路由轉發。匹配的方法就是鼎鼎大名的LPM,簡單的說,就是匹配最匹配的那一個。

所以整個過程可以看到,核心的是對目的地址的限制,其他的域都是用於輔助這個限制,甚至可以輔助決策。

我們看一個虛擬機器裡的預設路由表:

root@ubuntu:~/# ip route show

default via 192.168.142.2 dev ens33 proto static metric 100

169.254.0.0/16 dev ens33 scope link metric 1000

192.168.142.0/24 dev ens33 proto kernel scope link src 192.168.142.135 metric 100

跳過default,後面兩條的第一個域都是目的地址,確切的說,這裡指定的是目的網段,然後約束了裝置,也就是enc33,這個路由條目是link scope的,也就是說當主機收到目標地址是169.254.0.0/16 這個網段的時候,通過ens33這個裝置將包轉發出去。

雖然理論上是如此,但是實際上,例如在linux中,這個dev ens33是沒有在路由中起到任何作用的,也就是說你收到改了enc33的名字,而不改路由表,那這個路由表項一樣命中,從改名後的網口傳送出去。所以dev的這個限制相當於不存在,也就是隻是一個命名的作用。但是並不確定在其他的實現中是否有限制的意義。

Default路由本質上就是目標地址填了0.0.0.0的路由。Default路由有兩種新增方式,一種是約束閘道器地址,另外一種是約束源IP。因為要新增到閘道器地址的預設路由,是需要在新增的時候發一個arp請求到網路上,看這個閘道器的地址是否存在於二層的,但是這個arp請求也是需要首先經過路由的。也就是一個雞生蛋,蛋生雞的問題。所以一個空的路由表是不能直接配置預設路由是一個閘道器的。但是明明閘道器確實是和當前的主機在同一個網段的。

如果要配置預設閘道器,首先需要先讓這個網路通。這個通的方法一個是配一個link scope的路由,也就是目的地址是該網段的發包,都可以匹配這個路由。因為是link scope的,所有的請求都會走二層的路由表,這就解決了arp不能到達閘道器的問題。Link scope的特點是所有的資料請求走二層arp,而不是走三層路由。所以在配置了這條路由之後,再配置閘道器就可以了。

但是還有一個思路是使用源地址約束,我們要的只是這個查詢能命中一個可以出去的路由,當我們使用源地址約束,不指定目標地址,也就是源地址是自己裝置的IP的地址的包全部走link scope,同樣也可以匹配,由於是link scope,也就可以觸發arp請求了。

所以我們看到,整個過程的關鍵在於區分同二層和三層轉發。Link scope的作用是用在二層轉發,命中該路由條目的可以觸發arp查詢,但是如果是閘道器式的,就是一個三層轉發,雖然也會觸發arp查詢,但是目標MAC地址永遠是閘道器的地址,這樣下一跳就鎖死了。

但是這裡有一個問題是如果先添加了link scope的路由條目,然後又添加了gateway的路由,這個時候再把link scope的路由條目刪除,那麼gateway的路由條目仍然存在並且生效,這個時候,所有的轉發都會匹配這個gateway的路由條目,包括本來應該走二層轉發的資料包。也就是說,原本應該在同一個二層傳輸走arp的資料包,在這種情況下,也會直接走閘道器,網關回復一個icmp redirect,但是閘道器仍然會把這個資料包轉發到同一個二層的目標地址。

理論上,收到icmp redirect的主機應當更新自己的arp表,但是並不會更新路由表,而arp表是要先經過路由表查詢的,所以這個icmp redirect相當於沒有意義。Arp表裡面即使是有了IP到MAC的對映關係,但是由於路由沒有命中link scope,所以永遠不會查詢ARP表。

另外linux下的路由條目還會有一個proto的域,一般有proto kernel和proto dhcp兩種。Proto表明的是這個路由條目是由誰新增,例如給一個linux裝置新增一個IP的時候會自動新增一條有這個源IP約束的Link scope的路由。前面說了,也正是有了這條路由才能夠使得配置閘道器的路由條目可以進行。這個核心自動新增的路由就是proto kernel了。

需要理解的是,路由表和網路裝置是兩個實體,路由表在決策的時候,由路由表看到的網路裝置是獨立於路由表存在的。他們是並行的關係,先要查詢了路由表,找到滿足路由表的路由條目,才有可能按照條目約定的路由路徑去找到對應的裝置。路由過程並不是發生在裝置邏輯的內部,而是外部。所以實際上給裝置添加了一個IP地址的時候,同時生成的路由條目實際上是兩個操作被在上層進行了組合。技術上,完全可以分別的新增IP地址和路由條目,上面也說了這個新增的路由條目可以被刪除,然後再次添加回去