Linux 網路系統學習: Neighboring Subsystem

來源:互聯網
上載者:User
文章目錄
  • 2.2 資料結構關係:
  •  
  • 5.3 一個實際的 case
Linux 網路系統學習: Neighboring Subsystem1. 概述

在資料包的發送過程中,通過路由獲得下一跳的 L3 地址,下一步是獲得此 L3 地址所對應的 L2 地址,這個過程稱為 neighbor discovery。IPv4對應的是 ARP 協議,IPv6 對應的是Neighbor Discovery 協議。

Linux 中,用於處理neighbor discovery的模組稱為 neighboring subsystem。它分為兩層,底層是通用架構 neighboring infrastructure,在此之上,又有不同的具體實現,例如ARP 模組、 ND 模組等。

Neighboring subsystem 的主要任務包括:

1、Neighbour discovery;通過 L3 地址找到 L2 地址;為發送資料提供保障
2、接收 neighbor 包並進行處理
3、提供 cache,以加速 neighboring 的過程
4、為系統中其它模組需要 neighboring discovery 而提供 APIs

2. Neighboring infrastructure

 

2.1 主要資料結構:

1、  struct neighbour
最主要的結構

2、  struct neigh_table
用於管理 struct neighbour

3、  struct neigh_ops
用於映射到 L2  的輸出函數

4、  struct neigh_parms

5、  struct hh_cache

           

2.2 資料結構關係:

是 neighboring subsystem 中資料結構關係圖,其關係可描述如下:

1、系統通過 neigh_tables 來管理各種具體的 neigh_table,包括 arp_tbl 和 nd_table
2、Neigh_table 通過 hash_buckets 來維護一個 neigh_table 的 hash 表。可以迅速的增加、刪除、尋找  neighbour
3、neighbour  的作用??? Neighbour 的 parms 指向 neigh_parms 結構,此結構用於 neighbour 的維護,例如重傳次數,狀態轉換時間,垃圾收集時間等。
4、neighbour 的 ops 指向 neigh_ops 結構,此結構用於???
5、neighbour 的 hh 指向 hh_cache,此結構用於 cache L2 地址,以加速 L3 到 L2 的映射過程。


 


 

2.3 工具函數

1、struct neighbour *neigh_alloc(struct neigh_table *tbl)
建立一個 neighbour,並初始化,它只被  neighbour_create() 調用

2、struct neighbour * neigh_create(struct neigh_table *tbl, const void *pkey, struct net_device *dev)
調用 neigh_alloc() 分配一個 neighboure ,然後進一步調用具體協議的建構函式,以及具體裝置的特殊的設定函數;最後,將此 neighbour 加入 neighbour table 中
它主要被  __neigh_lookup() 調用,也就是說,當在 neighbour table 中找不到 neighbour 的時候,調用此函數來建立一個新的 neighbour

3、struct neighbour *neigh_lookup(struct neigh_table *tbl, const void *pkey, struct net_device *dev)
在 neighbour table 中尋找特定的 neighbour

4、static void neigh_timer_handler(unsigned long arg)
這是一個定時器處理函數。當某個 neighbour 逾時後,由此函數處理。

5、int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new, int override, int arp)

6、void neigh_table_init(struct neigh_table *tbl)
用於初始化一個 neigh_table。

每個 table  有一個定時器函數,用於垃圾收集,也就是清除那些逾時的 neighbour.

    init_timer(&tbl->gc_timer);
    tbl->lock = RW_LOCK_UNLOCKED;
    tbl->gc_timer.data = (unsigned long)tbl;
    tbl->gc_timer.function = neigh_periodic_timer;
    tbl->gc_timer.expires = now + 1;
    add_timer(&tbl->gc_timer);
 
這個 neigh_periodic_timer 實際是

static void SMP_TIMER_NAME(neigh_periodic_timer)(unsigned long arg)

7、int neigh_table_clear(struct neigh_table *tbl)
Neigh_table 通過 hash 表來維護 neighbour

struct neighbour            **hash_buckets;

