Linux鄰居協議 學習筆記 之五 通用鄰居項的狀態機機制
鄰居項的狀態機機制是通用鄰居層最重要的內容,主要是處理鄰居項中狀態的改變,其中包括幾個鄰居狀態的定時器機制,以及鄰居項的更新,solicit請求的傳送等
對於通用鄰居項的狀態機,主要有如下幾個狀態:
NUD_INCOMPLETE、NUD_REACHABLE、NUD_DELAY、NUD_PROBE、NUD_STALE、NUD_NOARP、NUD_PERMANENT、NUD_PROBE、NUD_FAILED
其中,處於如下狀態的鄰居項,都會啟動一個定時器:
#defineNUD_IN_TIMER (NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)
而處於如下狀態的鄰居項,我們認為鄰居項是可達的:
#defineNUD_CONNECTED (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)
對於處於NUD_PERMANENT、NUD_NOARP狀態的鄰居項,是不會進行鄰居項的狀態的轉換的,其中NUD_PERMANENT說明鄰居項是通過netlink機制新增的鄰居項,不會改變;而處於NUD_NOARP狀態的鄰居項,一般是地址為組播的鄰居項,其二層地址是可以根據三層地址計算出來的,不需要進行鄰居項的學習,也不會進行狀態的改變。
從上面2個巨集定義我們知道,處於NUD_REACHABLE狀態的鄰居項, 肯定會進行狀態的狀態的轉變。因為處於該狀態的鄰居項,要啟動一個定時器,那如果定時器超時後,鄰居項一直沒有被使用,則鄰居項的狀態就會轉變。
我們知道,鄰居項的建立原因有兩個:
1、 有資料要傳送出去,我們還不知道目的三層地址對應的二層地址或者下一跳閘道器對
應的二層地址。
(在有資料傳送時,會先去查詢路由表,若查詢到路由,且該路由沒有在路由快取中,則會建立路由快取,並在建立路由快取時,會建立一個鄰居項與該路由快取繫結)
2、 介面收到一個solicit的請求報文,且沒有在鄰居表的鄰居項hash 陣列中查詢到
符合條件的鄰居項,則建立一個鄰居項。
3、應用層通過netlink訊息建立一個三層地址對應的二層地址的鄰居項
對於上面3種建立鄰居項,其初始狀態以及狀態的變化會有所不同。
a) 對於因為有資料要傳送而建立的鄰居項,會將其鄰居項狀態設定為NUD_NONE。
然後鄰居項的狀態會設定為NUD_INCOMPLETE狀態,處於該狀態的鄰居項會主動傳送solicti請求,如果再定時器到期前收到應答,則會將鄰居項的狀態設定NUD_REACHABLE,否則,在定時器到期且超過最大發包次數的請求下,則會將鄰居項的狀態設定為NUD_FAILED,處於該狀態的鄰居項,其佔用的快取將會被釋放掉。
b) 對於接收到一個solicit的請求報文而建立的鄰居項,因為既然有遠方的solicit請求,則會有資料傳送過來,此時建立的鄰居項,就沒有將鄰居項的狀態設定為NUD_INCOMPLETE而傳送solicit請求,但也不能直接就將狀態設定為NUD_CONNECT,此時是將鄰居項的狀態設定為NUD_STALE,這樣如果遠方有資料傳送過來,而且需要通過該鄰居項傳送資料到遠方,就會將狀態設定為NUD_DELAY,如果再收到該遠方發來的四層資料的確認等,就間接實現了鄰居項的確認,從而將狀態設定為NUD_CONNET
c) 對於通過netlink訊息建立的靜態鄰居項,我們會將鄰居項的狀態設定為NUD_PERMANENT,且不會再改變該鄰居項的狀態。
疑問:因為建立的鄰居項的狀態為NUD_NONE,而NUD_NONE也不處於定時器狀態,那麼處於NUD_NONE狀態的鄰居項,是如何將鄰居項的狀態轉變為NUD_INCOMPLETE的呢?對於處於NUD_STALE狀態的鄰居項,又是如何實現鄰居項狀態的轉變的呢?
在上面的a)中,我只是說鄰居項從NUD_NONE轉變為NUD_INCOMPLETE,卻沒有說明這個轉換是如何進行的。其轉變過程大致如下(此處以ipv4為例):當有資料包要傳送時,首先是查詢路由表,確定目的地址可達。在這個查詢的過程中,若還沒有與該目的地址對應的鄰居項,則會建立一個鄰居項,並與查詢到的路由快取相關聯,此時鄰居項的狀態還是NUD_NONE。對於ipv4來說,接著就會執行ip_output,然後就會呼叫到ip_finish_output2,接著就會呼叫到neighbour->output,而在neighbour->output裡就會呼叫到__neigh_event_send判斷資料包是否可以直接傳送出去,如果此時鄰居項的狀態為NUD_NONE,則會將鄰居項的狀態設定為NUD_INCOMPLETE,並將要傳送的資料包快取到鄰居項的佇列中。而處於NUD_INCOMPLETE狀態的鄰居項的狀態轉變會有定時器處理函式來實現。
對於處於NUD_STALE狀態的鄰居項,有兩個條件實現狀態的轉變:
1) 在閒置時間沒有超過最大值之前,有資料要通過該鄰居項進行傳送,則會將鄰居項的狀態設定為NUD_DELAY,接著狀態的轉變就有定時器超時函式來接管了。
2) 在超過最大閒置時間後,沒有資料通過該鄰居項進行傳送,則會將鄰居項的狀態設定為NUD_FAILED,並會被垃圾回收機制進行快取回收。
1、對於NUD_INCOMPLETE,當本機發送完arp 請求包後,還未收到應答時,即會進入該狀態。 進入該狀態,即會啟動定時器,如果在定時器到期後,還沒有收到應答時:如果沒有到達最大發包上限時,即會重新進行傳送請求報文;如果超過最大發包上限還沒有收到應答,則會將狀態設定為failed
2、對於收到可到達性確認後,即會進入NUD_REACHABLE,當進入NUD_REACHABLE狀態。當進入NUD_REACHABLE後,即會啟動一個定時器,當定時器到時前,該鄰居協議沒有被使用過,就會將鄰居項的狀態轉換為NUD_STALE
3、對於進入NUD_STALE狀態的鄰居項,即會啟動一個定時器。如果在定時器到時前,有資料需要傳送,則直接將資料包傳送出去,並將狀態設定為NUD_DELAY;如果在定時器到時,沒有資料需要傳送,且該鄰居項的引用計數為1,則會通過垃圾回收機制,釋放該鄰居項對應的快取
4、處於NUD_DELAY狀態的鄰居項,如果在定時器到時後,沒有收到可到達性確認,則會進入NUD_PROBE狀態;如果在定時器到達之前,收到可到達性確認,則會進入NUD_REACHABLE (在該狀態下的鄰居項不會發送solicit請求,而只是等待可到達性應答。主要包括對以前的solicit請求的應答或者收到一個對於本裝置以前傳送的一個數據包的應答)
5、處於NUD_PROBE狀態的鄰居項,會發送arp solicit請求,並啟動一個定時器。如果在定時器到時前,收到可到達性確認,則進入NUD_REACHABLE;如果在定時器到時後,沒有收到可到達性確認:
a)沒有超過最大發包次數時,則繼續傳送solicit請求,並啟動定時器
b)如果超過最大發包次數,則將鄰居項狀態設定為failed
下圖是鄰居項的狀態轉換邏輯圖,通過上面的描述和下面的邏輯圖,能夠很好的理解鄰居項的狀態機機制。
鄰居項的建立:
我們知道鄰居項的建立有三種方式,下面分析第一種方式的處理流程,即有資料要傳送出去,我們還不知道目的三層地址對應的二層地址或者下一跳閘道器對應的二層地址,對鄰居項的建立(我們以ipv4為例)。通過上面的分析,我們知道,當查詢到路由並建立路由快取時,則會呼叫arp_bind_neighbour進行路由快取與鄰居項的繫結,該函式主要實現了鄰居項的查詢與建立,下面是該函式的分析:
該函式實現arp協議中neighbour項與路由快取中的dst_entry表項的繫結
通過下一跳閘道器地址和net_dev為關鍵字查詢一個neighbour項
1、若查詢到,則將dst->neighbour指向該neighbour項
2、若沒有查詢到,則呼叫neigh_create建立一個鄰居表項並加入到arp_table的鄰居表項鍊表中,並將dst->neighbour指向該neighbour項
int arp_bind_neighbour(struct dst_entry *dst)
{
struct net_device *dev = dst->dev;
struct neighbour *n = dst->neighbour;
if (dev == NULL)
return -EINVAL;
if (n == NULL) {
__be32 nexthop = ((struct rtable *)dst)->rt_gateway;
if (dev->flags&(IFF_LOOPBACK|IFF_POINTOPOINT))
nexthop = 0;
n = __neigh_lookup_errno(
#if defined(CONFIG_ATM_CLIP) || defined(CONFIG_ATM_CLIP_MODULE)
dev->type == ARPHRD_ATM ? clip_tbl_hook :
#endif
&arp_tbl, &nexthop, dev);
if (IS_ERR(n))
return PTR_ERR(n);
dst->neighbour = n;
}
return 0;
}
執行完上面鄰居項的建立以後,後面會間接呼叫函式neigh_event_send,實現鄰居項狀態從NUD_NONE到NUD_INCOMPLETTE狀態的改變。
我們接著分析第二種建立鄰居項的執行流程,仍以ipv4為例:
在arp的接收處理函式arp_rcv裡,在對arp包的頭部資訊進行檢查以及防火牆規則檢查以後,對於允許接收的arp包,則會呼叫arp_process進行後續的處理,而在arp_process中,對於接收到的arp請求報文後,則會呼叫neigh_event_ns進行鄰居項的查詢與建立功能,對於新建立的鄰居項,則會呼叫函式neigh_update更新鄰居項的狀態。
函式neigh_event_ns的邏輯流程還是比較簡單的,主要是將鄰居項的狀態設定為NUD_STALE
struct neighbour *neigh_event_ns(struct neigh_table *tbl,
u8 *lladdr, void *saddr,
struct net_device *dev)
{
struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev,
lladdr || !dev->addr_len);
if (neigh)
neigh_update(neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_OVERRIDE);
return neigh;
}
對於第三種建立鄰居項的執行流程,是通過netlink訊息機制實現的,最終會呼叫函式neigh_add實現鄰居項的建立。對於新建立的鄰居項,則會呼叫函式neigh_update或者neigh_event_send實現鄰居項狀態的更新。
通過對於以上三種情況下鄰居項的建立流程,我們發現會呼叫函式neigh_update、neigh_event_send進行鄰居項狀態的更新,其實對於處於定時器狀態的鄰居項,會通過定時器超時處理函式實現鄰居項狀態的轉變,下面我們分析一下這3個函式的處理流程。
鄰居項狀態的更新函式1:
下面我們分析一下函式neigh_update:
該函式的功能:鄰居項的更新,主要是更新二層地址與鄰居項的狀態,並會
根據鄰居項的狀態,選擇相對應的輸出函式
1、判斷輸入二層地址,判斷是否需要覆蓋鄰居項的二層地址
2、判斷鄰居項狀態的改變是否合法
3、根據不同的鄰居項狀態設定不同的鄰居項輸出函式,並設定與該
鄰居項關聯的所有二層快取頭部
該函式被呼叫的情形有:
1、當接收到鄰居項的應答報文後,則會呼叫該函式更新二層地址和狀態為CONNECT
2、當接收到鄰居項的請求報文後,則會呼叫該函式將鄰居項的狀態設定為STALE
3、處理通過ioctl或者netlink執行的鄰居項的新增、刪除鄰居項時,也會呼叫該函式
更新鄰居項的狀態與二層地址
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
u32 flags)
{
u8 old;
int err;
int notify = 0;
struct net_device *dev;
int update_isrouter = 0;
write_lock_bh(&neigh->lock);
dev = neigh->dev;
old = neigh->nud_state;
err = -EPERM;
/*
對於鄰居項的原狀態為NOARP或者PERMANENT,且不是admin傳送的請求,則直接返回
*/
if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
(old & (NUD_NOARP | NUD_PERMANENT)))
goto out;
/*
當鄰居項狀態不是有效狀態時(即是NUD_INCOMPLET、NUD_NONE、NUD_FAILED):
1、刪除該鄰居項的定時器
2、對於原狀態是CONNECT而新狀態不是有效態時,則呼叫neigh_suspect將鄰居項的
輸出函式設定為通用輸出函式
3、如果原狀態是NUD_INCOMPLETE或者NUD_PROBE,且新狀態為NUD_FAILED時,則呼叫
neigh_invalidate傳送錯誤報告,併發送通知資訊,函式返回
*/
if (!(new & NUD_VALID)) {
neigh_del_timer(neigh);
if (old & NUD_CONNECTED)
neigh_suspect(neigh);
neigh->nud_state = new;
err = 0;
notify = old & NUD_VALID;
if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
(new & NUD_FAILED)) {
neigh_invalidate(neigh);
notify = 1;
}
goto out;
}
/*
1、對於裝置二層地址長度為0的情形,則不需要更新二層地址,直接
使用neigh->ha
2、原狀態為有效的,且要更改的地址與鄰居項儲存的地址相同,則
無需更改
3、原狀態為無效,且要更改的地址也是無效,則是邏輯錯誤,函式直接
返回
4、原狀態有效,且要更改的地址無效時,則先將地址設定為鄰居項的地址. ???
5、其他情況下不更改傳進來的二層地址。
即:
原狀態有效,且修改的地址與原鄰居項地址不同
原狀態無效,且修改的地址有效時
*/
/* Compare new lladdr with cached one */
if (!dev->addr_len) {
/* First case: device needs no address. */
lladdr = neigh->ha;
} else if (lladdr) {
/* The second case: if something is already cached
and a new address is proposed:
- compare new & old
- if they are different, check override flag
*/
if ((old & NUD_VALID) &&
!memcmp(lladdr, neigh->ha, dev->addr_len))
lladdr = neigh->ha;
} else {
/* No address is supplied; if we know something,
use it, otherwise discard the request.
*/
err = -EINVAL;
if (!(old & NUD_VALID))
goto out;
lladdr = neigh->ha;
}
/*
1、鄰居項的新狀態是CONNECT時,更新connect時間
2、更新update時間
*/
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;
neigh->updated = jiffies;
/* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
err = 0;
/*
1、原狀態有效,且不允許覆蓋原來值時,且二層地址不同,且原狀態為
CONNECT時,則不更新鄰居項的二層地址,而只是將狀態設定為STALE
(這是在二層地址不同時,不修改二層地址的最後一個條件)
2、原狀態有效,且二層地址不變,且新狀態為STALE時,則不改鄰居項的
狀態
*/
update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
if (old & NUD_VALID) {
if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
update_isrouter = 0;
if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
(old & NUD_CONNECTED)) {
lladdr = neigh->ha;
new = NUD_STALE;
} else
goto out;
} else {
if (lladdr == neigh->ha && new == NUD_STALE &&
((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) ||
(old & NUD_CONNECTED))
)
new = old;
}
}
/*
當鄰居項的新舊狀態不同時,則會刪除定時器。若新狀態是
NUD_TIMER,則重新新增定時器。
(其中對於定時器的超時時間,如果新狀態為NUD_REACHABLE,則將
超時時間設定為reachable_time,否則將定時器的超時時間設定為當前時間)
設定鄰居項的狀態為新狀態
*/
if (new != old) {
neigh_del_timer(neigh);
if (new & NUD_IN_TIMER)
neigh_add_timer(neigh, (jiffies +
((new & NUD_REACHABLE) ?
neigh->parms->reachable_time :
0)));
neigh->nud_state = new;
}
/*
如果鄰居項的二層地址不同,則更新鄰居項裡的二層地址,並
呼叫neigh_update_hhs,更新與該鄰居項相關聯的所有二層頭部快取。
如果新狀態不是CONNECT狀態,則將confirm時間設定為比當前時間早
2*base_reachable_time.
根據鄰居項的不同更新鄰居項的輸出函式:
當為NUD_CONNECTED,則呼叫neigh_connect將鄰居項的輸出函式設定為快速輸出函式
當為非NUD_CONNECTED,則呼叫neigh_suspect將鄰居項的輸出函式設定為通用輸出函式
*/
if (lladdr != neigh->ha) {
memcpy(&neigh->ha, lladdr, dev->addr_len);
neigh_update_hhs(neigh);
if (!(new & NUD_CONNECTED))
neigh->confirmed = jiffies -
(neigh->parms->base_reachable_time << 1);
notify = 1;
}
if (new == old)
goto out;
if (new & NUD_CONNECTED)
neigh_connect(neigh);
else
neigh_suspect(neigh);
if (!(old & NUD_VALID)) {
struct sk_buff *skb;
/* Again: avoid dead loop if something went wrong */
while (neigh->nud_state & NUD_VALID &&
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
struct neighbour *n1 = neigh;
write_unlock_bh(&neigh->lock);
/* On shaper/eql skb->dst->neighbour != neigh :( */
if (skb_dst(skb) && skb_dst(skb)->neighbour)
n1 = skb_dst(skb)->neighbour;
n1->output(skb);
write_lock_bh(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
}
out:
if (update_isrouter) {
neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
(neigh->flags | NTF_ROUTER) :
(neigh->flags & ~NTF_ROUTER);
}
write_unlock_bh(&neigh->lock);
if (notify)
neigh_update_notify(neigh);
return err;
}
鄰居項狀態的更新函式2:
使用定時器實現鄰居項狀態轉變的處理:
通過上面的分析,我們知道對於這幾個鄰居項狀態的轉變,最主要的就是需要定時器的那幾個鄰居項的狀態,所以我們下面分析鄰居項的定時器的處理函式,對於這幾個鄰居項的狀態,核心使用的是同一個定時器,只不過設定的超時時間不同罷了。
該函式的功能:鄰居項最主要的定時器超時處理函式,
實現了諸多鄰居項狀態的轉換以及鄰居項solicit請求相關的函式
1、如果鄰居項的當前狀態不屬於NUD_IN_TIMER,則函式返回。
*/
static void neigh_timer_handler(unsigned long arg)
{
unsigned long now, next;
struct neighbour *neigh = (struct neighbour *)arg;
unsigned state;
int notify = 0;
write_lock(&neigh->lock);
state = neigh->nud_state;
now = jiffies;
next = now + HZ;
if (!(state & NUD_IN_TIMER)) {
#ifndef CONFIG_SMP
printk(KERN_WARNING "neigh: timer & !nud_in_timer\n");
#endif
goto out;
}
/*
對於處於reach狀態的鄰居項:
1、如果當前時間距確認時間confirmed,還未到超時時限reachable_time,則將定時器時間設定為鄰居項的超時
時限reachable_time
2、當前時間已晚於確認時間加上超時時限,當未超過鄰居項使用時間
加上delay_probe_time,則將狀態設定為DELAY。
這個狀態的改變條件,我感覺設定的很巧妙。
一般是進入stale狀態的鄰居項,在超時前有資料時,則進入Delay狀態。
為什麼可以直接從REACH狀態進入Delay狀態呢?
3、當前時間晚於used+delay_probe_time,說明在confirmed+reachable_time超時前的短暫時間點
內沒有資料傳送,此時即將狀態設定為STALE,
對於Delay狀態的鄰居項:
1、當前時間小於connect_time+delay_time時,說明鄰居項可能在定時器超時函式剛執行時
即已經更新了connect_time時間,此時即可以在鄰居項的狀態設定為reach
(connect_time會在neigh_update裡被更新)
2、說明該鄰居項在delay_time超時後,還沒有被外部確認,此時就需要將鄰居項
的狀態設定為probe,準備傳送solict請求
對於probe與incomplete狀態的鄰居項,此時需要將定時器的下一次超時時間設定為
retrain,如果在下一次超時前,還沒有得到確認,則還會執行該定時器處理函式
對於probe與incomplete狀態的鄰居項:
1、如果已經超過了最大發包次數,則將鄰居項的狀態設定FAILED,並呼叫
neigh_invalidate,傳送錯誤報告,並釋放快取的資料包
2、如果還沒有超過最大發包次數,則呼叫solicit,傳送鄰居項solicit請求。
*/
if (state & NUD_REACHABLE) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->reachable_time)) {
NEIGH_PRINTK2("neigh %p is still alive.\n", neigh);
next = neigh->confirmed + neigh->parms->reachable_time;
} else if (time_before_eq(now,
neigh->used + neigh->parms->delay_probe_time)) {
NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_suspect(neigh);
next = now + neigh->parms->delay_probe_time;
} else {
NEIGH_PRINTK2("neigh %p is suspected.\n", neigh);
neigh->nud_state = NUD_STALE;
neigh->updated = jiffies;
neigh_suspect(neigh);
notify = 1;
}
} else if (state & NUD_DELAY) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->delay_probe_time)) {
NEIGH_PRINTK2("neigh %p is now reachable.\n", neigh);
neigh->nud_state = NUD_REACHABLE;
neigh->updated = jiffies;
neigh_connect(neigh);
notify = 1;
next = neigh->confirmed + neigh->parms->reachable_time;
} else {
NEIGH_PRINTK2("neigh %p is probed.\n", neigh);
neigh->nud_state = NUD_PROBE;
neigh->updated = jiffies;
atomic_set(&neigh->probes, 0);
next = now + neigh->parms->retrans_time;
}
} else {
/* NUD_PROBE|NUD_INCOMPLETE */
next = now + neigh->parms->retrans_time;
}
if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) &&
atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
neigh->nud_state = NUD_FAILED;
notify = 1;
neigh_invalidate(neigh);
}
if (neigh->nud_state & NUD_IN_TIMER) {
if (time_before(next, jiffies + HZ/2))
next = jiffies + HZ/2;
if (!mod_timer(&neigh->timer, next))
neigh_hold(neigh);
}
if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
struct sk_buff *skb = skb_peek(&neigh->arp_queue);
/* keep skb alive even if arp_queue overflows */
if (skb)
skb = skb_copy(skb, GFP_ATOMIC);
write_unlock(&neigh->lock);
neigh->ops->solicit(neigh, skb);
atomic_inc(&neigh->probes);
kfree_skb(skb);
} else {
out:
write_unlock(&neigh->lock);
}
if (notify)
neigh_update_notify(neigh);
neigh_release(neigh);
}
在申請鄰居項的記憶體函式neigh_alloc裡,會建立該定時器,並會將定時器的超時處理函式設定為neigh_timer_handler。
鄰居項狀態的更新函式3
第三個鄰居項狀態的更新函式,通過
/*
1、對於connect、delay、probe狀態的鄰居項,返回0
2、
*/
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
int rc;
unsigned long now;
write_lock_bh(&neigh->lock);
rc = 0;
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
goto out_unlock_bh;
now = jiffies;
/*
當狀態為NUD_NONE時,
1、如果組播探測最大次數加上應用探測的最大次數
不為0,則將狀態設定為INCOMPLETE,更新update時間,並修改定時器的超時時間
2、否則將鄰居項的狀態設定為failed,更新update時間,直接釋放資料包快取
當狀態為STALE時,當有資料包要傳送時,則將狀態設定為DELAY,更新update時間
並修改鄰居項定時器的超時時間
*/
if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh->nud_state = NUD_INCOMPLETE;
neigh->updated = jiffies;
neigh_add_timer(neigh, now + 1);
} else {
neigh->nud_state = NUD_FAILED;
neigh->updated = jiffies;
write_unlock_bh(&neigh->lock);
kfree_skb(skb);
return 1;
}
} else if (neigh->nud_state & NUD_STALE) {
NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_add_timer(neigh,
jiffies + neigh->parms->delay_probe_time);
}
/*
對於處於INCOMPLETE狀態的鄰居項,已經啟動了鄰居項定時器,此時只需要
將要傳送的資料包存入鄰居項的資料包快取佇列裡即可。後續如果
鄰居項可達時則會有相應的函式傳送出去
*/
if (neigh->nud_state == NUD_INCOMPLETE) {
if (skb) {
if (skb_queue_len(&neigh->arp_queue) >=
neigh->parms->queue_len) {
struct sk_buff *buff;
buff = __skb_dequeue(&neigh->arp_queue);
kfree_skb(buff);
NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
}
__skb_queue_tail(&neigh->arp_queue, skb);
}
rc = 1;
}
out_unlock_bh:
write_unlock_bh(&neigh->lock);
return rc;
}
總結:通過上面的分析,用於鄰居項狀態改變的函式有neigh_update、neigh_timer_handler、__neigh_event_send。而對於每個函式被使用的情景,我們已經在上面分析完了。
下面分析一下幾個通用鄰居項常用的函式,它們或者被上面的函式間接呼叫或者被直接呼叫,它們的邏輯結構也比較簡單:
/*
遍歷該鄰居項所關聯的所有二層快取結構,將其函式指標hh_output指向
該鄰居項的通用輸出函式
*/
static void neigh_suspect(struct neighbour *neigh)
{
struct hh_cache *hh;
NEIGH_PRINTK2("neigh %p is suspected.\n", neigh);
neigh->output = neigh->ops->output;
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->output;
}
/* Neighbour state is OK;
enable fast path.
Called with write_locked neigh.
*/
/*
遍歷該鄰居項所關聯的所有二層快取結構,將其函式指標hh_output指向
該鄰居項的快速輸出函式hh_output
*/
static void neigh_connect(struct neighbour *neigh)
{
struct hh_cache *hh;
NEIGH_PRINTK2("neigh %p is connected.\n", neigh);
neigh->output = neigh->ops->connected_output;
for (hh = neigh->hh; hh; hh = hh->hh_next)
hh->hh_output = neigh->ops->hh_output;
}
/*
返回鄰居項能傳送solicit的最大次數
*/
static __inline__ int neigh_max_probes(struct neighbour *n)
{
struct neigh_parms *p = n->parms;
return (n->nud_state & NUD_PROBE ?
p->ucast_probes :
p->ucast_probes + p->app_probes + p->mcast_probes);
}
/*
功能:傳送錯誤報告
1、更新鄰居項的update時間
2、對於鄰居狀態為failed,且鄰居項的佇列裡有資料包等待發送時,則呼叫
error_report函式,傳送錯誤報告
*/
static void neigh_invalidate(struct neighbour *neigh)
__releases(neigh->lock)
__acquires(neigh->lock)
{
struct sk_buff *skb;
NEIGH_CACHE_STAT_INC(neigh->tbl, res_failed);
NEIGH_PRINTK2("neigh %p is failed.\n", neigh);
neigh->updated = jiffies;
/* It is very thin place. report_unreachable is very complicated
routine. Particularly, it can hit the same neighbour entry!
So that, we try to be accurate and avoid dead loop. --ANK
*/
while (neigh->nud_state == NUD_FAILED &&
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
write_unlock(&neigh->lock);
neigh->ops->error_report(neigh, skb);
write_lock(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
}
/*
功能:對於一個給定的鄰居項,更新與該鄰居項有關聯的所有二層快取頭部結構成員
*/
static void neigh_update_hhs(struct neighbour *neigh)
{
struct hh_cache *hh;
void (*update)(struct hh_cache*, const struct net_device*, const unsigned char *)
= neigh->dev->header_ops->cache_update;
if (update) {
for (hh = neigh->hh; hh; hh = hh->hh_next) {
write_seqlock_bh(&hh->hh_lock);
update(hh, neigh->dev, neigh->ha);
write_sequnlock_bh(&hh->hh_lock);
}
}
}
/*
功能:鄰居項關聯的二層快取頭部的初始化
*/
static void neigh_hh_init(struct neighbour *n, struct dst_entry *dst,
__be16 protocol)
{
struct hh_cache *hh;
struct net_device *dev = dst->dev;
for (hh = n->hh; hh; hh = hh->hh_next)
if (hh->hh_type == protocol)
break;
if (!hh && (hh = kzalloc(sizeof(*hh), GFP_ATOMIC)) != NULL) {
seqlock_init(&hh->hh_lock);
hh->hh_type = protocol;
atomic_set(&hh->hh_refcnt, 0);
hh->hh_next = NULL;
if (dev->header_ops->cache(n, hh)) {
kfree(hh);
hh = NULL;
} else {
atomic_inc(&hh->hh_refcnt);
hh->hh_next = n->hh;
n->hh = hh;
if (n->nud_state & NUD_CONNECTED)
hh->hh_output = n->ops->hh_output;
else
hh->hh_output = n->ops->output;
}
}
if (hh) {
atomic_inc(&hh->hh_refcnt);
dst->hh = hh;
}
}
至此,大致分析完了通用鄰居項的工作流程,通過這次認真的閱讀通用鄰居層的程式碼機制,對於以後程式設計應該會有影響。通過分析該子層,對於一個比較好的子層程式碼:
1、 當實現一個功能時,儘量抽象一個通用層,實現通用層功能的處理,也有利於後續增加新的具體的子層功能
2、 要具有垃圾回收機制,因為記憶體是有限的,所以就需要有一個機制實現週期性的內
存回收,同時最好需要一個同步的回收機制,以便沒有記憶體用於建立新的項時,能夠及時的刪除很久沒被使用的項以建立新的項
3、 對於網路層相關的子層,一般會伴隨狀態的變化,需要考慮狀態機的構建以及完整
性分析
目前也就總結出這3項,通過這次總結,後續分析路由子層的程式碼時,應該會比較容易。