Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux 協議棧鄰居子系統輸出路徑:neigh_resolve_output 與 neigh_event_send

編輯:Linux 教學

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

六、 關鍵參數

Copyright © Windows教程網 All Rights Reserved
參數含義默認值
reachable_timeREACHABLE 狀態保持時間(隨機化)30s (base_reachable_time)
delay_probe_timeSTALE→DELAY 延遲探測時間5s
retrans_time探測重發間隔1s
ucast_probes單播探測次數3
mcast_probes多播探測次數3
app_probes應用層探測次數0
queue_lenarp_queue 最大長度3