具體的協議實現需要提高 hash 函數,例如  arp_hash()

neigh_hash_alloc 用於建立  hash 表

 

3.

Neighbour 系統的初始化

全域變數 neigh_tables 維護系統中所有的 neigh_table

static struct neigh_table *neigh_tables;

IPv4 ARP 的初始化:
    調用neigh_table_init() 對 arp_tbl 初始化
    調用dev_add_pack(&arp_packet_type) ,註冊 ARP 包接收函數

IPv6 Neighborour Discovery 的初始化:
    調用 neigh_table_init(&nd_tbl) 對 nd_tbl 初始化
    IPv6 通過 ICMPv6 來處理 ND 的包,沒有專門的 ARP包類型。

發送資料包過程中,在路由過程中,與 neighbour 結構進行關聯,路由結束後,資料包交給 neighboring subsystem 進一步處理。

4. Routing與 Neighboring subsystem的關聯4.1 Neighbour與路由的關聯

在路由過程中,需要尋找或建立 struct dst_entry (另一種形式是 struct rtable)。 dst_entry 通過neighbour 域與 struct neighbour 關聯。


4.1.1 關聯的目的

 每個 dst_entry 對應一個 neighbour,這樣在路由之後,立刻能找到對應的 neighbour,此後,資料包通過 neighbour->output 送到鏈路層。

以 UDP 包的發送過程為例,這個過程如下:

Udp_sendmsg() ==> ip_route_output() ==> ip_route_output_slow()

Ip_route_output_slow() :
    當查不到路由 cache 後,根據 route rule ,通過 dst_alloc() 建立一個 dst_entry 結構,這同時也是一個  rtable 結構,然後將 dst_entry  的 output 指向 ip_output()
    
    rth->u.dst.output=ip_output;

    此後,udp_sendmsg 繼續調用 ip_build_xmit() 來發包

Udp_sendmsg() ==> Ip_build_xmit() ==> output_maybe_reroute ==> skb->dst->output()

這裡的 output 就是 ip_output()

ip_output ==> __ip_finish_output() ==> ip_finish_output2() ==> dst->neighbour->output()

因此,最終資料包是通過 neighbour->output() 往下送的。

4.1.2

關聯的過程

IPv4 代碼實現:ip_route_output_slow

在路由 cache 中查不到路由結果後,尋找 route rule ,如果沒有合適的路由規則,則失敗返回。否則,通過 dst_alloc() 建立一個 dst_entry 結構,這同時也是一個  rtable 結構,此 rtable 結構被掛入 hash 表中。這時候我們已經有了下一跳的 L3地址。(也可能沒有,例如綁定 interface 的情況,需要看代碼是如何處理的)。

下一步,要通過arp_bind_neighbour 將 rtable 與 neighbour 進行綁定

rt_intern_hash  è arp_bind_neighbour()

arp_bind_neighbour() 根據給定的下一跳 L3 地址,到 arp hash 表中找 neighbour,找到的話,dst->neighbour 就有了歸宿;找不到,只好調用 neighbour_create() 建立一個新的 neighbour,這是在__neigh_lookup_errno() 中完成的

arp_bind_neighbour() ==> __neigh_lookup_errno() ==> neigh_lookup() ==> neigh_create()。

    ip_route_output_slow()
    fib_lookup()
    rt_intern_hash()
    arp_bind_neighbour()

4.2 Neighbour 的構造和設定

neigh_alloc() 用於分配 neighbour 結構

neigh_create() 進一步設定此結構,對於 ARP 來說,它調用 arp_constructor() ,在這個函數裡面,對 neighbour 的 ops 域和 output 域進行設定。

Ops 域,根據底層 driver 的類型進行不同的設定,

    對於沒有鏈路層地址的,指向arp_direct_ops
    對於沒有鏈路層 cache 的,指向arp_generic_ops
    對於有鏈路層 cache 的, 指向arp_hh_ops

