ARP,全稱Address Resolution Protocol
,譯作地址解析協議,是位於TCP/IP協議棧底層的協議。任何網絡的通信都是基於底層硬件鏈路的,底層的數據鏈路有着自己的一套尋址機制,在以太網中,往往是通過一個48位的MAC地址來標示不同的網絡通信設備的。而 TCP/IP
協議的上層是使用IP地址作爲各個主機間通信尋址機制的。當源主機上層要向目標主機發送數據時,它只知道目標主機的IP地址,此時,源主機需要將該IP地址轉換爲目的主機對應的MAC地址,這樣才能在數據鏈路上選擇正確的通道將數據傳送出去,這就是ARP的作用。
協議裏面的一段描述可能更明瞭:在ARP背後有一個基本概念,那就是每個網絡接口有一個硬件地址(一個 48 bit的值,標識不同的以太網或令牌環網絡接口),在硬件層次上進行的數據幀交換必須有正確的硬件接口地址。但是,TCP/IP有自己的地址:32bit的IP地址。知道主機的IP地址並不能讓內核發送一幀數據給主機。內核(如以太網驅動程序)必須知道目的端的硬件地址才能發送數據。ARP的功能是在32 bit的I P地址和採用不同網絡技術的硬件地址之間提供動態映射。ARP協議的基本功能就是通過目標設備的IP地址,查詢目標設備的MAC地址,以保證通信的進行。
ARP協議實現的核心是ARP緩存表,ARP的實質就是對緩存表的建立、更新、查詢等操作。ARP緩存表是由一個個的**緩存表項(entry)**組成的,LWIP中描述緩存表項的數據結構叫etharp_entry
,上源代碼:
struct etharp_entry {
#if ARP_QUEUEING
struct etharp_q_entry *q; // 數據包緩衝隊列指針
#endif
struct ip_addr ipaddr; // 目標IP地址
struct eth_addr ethaddr; // MAC地址
enum etharp_state state; // 描述該entry的狀態
u8_t ctime; // 描述該entry的時間信息
struct netif *netif; // 相應網絡接口信息
};
ARP_QUEUEING是編譯選項,表示是否允許緩存表項有數據包緩衝隊列,在opt.h
裏面設置。爲什麼要用數據包緩衝隊列指針,隨後慢慢道來。ipaddr
和ethaddr
字段就是分別用於存儲IP地址和MAC地址的,它們是ARP緩存表項的核心部分了。state
是個枚舉類型,它表示該緩存表項的狀態,一個表項有三個可能的狀態,我們用枚舉型etharp_state
進行描述。
enum etharp_state {
ETHARP_STATE_EMPTY = 0,
ETHARP_STATE_PENDING,
ETHARP_STATE_STABLE
};
LWIP內核通過數組的方式來創建ARP緩存表,如下,
static struct etharp_entry arp_table[ARP_TABLE_SIZE];
初始化緩存表中的各個緩存表項都處於初始狀態,沒有記錄任何信息,此時每個表項都處於ETHARP_STATE_EMPTY
狀態,ETHARP_STATE_PENDING
狀態表示該表項處於不穩定狀態,很可能的情況是,該表項只記錄到了IP地址,但是還未記錄到對應該IP地址
的MAC地址
,此時就該表項就處於ETHARP_STATE_PENDING
狀態。在該狀態下,LWIP內核會發出一個廣播ARP請求到數據鏈路上,以讓對應IP地址的主機迴應其MAC地址,當源主機接收到MAC地址時,它就更新對應的ARP表項。當ARP表項得到更新後,它就完全記錄了一對IP地址和MAC地址,此時該表項就處於ETHARP_STATE_STABLE
狀態。注意當某表項處在PENDING
狀態時,要發往該表項中IP地址處的數據包會被連接在表項對應的數據包緩衝隊列上,當等到該表項穩定後,這些數據包纔會被髮送出去。這就是爲什麼每個表項需要有數據包緩衝隊列指針了。
ctime字段記錄表項處於某個狀態的時間,當某表項的ctime
值大於規定的表項最大生存值時,該表項會被內核刪除。在第一講中,我們就說到了關於LWIP的超時事件,要使用ARP功能,就必須設置一個ARP超時事件
,該超時事件的基本功能就是對每個表項的ctime
字段值加1
,然後刪除那些生存時間大於最大生存值的表項。
好了,下面講講能夠正確建立ARP緩存的基礎:ARP數據包。要在源主機上建立關於目標主機的IP地址與MAC地址對應表項,則源主機和目的主機間的基本信息交互是必須的,簡單的說就是,源主機如何告訴目的主機:我需要你的MAC地址;而目的主機如何回覆:這就是我的MAC地址。ARP數據包,這就派上用場了。
ARP數據包可以分爲ARP請求數據包和ARP應答數據包,ARP數據包到達底層鏈路時會被加上以太網數據包頭髮送出去,最終呈現在鏈路上的數據報頭格式如下圖,
以太網包頭中的前兩個字段是以太網的目的MAC地址和源MAC地址,在前面一章已經有講解。目的地址爲全 1
的特殊地址是廣播地址。在ARP表項建立前,源主機只知道目的主機的IP地址,並不知道其MAC地址,所以在數據鏈路上,源主機只有通過廣播的方式將ARP請求數據包
發送出去。電纜上的所有以太網接口都要接收廣播的數據包,並檢測數據包是否是發給自己的,這點通過對照目的IP地址來實現,如果是發給自己的,目的主機需要回復一個ARP應答數據包
給源主機,以告訴源主機自己的MAC地址
。
兩個字節長的以太網幀類型表示後面數據的類型。對於ARP請求或應答數據包來說,該字段的值爲0x0806
,對於IP數據包來說,該字段的值爲0x0800
。
以太網數據報頭說完,來說ARP數據報頭。
硬件類型字段表示硬件地址的類型,它的值爲 1
即表示以太網MAC地址,長度爲6個字節。協議類型字段表示要映射的協議地址類型。它的值爲0x0800
即表示要映射爲IP地址。它的值與包含IP數據報的以太網數據幀頭中的類型字段的值相同。
接下來的兩個1字節的字段,硬件地址長度和協議地址長度分別指出硬件地址和協議地址的長度,以字節爲單位。對於以太網上A R P請求或應答來說,它們的值分別爲6
和4
。
操作字段op指出四種操作類型,它們是 A R P請求(值爲1
)、A R P應答(值爲2
)、R A R P請求(值爲3
)和R A R P應答(值爲4
),這裏我們只關心前兩個類型。這個字段必需的,因爲A R P請求和A R P應答的幀類型字段值是相同的。
接下來的四個字段是發送端的以太網MAC地址、發送端的I P地址、目的端的以太網MAC地址和目的端的I P地址。
注意,這裏有一些重複信息:在以太網的數據幀報頭中和A R P請求數據幀中都有發送端的以太網MAC地址。對於一個ARP請求來說,除目的端MAC地址外的所有其他的字段都有填充值。當目的主機收到一份給自己的ARP請求報文後,它就把自己的硬件地址填進去,然後將該請求數據包的源主機信息和目的主機信息交換位置,並把操作字段op置爲2,最後把該新構建的數據包發送回去,這就是ARP響應。
最後,用源碼來看看LWIP是如何描述上面的這個數據報頭的:
struct etharp_hdr {
PACK_STRUCT_FIELD(struct eth_hdr ethhdr); // 14字節的以太網數據報頭
PACK_STRUCT_FIELD(u16_t hwtype); // 2字節的硬件類型
PACK_STRUCT_FIELD(u16_t proto); // 2字節的協議類型
PACK_STRUCT_FIELD(u16_t _hwlen_protolen); // 兩個1字節的長度字段
PACK_STRUCT_FIELD(u16_t opcode); // 2字節的操作字段op
PACK_STRUCT_FIELD(struct eth_addr shwaddr); // 6字節源MAC地址
PACK_STRUCT_FIELD(struct ip_addr2 sipaddr); // 4字節源IP地址
PACK_STRUCT_FIELD(struct eth_addr dhwaddr); // 6字節目的MAC地址
PACK_STRUCT_FIELD(struct ip_addr2 dipaddr); // 4字節目的IP地址
} PACK_STRUCT_STRUCT;
和前面的各個描述完全相符。PACK_STRUCT_FIELD()
是防止編譯器字對齊的宏定義。