SO_DONTROUTE並沒有跳過路由表的尋找,而只是將尋找範圍縮小到了直連的同三層網段主機,SO_BINDTODEVICE亦沒有跳過路由表尋找,而只是將外出裝置固定,也就是增加了一個尋找鍵,因此二者都無法跳過尋找路由表的過程,本質上,SO_DONTROUTE也是增加了一個尋找鍵。路由表的尋找在linux實現的協議棧中是無法越過的,但是卻可以增加若干的限制條件,以hash路由表為例,在fn_hash_lookup函數中:
if (f->fn_scope < flp->fl4_scope) //檢查路由的範圍(**)
continue;
err = fib_semantic_match(f->fn_type, FIB_INFO(f), flp, res);
if (err == 0) {
//找到
}
在fib_semantic_match中:
if (!flp->oif || flp->oif == nh->nh_oif) //***
break; //找到
可見在核心的尋找函數中,DONTROUTE和BINDTODEVICE只是做了一些限制,並且在核心尋找函數的任意層次調用函數中都沒有繞過路由尋找的邏輯。
在正常的資料發送過程中,尋找路由的時候是沒有出口裝置資訊的,出口裝置由路由表的匹配結果來決定,而在通過setsockopt設定了SO_BINDTODEVICE之後,在尋找路由之前就有了出口裝置資訊,ip_queue_xmit中,尋找鍵fl中會添加.oif = sk->sk_bound_dev_if這個資訊(沒有bindtodevice的情況下,sk->sk_bound_dev_if為0),然後路由尋找按照往常的方式繼續,到達fib_semantic_match的時候,在***處起作用;對於SO_DONTROUTE這個選項,同樣的道理,在**處起作用,linux核心協議棧將“路由範圍”定義成了一個枚舉,一共N中類型:
enum rt_scope_t
{
RT_SCOPE_UNIVERSE=0, //任意的地址路由
RT_SCOPE_SITE=200, //使用者自訂
RT_SCOPE_LINK=253, //本地直連的路由
RT_SCOPE_HOST=254, //主機路由
RT_SCOPE_NOWHERE=255 //不存在的路由
};
數值逐漸增大,越大的越不易匹配,因此在**處,如果由於設定SO_DONTROUTE而配置了RT_SCOPE_LINK的話,如果目的主機在外部,路由表中的scope就是RT_SCOPE_UNIVERSE,這樣就不會匹配,因此也就找不到了,因此如果設定了SO_DONTROUTE的話,即使一個目的地址存在路由,只要它在外部,那就是不可達的。另外SO_BINDTODEVICE還有一個約束,那就是資料包從那裡走必然從哪裡回來,如果不是從出去的口回來的,那麼將無法送到使用者態處理,這由INET_MATCH來處理,進入__inet_lookup之後,目的是尋找一個和該資料包關聯的通訊端,對於每一個哈西衝突鏈中套結字都會調用INET_MATCH來進行匹配,INET_MATCH中有一句:
!((__sk)->sk_bound_dev_if) || ((__sk)->sk_bound_dev_if == (__dif)
同樣對於udp來說,udp_v4_lookup_longway中也會有相同的邏輯,這樣就保證了從哪裡走從哪裡回來。
根據SO_BINDTODEVICE的特點來說,用它來做負載平衡是可以的,但是前提是需要均衡的資料是本機發出的,而不是forward的,因為在ip_route_input_slow中設定路由表尋找鍵的時候出口裝置設定為0,在ip_route_input中尋找路由緩衝的時候rth->fl.oif ==
0說明出口裝置必須不能設定(),因此要想對過路資料做負載平衡,必須首先將其redirect到原生使用者態,然後再分別建立多個(取決於可以均衡的網卡數量)socket,每一個綁定一個出口網卡裝置,然後視負載情況在這些裝置關聯的socket將資料代理出去,不過這要求機器效能足夠好,負載平衡帶來的收益要遠大於redirect帶來的開銷損失。負載平衡永遠都是一個重要話題,特別是魚龍混雜的linux世界,linux主機有的效能超棒有的卻只是386的古董,加上linux核心的路由緩衝機制使之並不能實現基於包的負載平衡,在不修改協議棧的情況下,必須在使用者態想辦法,一種辦法就是redirect,另一種辦法就是使用虛擬網卡,即使用虛擬網卡的字元裝置介面將三層或者二層資料匯入到使用者態,然後再使用SO_BINDTODEVICE均衡到各個網卡,但要注意,此法到此為止只實現了一半,由於虛擬網卡的字元裝置出來的並不是使用者空間資料,因此再往socket發送相當於做了一個隧道封裝,什麼時候解鎖裝,這是一個問題,最好是資料通過需要均衡的路段後就解鎖裝,因此必然需要一個對稱的主機負責解鎖裝。
但是且慢,難道SO_DONTROUTE就沒有別的作用,就沒有別的說頭嗎?不是這樣的,也不會這麼簡單!這裡的關鍵在於“出口裝置”,只要能確定裝置,帶有SO_DONTROUTE的套結字就能成功將資料發送出去的,這是事實,在ip_route_output_slow中:
if (fib_lookup(&fl, &res)) {
//尋找失敗的可能:1.本身就沒有路由表項匹配;2.有路由表項匹配,但是不是本地的。
res.fi = NULL;
if (oldflp->oif) { //但是,只要有出口裝置,就能發送成功:1.設定SO_BINDTODEVICE綁定一個裝置;2.增加一條裝置路由ip route add ip/mask dev device
if (fl.fl4_src == 0)
fl.fl4_src = inet_select_addr(dev_out, 0, RT_SCOPE_LINK);
res.type = RTN_UNICAST;
goto make_route;
}
if (dev_out)
dev_put(dev_out);
err = -ENETUNREACH;
goto out;
}
}
因此,不但SO_DONTROUTE沒有跳過路由尋找,還多了尋找失敗後的動作,DONTROUTE不是不尋找路由表,而是對該包不進行路由,也就是該包是不會經過路由器的,設定了dontroute的通訊端發送的包是永遠不會發到網關的,但是本地路由尋找是避不開的哦!SO_DONTROUTE多用於僅能確定發送出口卻沒有路由的情況。不要太著急哦,why?還有arp呢?既然dontroute只關心是否有出口裝置,那麼如果有出口裝置的話,資料到了鏈路層之後呢?稍微知道網路過程的傢伙都知道arp,真實的資料通訊是需要鏈路層通道的建立的,因此,arp是必須的。不必擔心,arp完全按照以前的方式進行哦,如果一個主機確實是和發送主機是直連的,那麼很顯然,只要目的主機有路由,即使它們配置的ip不在一個網段,arp回複就會正確被收到的,so_dontroute也正是用於這一情況的,但是如果我們僅僅想做一下實驗,也就是目的ip是不同網段的一個不存在的ip的話,arp還會有回應嗎?如果沒有回應,資料如何通訊呢?很簡單,如果沒有回應,資料無法進行通訊,只有有回應才可以的,但是顯然,這個回應是不可能發回來的,然而考慮兩種情況,第一是網關啟動了arp代理,這樣雖然發送通訊端設定了SO_DONTROUTE的資料不被網關路由,但是由於網關回應了arp(實際上是一次欺騙),資料還是被發往網關了;第二種情況是網關沒有啟動arp代理,但是卻使用了ip-mac保護,它偵測到有一個奇怪的arp請求(內部請求一個不同網段的ip地址的mac),雖說這並不是非法的,路由器也可以由於好奇而回應自己的mac,從而將資料包引入自己這裡,如果說請求一個不同網段ip的mac並不是非法的,但是如果網關設定了ip和mac的綁定,它並沒有在這些綁定中查到目的ip的任何資訊,於是它就認為這個請求有問題,於是仍然可能回應自己的mac而引入這個資料包。所以除非你真的有一個直連的機器,否則資料不但發不到,一些arp的資訊都足以是你疑惑萬分。
SO_BINGTODEVICE僅僅為通訊端綁定了一個介面,而SO_DONTROUTE僅僅是不通過網關發送,不管你設定網關是什麼,它總是以資料的目的地址作為下一跳。