對於乙太網路驅動程式,它的 net_device 結構在初始化的時候,已經有了預設的 hard_header 和 hard_header_cache 函數,

    ether_setup()
    dev->hard_header        = eth_header;
    dev->hard_header_cache         = eth_header_cache;

因此,預設情況下,它的 ops 指向 arp_hh_ops()

對於 output 域,關鍵是看 neighbour 的狀態,如果是有效狀態,則設定為 ops->connected_output(),這樣可以加快速度,否則設定為 ops->output(),這樣,需要進行 neighbor discovery 的處理。

對於 ARP 來說,無論是 output ,還是 connect_output都是指向 neigh_resolve_output()。(原因?)

neigh_resolve_output 進行 neighbor discovery 的過程。

在理解 neighbor discovery 之前,我們需要先理解 neighbour 的狀態轉換機制。

5. Neighbour 的狀態轉換


5.1 Neighbour 的狀態

Neighbour 結構可以處於不同狀態, 包括:

#define NUD_INCOMPLETE 0x01
#define NUD_REACHABLE   0x02
#define NUD_STALE  0x04
#define NUD_DELAY 0x08
#define NUD_PROBE 0x10
#define NUD_FAILED            0x20
#define NUD_NOARP            0x40
#define NUD_PERMANENT  0x80
#define NUD_NONE  0x00


5.2  Neighbour 的狀態轉換過程

Linux 為它維護一個狀態機器,狀態機器通過 timer 以及資料包的收發來驅動。它可以描述如下:

1、  neighbour 建立後,處於 NONE 狀態

2、  neigh_resolve_output() 調用neigh_event_send() 來觸發狀態轉換;對於 NONE 狀態來說,調用__neigh_event_send()。

__neigh_event_send()將 neighbour 狀態設定為 INCOMPLETE,然後設定 timer,timer 的逾時時間為neigh->parms->retrans_time。
然後調用neigh->ops->solicit 發送 neighbour discovery 包。對 ARP 來說就是 arp_solicit()。
對於正常收到響應包的處理,我們在 ARP 包接收部分進行分析,總之狀態會轉換為 REACHABLE。
如果逾時未收到包,則由逾時處理函數neigh_timer_handler 進行處理

3、  neigh_timer_handler() 會重傳請求包,重傳次數由neigh_max_probes() 計算。如果超過重傳次數,則狀態轉為 FAILED

4、  處於 REACHABLE 狀態的 neighbour,有一定的有效期間(參數?),超過這個有效期間後,由neigh_periodic_timer() 進行處理。

5、  第一個有效期間是 reachable_time(對 ARP,這個值是 30 HZ,也就是 300ms),這個時間以後,

6、    STALE 狀態的轉換比較難理解。Neighbour 處於 REACHABLE 狀態後,如果在一段時間內,沒有收到過 reply ,那麼則懷疑此 neighbor 不可達,需要將它的狀態轉為 STALE,但是又不立刻進行轉換。(在哪些情況下進行狀態轉換?)

在 STABLE 狀態下,再次進入__neigh_event_send的時候,則將之狀態轉為 DELAY,並啟動定時器。(逾時時間是delay_probe_time,預設為 50ms)
在 STALE 狀態,並不進行 ARP 解析,資料包仍然可以直接發送出去。

7、  在 DELAY 狀態,資料可以直接發送出去,但是一旦定時器逾時,則轉入 PROBE 狀態。如果在此期間收到過 reply 包,則轉為 REACHABLE。

8、  在 PROBE 狀態,資料仍然可以直接發送出去。但是在此狀態,開始 ARP 探測,而且僅探測一次,如果失敗,則轉為 FAILED;如果收到 reply 包,則轉為 REACHABLE。

9、  neigh_periodic_timer 會定時把 FAILED 狀態的 neighbour 清理掉

 

 

 

 

Linux 還定義了幾種狀態組合的變數:

#define NUD_IN_TIMER        (NUD_INCOMPLETE|NUD_DELAY|NUD_PROBE)

INCOMPLETE、 DELAY、PROBE 狀態,有定時器在工作

