linux socket 綁定機制的研究
1. 概述:
在多 link 環境中,如何保證資料在正確的 link 上傳輸是一個棘手的問題。
本文主要分析 linux socket 綁定機制的實現,從而協助開發人員更好的瞭解 socket 綁定的本質。
2. linux kernel 提供給 user space 的介面
linux 提供一個新的 setsockopt() 選項 SO_BINDTODEVICE 作為 user space 的介面。
源碼:net/core/sock.c:
在編譯核心的時候,必須開啟 CONFIG_NETDEVICES 選項,否則 SO_BINDTODEVICE 不起作用;
在 struct sock 結構中,專門有一個域 bound_dev_if;在調用 setsockopt() 後,bound_dev_if 被設定為所綁定的 interface 的 index 。bound_dev_if 將會影響後續的資料包的發送和接收。
sk->bound_dev_if = dev->ifindex;
socket 綁定之所以能保證資料在所綁定的 interface 上傳輸,是因為 linux 在路由選擇的時候,需要考慮 socket 所綁定的interface(也就是 sock->bound_dev_if)。下面從發送和接收資料兩個方面來分析這種影響:
3. socket 綁定對發送資料包的影響
源碼:include/net/route.h
net/ipv4/route.c
影響1:
任何傳輸層協議,最終都要走到 ip_route_output() 進行路由選擇,linux 是根據 socket 的源地址、目的地址、TOS值以及所綁定的 interface(如果有)作為關鍵字進行路由尋找的。因此,在 socket 綁定情況下,如果一個路由項的源地址、目的地址、TOS 都和 socket 匹配,但是 out interface 和 socket 所綁定的 interface 不一致,那麼該路由項也不會被選中。
這也正是綁定的意義所在:必須保證資料包被路由到所綁定的 interface 上。
影響2:
socket 綁定對發送資料的路由選擇的另一個重要影響是:即使在 route cache 和 route table 中都查不到路由,該資料包仍然會被送到所綁定的 interface 上。具體代碼在ip_route_output_slow() 中。
4. socket 綁定對接收資料包的影響
源碼:net/ipv4/ip_input.c
Net/ipv4/tcp_ipv4.c
Net/ipv4/udp.c
接收資料包需要在 ip_route_input() 中進行路由選擇,socket 綁定對此階段沒有影響。
路由以後,到本地的包走到ip_local_deliver(),ip_local_deliver_finish(),此後,根據傳輸層協議的不同,分別走到tcp_v4_rcv() 或 udp_rcv()。
這兩個函數都首先需要找到該資料包所對應的 struct sock 結構:
tcp_v4_rcv() 在尋找sock 時,會嚴格檢查 sock 的bound_dev_if 與資料包進來的 interface 是否一致,如果兩者不一致,則該 sock 不會被匹配。
udp_rcv() 在尋找 sock 時,也檢查這兩個值是否匹配(具體代碼在udp_v4_lookup_longway()中),但是就算不匹配,也可能接收這個包,原因是 UDP 相對於 TCP 來說,在一致性檢查上沒有那麼嚴格。它的策略是:在源地址、目的地址、目的連接埠、綁定的 interface 四項檢查中,尋找匹配程度最高的那個 sock。
5. socket 綁定使用時候的注意事項
不要將一個 socket 綁定到多個不同的 interface 上,這樣會導致不可預料的後果。因為當你第一次綁定到一個interface 上發送資料後,在 route cache 中將會儲存一個路由項,如果此時將該 socket 綁定到另一個 interface,那麼從前一個 interface 上回來的資料包在路由的時候,就可能查不到對應的 socket ,從而被丟棄。