天天看點

Linux實作的ARP緩存老化時間原了解析一.問題二.解答問題前的說明三.Linux如何來維護這個stale狀态四.Linux的ARP緩存實作要點五.第一個問題的解決六.第二個問題的解決七.總結

衆所周知,ARP是一個鍊路層的位址解析協定,它以IP位址為鍵值,查詢保有該IP位址主機的MAC位址。協定的詳情就不詳述了,你可以看RFC,也可以看教科書。這裡寫這麼一篇文章,主要是為了做一點記錄,同時也為同學們提供一點思路。具體呢,我遇到過兩個問題:

ARP協定的規範隻是闡述了位址解析的細節,然而并沒有規定協定棧的實作如何去維護ARP緩存。ARP緩存需要有一個到期時間,這是必要的,因為ARP緩存并不維護映射的狀态,也不進行認證,是以協定本身不能保證這種映射永遠都是正确的,它隻能保證該映射在得到arp應答之後的一定時間内是有效的。這也給了ARP欺騙以可乘之機,不過本文不讨論這種欺騙。

    像Cisco或者基于VRP的華為裝置都有明确的配置來配置arp緩存的到期時間,然而Linux系統中卻沒有這樣的配置,起碼可以說沒有這樣的直接配置。Linux使用者都知道如果需要配置什麼系統行為,那麼使用sysctl工具配置procfs下的sys接口是一個方法,然而當我們google了好久,終于發現關于ARP的配置處在/proc/sys/net/ipv4/neigh/ethX的時候,我們最終又迷茫于該目錄下的N多檔案,即使去查詢Linux核心的Documents也不能清晰的明了這些檔案的具體含義。對于Linux這樣的成熟系統,一定有辦法來配置ARP緩存的到期時間,但是具體到操作上,到底怎麼配置呢?這還得從Linux實作的ARP狀态機說起。

    如果你看過《Understading Linux Networking Internals》并且真的做到深入了解的話,那麼本文講的基本就是廢話,但是很多人是沒有看過那本書的,是以本文的内容還是有一定價值的。

    Linux協定棧實作為ARP緩存維護了一個狀态機,在了解具體的行為之前,先看一下下面的圖(該圖基于《Understading Linux Networking Internals》裡面的圖26-13修改,在第二十六章):

Linux實作的ARP緩存老化時間原了解析一.問題二.解答問題前的說明三.Linux如何來維護這個stale狀态四.Linux的ARP緩存實作要點五.第一個問題的解決六.第二個問題的解決七.總結

在上圖中,我們看到隻有arp緩存項的reachable狀态對于外發包是可用的,對于stale狀态的arp緩存項而言,它實際上是不可用的。如果此時有人要發包,那麼需要進行重新解析,對于正常的了解,重新解析意味着要重新發送arp請求,然後事實上卻不一定這樣,因為Linux為arp增加了一個“事件點”來“不用發送arp請求”而對arp協定生成的緩存維護的優化措施,事實上,這種措施十分有效。這就是arp的“确認”機制,也就是說,如果說從一個鄰居主動發來一個資料包到本機,那麼就可以确認該包的“上一跳”這個鄰居是有效的,然而為何隻有到達本機的包才能确認“上一跳”這個鄰居的有效性呢?因為Linux并不想為IP層的處理增加負擔,也即不想改變IP層的原始語義。

    Linux維護一個stale狀态其實就是為了保留一個neighbour結構體,在其狀态改變時隻是個别字段得到修改或者填充。如果按照簡單的實作,隻儲存一個reachable狀态即可,其到期則删除arp緩存表項。Linux的做法隻是做了很多的優化,但是如果你為這些優化而絞盡腦汁,那就悲劇了...

在Linux實作的ARP狀态機中,最複雜的就是stale狀态了,在此狀态中的arp緩存表項面臨着生死抉擇,抉擇者就是本地發出的包,如果本地發出的包使用了這個stale狀态的arp緩存表項,那麼就将狀态機推進到delay狀态,如果在“垃圾收集”定時器到期後還沒有人使用該鄰居,那麼就有可能删除這個表項了,到底删除嗎?這樣看看有木有其它路徑使用它,關鍵是看路由緩存,路由緩存雖然是一個第三層的概念,然而卻保留了該路由的下一條的ARP快取記錄項,這個意義上,Linux的路由緩存實則一個轉發表而不是一個路由表。

    如果有外發包使用了這個表項,那麼該表項的ARP狀态機将進入delay狀态,在delay狀态中,隻要有“本地”确認的到來(本地接收包的上一跳來自該鄰居),linux還是不會發送ARP請求的,但是如果一直都沒有本地确認,那麼Linux就将發送真正的ARP請求了,進入probe狀态。是以可以看到,從stale狀态開始,所有的狀态隻是為一種優化措施而存在的,stale狀态的ARP快取記錄項就是一個緩存的緩存,如果Linux隻是将過期的reachable狀态的arp緩存表項删除,語義是一樣的,但是實作看起來以及了解起來會簡單得多!

    再次強調,reachable過期進入stale狀态而不是直接删除,是為了保留neighbour結構體,優化記憶體以及CPU利用,實際上進入stale狀态的arp緩存表項時不可用的,要想使其可用,要麼在delay狀态定時器到期前本地給予了确認,比如tcp收到了一個包,要麼delay狀态到期進入probe狀态後arp請求得到了回應。否則還是會被删除。

在blog中分析源碼是兒時的記憶了,現在不再浪費版面了。隻要知道Linux在實作arp時維護的幾個定時器的要點即可。

1.Reachable狀态定時器

每當有arp回應到達或者其它能證明該ARP表項表示的鄰居真的可達時,啟動該定時器。到期時根據配置的時間将對應的ARP快取記錄項轉換到下一個狀态。

