Linux實現的ARP快取老化時間原理解析

來源:互聯網
上載者:User
文章目錄
  • 1.使用keepalived進行熱備份的系統需要一個虛擬IP地址,然而該虛擬IP地址到底屬於哪台機器是根據熱備群的主備來決定的,因此主機器在獲得該虛擬IP的時候,必須要廣播一個免費的arp,起初人們認為這沒有必要,理由是不這麼做,熱備群也工作的很好,然而事實證明,這是必須的;
  • 2.ARP緩衝表項都有一個老化時間,然而在linux系統中卻沒有給出具體如何來設定這個老化時間。那麼到底怎麼設定這個老化時間呢?
一.問題

眾所周知,ARP是一個鏈路層的位址解析通訊協定,它以IP地址為索引值,查詢保有該IP地址主機的MAC地址。協議的詳情就不詳述了,你可以看RFC,也可以看教科書。這裡寫這麼一篇文章,主要是為了做一點記錄,同時也為同學們提供一點思路。具體呢,我遇到過兩個問題:
1.使用keepalived進行熱備份的系統需要一個虛擬IP地址,然而該虛擬IP地址到底屬於哪台機器是根據熱備群的主備來決定的,因此主機器在獲得該虛擬IP的時候,必須要廣播一個免費的arp,起初人們認為這沒有必要,理由是不這麼做,熱備群也工作的很好,然而事實證明,這是必須的;2.ARP緩衝表項都有一個老化時間,然而在linux系統中卻沒有給出具體如何來設定這個老化時間。那麼到底怎麼設定這個老化時間呢?二.解答問題前的說明

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修改,在第二十六章):

在中,我們看到只有arp快取項目的reachable狀態對於外發包是可用的,對於stale狀態的arp快取項目而言,它實際上是停用。如果此時有人要發包,那麼需要進行重新解析,對於常規的理解,重新解析意味著要重新發送arp請求,然後事實上卻不一定這樣,因為Linux為arp增加了一個“事件點”來“不用發送arp請求”而對arp協議產生的緩衝維護的最佳化措施,事實上,這種措施十分有效。這就是arp的“確認”機制,也就是說,如果說從一個鄰居主動發來一個資料包到本機,那麼就可以確認該包的“上一跳”這個鄰居是有效,然而為何只有到達原生包才能確認“上一跳”這個鄰居的有效性呢?因為Linux並不想為IP層的處理增加負擔,也即不想改變IP層的原始語義。
    Linux維護一個stale狀態其實就是為了保留一個neighbour結構體,在其狀態改變時只是個別欄位得到修改或者填充。如果按照簡單的實現,只儲存一個reachable狀態即可,其到期則刪除arp緩衝表項。Linux的做法只是做了很多的最佳化,但是如果你為這些最佳化而絞盡腦汁,那就悲劇了...
三.Linux如何來維護這個stale狀態

在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請求得到了回應。否則還是會被刪除。
四.Linux的ARP緩衝實現要點

在blog中分析源碼是兒時的記憶了,現在不再浪費版面了。只要知道Linux在實現arp時維護的幾個定時器的要點即可。
1.Reachable狀態定時器
每當有arp回應到達或者其它能證明該ARP表項表示的鄰居真的可達時,啟動該定時器。到期時根據配置的時間將對應的ARP緩衝表項轉換到下一個狀態。
2.記憶體回收定時器
定時啟動該定時器,具體下一次什麼到期,是根據配置的base_reachable_time來決定的,具體見下面的代碼:

static void neigh_periodic_timer(unsigned long arg){    ...    if (time_after(now, tbl->last_rand + 300 * HZ)) { //核心每5分鐘重新進行一次配置        struct neigh_parms *p;        tbl->last_rand = now;        for (p = &tbl->parms; p; p = p->next)            p->reachable_time =                neigh_rand_reach_time(p->base_reachable_time);    }    ...     /* Cycle through all hash buckets every base_reachable_time/2 ticks.      * ARP entry timeouts range from 1/2 base_reachable_time to 3/2      * base_reachable_time.     */    expire = tbl->parms.base_reachable_time >> 1;    expire /= (tbl->hash_mask + 1);    if (!expire)        expire = 1;    //下次何時到期完全基於base_reachable_time);     mod_timer(&tbl->gc_timer, now + expire);    ...}

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

if (atomic_read(&n->refcnt) == 1 && //n->used可能會因為“本地確認”機制而向前推進    (state == NUD_FAILED ||    time_after(now, n->used + n->parms->gc_staletime))) {    *np = n->next;    n->dead = 1;    write_unlock(&n->lock);    neigh_release(n);    continue;}

如果在實驗中,你的處於stale狀態的表項沒有被及時刪除,那麼試著執行一下下面的命令:

ip route flush cache

然後再看看ip neigh ls all的結果,注意,不要指望馬上會被刪除,因為此時記憶體回收定時器還沒有到期呢...但是我敢保證,不長的時間之後,該緩衝表項將被刪除。
五.第一個問題的解決

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

如果發生了切換,假設路由器上的arp緩衝逾時時間為1小時,那麼在將近一小時內,單向資料將無法通訊(假設群組中的主機不會發送資料通過路由器,排出“本地確認”,畢竟我不知道路由器是不是在運行Linux),路由器上的資料將持續不斷的法往原來的master,然而原始的matser已經不再持有虛擬IP地址。
    因此,為了使得資料行為不再依賴路由器的配置,必須在vrrp協議下切換到master時手動綁定虛擬IP地址和自己的MAC地址,在Linux上使用方便的arping則是:

arping -i ethX -S 1.1.1.1 -B -c 1

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

if (arp->ar_op != htons(ARPOP_REPLY) || skb->pkt_type != PACKET_HOST)    state = NUD_STALE;neigh_update(n, sha, state, override ? NEIGH_UPDATE_F_OVERRIDE : 0);

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

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

vrrp_send_update(vrrp_rt * vrrp, ip_address * ipaddress, int idx){char *msg;char addr_str[41];if (!IP_IS6(ipaddress)) {msg = "gratuitous ARPs";inet_ntop(AF_INET, &ipaddress->u.sin.sin_addr, addr_str, 41);send_gratuitous_arp(ipaddress);} else {msg = "Unsolicited Neighbour Adverts";inet_ntop(AF_INET6, &ipaddress->u.sin6_addr, addr_str, 41);ndisc_send_unsolicited_na(ipaddress);}if (0 == idx && debug & 32) {log_message(LOG_INFO, "VRRP_Instance(%s) Sending %s on %s for %s",    vrrp->iname, msg, IF_NAME(ipaddress->ifp), addr_str);}}

六.第二個問題的解決

扯了這麼多,在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中:

if (time_after(now, tbl->last_rand + 300 * HZ)) {    struct neigh_parms *p;    tbl->last_rand = now;    for (p = &tbl->parms; p; p = p->next)        //隨計化很重要,防止“共振行為”引發的ARP解析風暴        p->reachable_time = neigh_rand_reach_time(p->base_reachable_time);}...expire = tbl->parms.base_reachable_time >> 1;expire /= (tbl->hash_mask + 1);if (!expire)    expire = 1;mod_timer(&tbl->gc_timer, now + expire);

可見一斑啊!適當地,我們可以通過看代碼注釋來理解這一點,好心人都會寫上注釋的。為了實驗的條理清晰,我們設計以下兩個情境:
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來玩小技巧。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.