#define NUD_VALID          (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE|NUD_PROBE|NUD_STALE|NUD_DELAY)

以上狀態,資料包都直接發送,不進行 ARP 解析過程。

當 neighbour 未處於 VALID 狀態時,資料包無法發送,只能送入 neighbour-> arp_queue 中。(可以從__neigh_event_send看到相關代碼)。

#define NUD_CONNECTED  (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)

以上狀態,資料包不僅直接發送,而且可以肯定這個 neighboring 是可達的。

5.3 一個實際的 case

在筆者參與的一個嵌入式無線產品開發過程中,應用需要每隔 3s 發送一個 UDP 包出去,這個 UDP 包是單向的,也就是說只向外發送,並沒有響應的包。在測試過程中發現,在每次發送 UDP 包之前,都會先有一個 ARP 查詢,這種沒有必要的 ARP 包不僅影響效能,也很耗電。

分析其原因,發現:

在一次成功的 ARP 解析後,neighbour  的有效期間大概為 300 ms。

 

在第一次發送 UDP 包之前,因為 Neighbour Cache 中沒有資料,所以需要發送 ARP 查詢包,以獲得下一跳的 MAC 位址。當收到 ARP 應答後,找到對應的 neighbour,然後將它的狀態轉為 STALE,然後立刻轉為 DELAY,並啟動 50ms 的定時器,這時候,那個導致 ARP 查詢的 UDP 包可以使用 neighbour 的映射並發送出去,但是由於這個 UDP 包並不要求回應,因此50 ms 後,neighbour 的狀態轉為 PROBE。這樣,3s 以後,下一個 UDP 包又會導致一次 ARP 查詢。

解決方案:
由於此嵌入式裝置的下一跳肯定是網關,因此可以先通過 ARP 查詢獲得網關 的 L2 地址,然後在嵌入式裝置上設定一條靜態 ARP 規則,這樣,就再也不會有這種無實際意義的 ARP 包出現了。

6.  Neighbor Discovery 的 過程

從上面的狀態機器可以看到,當 neighbour 處於 INCOMPLETE、PROBE 狀態的時候,會發送 Neighbor Solicit 包:

例如,通過 neigh_resolve_output() 導致新建立一個 neighbour 結構後,最後會調用 neigh->ops->solicit() 來發送 NS 包,對於 ARP 來說,就是 arp_solicit():

neigh_resolve_output() ==>  neigh_event_send() ==> __neigh_event_send()  ==> neigh->ops->solicit(neigh, skb);  ==>  arp_solicit()

arp_solicit 調用 arp_send() 構造並發送 ARP request:

對於 INCOMPLETE 狀態,需要發送一個新的 ARP 請求,它的目的 MAC 位址是廣播位址,這樣鏈路上所有節點都能收到此廣播包;

對於 PROBE 狀態, neighbour 中已經有了對端的 MAC 位址,此時發 ARP request 的目的只是驗證這個映射還是有效,因此此時發出的 ARP 包的目的 MAC 位址可以從 neighbour 中取到,是一個單播的 ARP 包。

7.  ARP 包的接收處理過程

Arp_rcv()  ==>  arp_process()

如果收到的是 ARP request,且是到原生,調用neigh_event_ns ,以建立一個新的 neighbour 結構,然後調用arp_send() 回應一個 ARP reply。
如果收到的是 ARP reply,那麼調用__neigh_lookup 去尋找是否有對應的 neighbour,如果沒有,則丟棄此包;否則調用neigh_update() 將此 neighbour 狀態更新為 REACHABLE。同時,所有在此 neighbour 上等待的資料包被發送

8. 參考文獻

1、Linux 2.4 核心源碼
2、Linux 2.6 核心源碼
3、<<The.Linux.Networking.Architecture_Design.and.Implementation.of.Network.Protocols.in.the.Linux.Kernel >>
4、<<Understanding Linux Network Internals>>
5、<< The Linux TCPIP Stack- Networking for Embedded Systems>>
 


相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.