2.垃圾回收定時器

定時啟動該定時器,具體下一次什麼到期,是根據配置的base_reachable_time來決定的,具體見下面的代碼:

一旦這個定時器到期,将執行neigh_periodic_timer回調函數,裡面有以下的邏輯,也即上面的...省略的部分:

如果在實驗中,你的處于stale狀态的表項沒有被及時删除,那麼試着執行一下下面的指令:

然後再看看ip neigh ls all的結果,注意,不要指望馬上會被删除,因為此時垃圾回收定時器還沒有到期呢...但是我敢保證,不長的時間之後,該緩存表項将被删除。

在啟用keepalived進行基于vrrp熱備份的群組上,很多同學認為根本不需要在進入master狀态時重新綁定自己的MAC位址和虛拟IP位址,然而這是根本錯誤的,如果說沒有出現什麼問題,那也是僥幸,因為各個路由器上預設配置的arp逾時時間一般很短,然而我們不能依賴這種配置。請看下面的圖示:

Linux實作的ARP緩存老化時間原了解析一.問題二.解答問題前的說明三.Linux如何來維護這個stale狀态四.Linux的ARP緩存實作要點五.第一個問題的解決六.第二個問題的解決七.總結

如果發生了切換,假設路由器上的arp緩存逾時時間為1小時,那麼在将近一小時内,單向資料将無法通信(假設群組中的主機不會發送資料通過路由器,排出“本地确認”,畢竟我不知道路由器是不是在運作Linux),路由器上的資料将持續不斷的法往原來的master,然而原始的matser已經不再持有虛拟IP位址。

    是以,為了使得資料行為不再依賴路由器的配置,必須在vrrp協定下切換到master時手動綁定虛拟IP位址和自己的MAC位址,在Linux上使用友善的arping則是:

這樣一來,獲得1.1.1.1這個IP位址的master主機将IP位址為255.255.255.255的ARP請求廣播到全網,假設路由器運作Linux,則路由器接收到該ARP請求後将根據來源IP位址更新其本地的ARP快取記錄項(如果有的話),然而問題是,該表項更新的結果狀态卻是stale,這隻是ARP的規定,具體在代碼中展現是這樣的,在arp_process函數的最後:

由此可見,隻有實際的外發包的下一跳是1.1.1.1時,才會通過“本地确認”機制或者實際發送ARP請求的方式将對應的MAC位址映射reachable狀态。

更正:在看了keepalived的源碼之後,發現這個擔心是多餘的,畢竟keepalived已經很成熟了,不應該犯“如此低級的錯誤”,keepalived在某主機切換到master之後,會主動發送免費arp,在keepalived中有代碼如是:

扯了這麼多,在Linux上到底怎麼設定ARP緩存的老化時間呢?

我們看到/proc/sys/net/ipv4/neigh/ethX目錄下面有多個檔案,到底哪個是ARP緩存的老化時間呢?實際上,直接點說,就是base_reachable_time這個檔案。其它的都隻是優化行為的措施。比如gc_stale_time這個檔案記錄的是“ARP快取記錄項的緩存”的存活時間,該時間隻是一個緩存的緩存的存活時間,在該時間内,如果需要用到該鄰居,那麼直接使用表項記錄的資料作為ARP請求的内容即可,或者得到“本地确認”後直接将其置為reachable狀态,而不用再通過路由查找,ARP查找,ARP鄰居建立,ARP鄰居解析這種慢速的方式。

    預設情況下,reachable狀态的逾時時間是30秒,超過30秒,ARP快取記錄項将改為stale狀态,此時,你可以認為該表項已經老化到期了,隻是Linux的實作中并沒有将其删除罷了,再過了gc_stale_time時間,表項才被删除。在ARP快取記錄項成為非reachable之後,垃圾回收器負責執行“再過了gc_stale_time時間,表項才被删除”這件事,這個定時器的下次到期時間是根據base_reachable_time計算出來的,具體就是在neigh_periodic_timer中:

可見一斑啊!适當地,我們可以通過看代碼注釋來了解這一點,好心人都會寫上注釋的。為了實驗的條理清晰,我們設計以下兩個場景:

1.使用iptables禁止一切本地接收,進而屏蔽arp本地确認,使用sysctl将base_reachable_time設定為5秒,将gc_stale_time為5秒。

2.關閉iptables的禁止政策,使用TCP下載下傳外部網絡一個超大檔案或者進行持續短連接配接,使用sysctl将base_reachable_time設定為5秒,将gc_stale_time為5秒。

在兩個場景下都使用ping指令來ping本地區域網路的預設網關,然後迅速Ctrl-C掉這個ping,用ip neigh show all可以看到預設網關的arp表項,然而在場景1下,大約5秒之内,arp表項将變為stale之後不再改變,再ping的話,表項先變為delay再變為probe,然後為reachable,5秒之内再次成為stale,而在場景2下,arp表項持續為reachable以及dealy,這說明了Linux中的ARP狀态機。那麼為何場景1中,當表項成為stale之後很久都不會被删除呢?其實這是因為還有路由緩存項在使用它,此時你删除路由緩存之後,arp表項很快被删除。

1.在Linux上如果你想設定你的ARP緩存老化時間,那麼執行sysctl -w net.ipv4.neigh.ethX=Y即可,如果設定别的,隻是影響了性能,在Linux中,ARP緩存老化以其變為stale狀态為準,而不是以其表項被删除為準,stale狀态隻是對緩存又進行了緩存;

2.永遠記住,在将一個IP位址更換到另一台本網段裝置時,盡可能快地廣播免費ARP,在Linux上可以使用arping來玩小技巧。

 本文轉自 dog250 51CTO部落格,原文連結:http://blog.51cto.com/dog250/1269009