IP 層路由查找完成後,數據包通過 dst_entry->neighbour 關聯的鄰居條目發送。neigh_resolve_output() 是鄰居子系統的統一發送入口,負責判斷鄰居可達性、構建鏈路層頭部、發起地址解析、排隊待解析數據包。本文基於 Linux 2.6.21 源碼詳細解析發送路徑、狀態機驅動、定時器探測機制。
neigh_resolve_output()
int neigh_resolve_output(struct sk_buff *skb) {
struct dst_entry *dst = skb->dst;
struct neighbour *neigh;
int rc = 0;
if (!dst || !(neigh = dst->neighbour)) // 無鄰居綁定
goto discard;
__skb_pull(skb, skb->nh.raw - skb->data); // 回退到網絡層頭部
// 核心判斷:鄰居是否可直接發送
if (!neigh_event_send(neigh, skb)) {
int err;
struct net_device *dev = neigh->dev;
// 構建鏈路層頭部
if (dev->hard_header_cache && !dst->hh) {
write_lock_bh(&neigh->lock);
if (!dst->hh)
neigh_hh_init(neigh, dst, dst->ops->protocol);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
write_unlock_bh(&neigh->lock);
} else {
read_lock_bh(&neigh->lock);
err = dev->hard_header(skb, dev, ntohs(skb->protocol), neigh->ha, NULL, skb->len);
read_unlock_bh(&neigh->lock);
}
if (err >= 0)
rc = neigh->ops->queue_xmit(skb); // 驅動發送
else
goto out_kfree_skb;
}
return rc;
discard:
NEIGH_PRINTK1("neigh_resolve_output: dst=%p neigh=%p\n", dst, dst ? dst->neighbour : NULL);
out_kfree_skb:
rc = -EINVAL;
kfree_skb(skb);
return rc;
}
neigh_event_send()
static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb) {
neigh->used = jiffies; // 更新最後使用時間
// 可直接發送的狀態:CONNECTED、DELAY、PROBE
if (!(neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE)))
return __neigh_event_send(neigh, skb); // 需發起解析
return 0; // 可直接發送
}
__neigh_event_send()
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb) {
int rc = 0;
unsigned long now = jiffies;
write_lock_bh(&neigh->lock);
// 雙重檢查
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
goto out_unlock;
// 1. 全新條目或 INCOMPLETE/FAILED:發起 ARP 請求
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_hold(neigh);
neigh->timer.expires = now + 1; // 立即發送第一個探測
add_timer(&neigh->timer);
} else {
// 無探測配置:直接標記失敗
neigh->nud_state = NUD_FAILED;
write_unlock_bh(&neigh->lock);
if (skb) kfree_skb(skb);
return 1;
}
}
// 2. STALE 狀態:進入 DELAY,延遲探測
else if (neigh->nud_state & NUD_STALE) {
NEIGH_PRINTK2("neigh %p is delayed.\n", neigh);
neigh_hold(neigh);
neigh->nud_state = NUD_DELAY;
neigh->timer.expires = jiffies + neigh->parms->delay_probe_time;
add_timer(&neigh->timer);
// 返回 0,上層 neigh_resolve_output 將繼續發送數據包
}
// 3. 將數據包排隊(僅 INCOMPLETE 狀態)
if (neigh->nud_state == NUD_INCOMPLETE) {
if (skb) {
// 隊列長度限制
if (skb_queue_len(&neigh->arp_queue) >= neigh->parms->queue_len) {
struct sk_buff *buff = neigh->arp_queue.next;
__skb_unlink(buff, &neigh->arp_queue);
kfree_skb(buff); // 丟棄最舊
}
__skb_queue_tail(&neigh->arp_queue, skb);
}
rc = 1; // 告知上層數據包已排隊
}
out_unlock:
write_unlock_bh(&neigh->lock);
return rc;
}
neigh_timer_handler()定時器超時觸發探測發送或狀態轉換:
void neigh_timer_handler(struct timer_list *t) {
struct neighbour *neigh = from_timer(neigh, t, timer);
unsigned long now = jiffies;
write_lock(&neigh->lock);
switch (neigh->nud_state) {
case NUD_INCOMPLETE:
case NUD_PROBE:
// 發送單播/多播探測
if (atomic_read(&neigh->probes) > 0) {
atomic_dec(&neigh->probes);
neigh->ops->solicit(neigh, NULL); // arp_solicit / ndisc_solicit
neigh->timer.expires = now + neigh->parms->retrans_time;
add_timer(&neigh->timer);
} else {
// 探測耗盡,標記失敗
neigh->nud_state = NUD_FAILED;
neigh_release_skb(neigh); // 丟棄排隊 skb 並發送 ICMP 錯誤
}
break;
case NUD_DELAY:
// 延遲結束,進入 PROBE
neigh->nud_state = NUD_PROBE;
atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh_hold(neigh);
neigh->timer.expires = now + neigh->parms->retrans_time;
add_timer(&neigh->timer);
break;
case NUD_REACHABLE:
// 可達時間過期,轉為 STALE
neigh->nud_state = NUD_STALE;
neigh_suspect(neigh); // 輸出函數改為 suspect
break;
case NUD_STALE:
case NUD_FAILED:
case NUD_PERMANENT:
case NUD_NOARP:
// 無需處理
break;
}
write_unlock(&neigh->lock);
}
發送數據包
↓
neigh_resolve_output()
↓
neigh_event_send() --狀態∈{CONNECTED,DELAY,PROBE}?--Yes→ 構建硬件頭 → queue_xmit 發送
↓ No
__neigh_event_send()
↓
狀態判斷:
INCOMPLETE/FAILED/新建 → 置 INCOMPLETE,啟動定時器(1 jiffy),排隊 skb,返回 1
STALE → 置 DELAY,啟動定時器(delay_probe_time),返回 0(繼續發送)
↓
定時器超時 (neigh_timer_handler)
INCOMPLETE/PROBE → 發送探測,遞減 probes,重啟定時器(retrans_time)
probes 歸零 → 置 FAILED,丟棄排隊 skb
DELAY → 置 PROBE,設置 probes,啟動定時器
REACHABLE → 置 STALE,輸出函數改為 suspect
↓
收到 ARP Reply / NDP Neighbor Advertisement
↓
neigh_update(..., NUD_REACHABLE, OVERRIDE)
↓
置 REACHABLE,綁定 connect 輸出,發送 arp_queue 排隊 skb
| 參數 | 含義 | 默認值 |
|---|---|---|
| reachable_time | REACHABLE 狀態保持時間(隨機化) | 30s (base_reachable_time) |
| delay_probe_time | STALE→DELAY 延遲探測時間 | 5s |
| retrans_time | 探測重發間隔 | 1s |
| ucast_probes | 單播探測次數 | 3 |
| mcast_probes | 多播探測次數 | 3 |
| app_probes | 應用層探測次數 | 0 |
| queue_len | arp_queue 最大長度 | 3 |