neigh_update() 是鄰居子系統更新鄰居條目狀態、鏈路層地址、輸出路徑的核心函數,由 ARP/NDP 接收處理、用戶態配置、內核探測邏輯調用。本文基於 Linux 2.6.21 源碼詳細解析參數校驗、狀態轉換邏輯、地址比較覆蓋策略、定時器管理、輸出函數綁定全流程。
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new_state, u32 flags)
/* flags 位定義 */
#define NEIGH_UPDATE_F_ADMIN 0x01 // 管理員靜態設置
#define NEIGH_UPDATE_F_OVERRIDE 0x02 // 強制覆蓋地址
#define NEIGH_UPDATE_F_WEAK_OVERRIDE 0x04 // 弱覆蓋(僅 CONNECTED→STALE)
#define NEIGH_UPDATE_F_OVERRIDE_ISROUTER 0x08 // 更新 isrouter 標志
write_lock_bh(&neigh->lock);
if (!(flags & NEIGH_UPDATE_F_ADMIN) && (old & (NUD_NOARP | NUD_PERMANENT)))
goto out; // 非管理員不可修改永久/無ARP條目
if (!(new & NUD_VALID)) { // 轉為無效狀態(FAILED/INCOMPLETE)
neigh_del_timer(neigh);
if (old & NUD_CONNECTED)
neigh_suspect(neigh); // 輸出函數改為 solicited
neigh->nud_state = new;
goto out;
}
// 無地址設備(如隧道)直接使用現有地址
if (!dev->addr_len) lladdr = neigh->ha;
// 有新地址且舊地址有效:比較是否相同
if (lladdr && (old & NUD_VALID) && !memcmp(lladdr, neigh->ha, dev->addr_len))
lladdr = neigh->ha; // 相同則忽略新地址
// 無新地址:若舊地址有效則保留,否則報錯
if (!lladdr) {
if (!(old & NUD_VALID)) { err = -EINVAL; goto out; }
lladdr = neigh->ha;
}
// 連接狀態重置 confirmed 時間戳
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;
neigh->updated = jiffies;
int 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)) {
// 弱覆蓋:CONNECTED 狀態下允許降級為 STALE
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; // 保持原狀態
}
}
if (new != old) {
neigh_del_timer(neigh);
if (new & NUD_IN_TIMER) { // REACHABLE / DELAY / PROBE
neigh_hold(neigh);
neigh->timer.expires = jiffies +
((new & NUD_REACHABLE) ? neigh->parms->reachable_time : 0);
add_timer(&neigh->timer);
}
neigh->nud_state = new;
}
// 更新鏈路層地址
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; // 觸發 ARPD 通知
}
if (new == old) goto out;
// 綁定輸出函數
if (new & NUD_CONNECTED)
neigh_connect(neigh); // 輸出 → neigh_resolve_output (正常發送)
else
neigh_suspect(neigh); // 輸出 → neigh_suspect_output (發起探測)
// 釋放 arp_queue 中排隊的 skb
if ((old & NUD_VALID) == 0 && (new & NUD_VALID))
neigh_release_skb(neigh);
| 舊狀態 | 新狀態 | 觸發場景 | 關鍵動作 |
|---|---|---|---|
| INCOMPLETE | REACHABLE | 收到 ARP Reply | 綁定 lladdr、啟動 reachable 定時器、綁定 connect 輸出、發送排隊 skb |
| STALE | DELAY | 上層發送數據 | 啟動 delay_probe_time 定時器 |
| DELAY | PROBE | 定時器超時 | 發送單播探測、啟動 retrans 定時器 |
| PROBE | REACHABLE | 收到探測回應 | 同 INCOMPLETE→REACHABLE |
| REACHABLE | STALE | reachable_time 過期 | 無定時器,標記 STALE |
| 任何 | FAILED | 探測失敗/管理設置 | 清理定時器、綁定 suspect 輸出、GC 回收 |
| 任何 | PERMANENT | 管理員靜態添加 | 永不老化、無定時器、refcnt 永不歸零 |
neigh_connect():neigh->ops->output = neigh_resolve_output,hh_output 可用時走快路徑。neigh_suspect():output = neigh_suspect_output,發送數據前先發起探測。neigh_update_hhs():遍歷 neigh->hh 硬件頭緩存鏈表,更新目的 MAC 地址。neigh_release_skb():將 neigh->arp_queue 中排隊的 skb 通過 neigh->ops->output 發送。