本文轉(zhuǎn)自公眾號歡迎關(guān)注
https://mp.weixin.qq.com/s/g4WKu5IaF0OVsxqyoIIhnw
一.前言
TCP/IP通訊第一步需要先調(diào)通ARP,否則TCP/IP包都不知道MAC地址要發(fā)給誰。這一篇來基于LWIP的ARP實(shí)現(xiàn)進(jìn)行相關(guān)的分析。
二.ARP協(xié)議回顧
ARP協(xié)議可以參考rfc826
幀格式如下:
硬件類型~目的端協(xié)議地址部分才是ARP協(xié)議部分,其他的為MAC幀頭尾。
總共42字節(jié),注意要+18字節(jié)的填充
這樣包括后面4字節(jié)的CRC,才滿足42+18+4=64字節(jié)的最小幀長要求。
區(qū)域 | 目的MAC地址DA | 源MAC地址SA | 類型長度Type/len | 硬件類型Hardware Type | 協(xié)議類型Protocol Type |
---|---|---|---|---|---|
大小字節(jié) | 6 | 6 | 2 | 2 | 2 |
值 | 請求時一般用FFFFFFFFFFFF廣播,響應(yīng)時用請求中解析出的對端的地址。 | 本機(jī)MAC地址 | 0x0806 | Ethernet為0x0001 | IP為0x0800 |
區(qū)域 | 硬件地址長度Hardware Addr Len | 協(xié)議地址長度Prot Addr Len | 操作碼Opcode | 發(fā)送端硬件地址Prot Addr Len | 發(fā)送端協(xié)議地址Sender Protocol Address |
大小字節(jié) | 1 | 1 | 2 | 6 | 4 |
值 | 6 | 4 | 請求為1響應(yīng)為2 | 發(fā)送端MAC地址 | 發(fā)送端IP地址 |
區(qū)域 | 目的端硬件地址Target Hardware Address | 目的端協(xié)議地址Target Protocol Address | 填充 | CRC | |
大小字節(jié) | 6 | 4 | 18 | 4 | |
值 | 目的端MAC地址 | 目的端IP地址 |
以太網(wǎng)幀是通過MAC地址來定位發(fā)送者和接收者的,但是TCP/IP協(xié)議則是通過IP地址來定位的。協(xié)議層的地址和MAC幀的地址需要一個映射表,這樣底層才知道對應(yīng)的協(xié)議地址需要綁定哪個MAC地址,最終鏈路層看的是MAC地址。
其實(shí)ARP協(xié)議不僅僅是用于IP和MAC地址的解析,實(shí)際它是通用的,可以用于不同地址空間的地址解析,地址的大小也可不同。
使用wireshark可以幫助解析
ARP協(xié)議的工作過程簡單描述就是,
發(fā)送端開始知道IP但是不知道對應(yīng)的MAC地址,所以先發(fā)廣播包問,問該IP的MAC地址是多少,同時附帶了字節(jié)IP和MAC地址,
接收端接收到這個廣播包就可以從中解析發(fā)送端的IP和MAC地址,添加到自己的ARP表格中。如果某個主機(jī)發(fā)現(xiàn)詢問的是自己的MAC地址(IP匹配),則會響應(yīng)自己的IP和MAC地址。
發(fā)送端接收到響應(yīng)之后,就知道IP地址和MAC地址的對應(yīng)關(guān)系,存到ARP表中,就可以發(fā)IP包了。
三. LWIP的ARP處理
ARP需要使能宏LWIP_ARP
ARP的處理依賴于定時器,定時器前面有分析。
定時器回調(diào)函數(shù)是etharp_tmr
周期為1S
#define ARP_TMR_INTERVAL 1000
相關(guān)代碼位于
etharp.c/h
ARP表
數(shù)據(jù)結(jié)構(gòu)
表大小ARP_TABLE_SIZE可配置,默認(rèn)是10,可配置可存的ARP條目數(shù)。
struct etharp_entry {
#if ARP_QUEUEING
/** Pointer to queue of pending outgoing packets on this ARP entry. */
struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
/** Pointer to a single pending outgoing packet on this ARP entry. */
struct pbuf *q;
#endif /* ARP_QUEUEING */
ip4_addr_t ipaddr;
struct netif *netif;
struct eth_addr ethaddr;
u16_t ctime;
u8_t state;
};
static struct etharp_entry arp_table[ARP_TABLE_SIZE];
其中使能ARP_QUEUEING則表示如果當(dāng)前還不知道IP對應(yīng)的MAC地址,可以先暫時掛起待發(fā)送的包,按照隊(duì)列掛起,如果未配置則只能掛起一個待發(fā)送的包。
ctime維護(hù)一個軟定時器,arp定時器回調(diào)時增加1,增加到一定值釋放表項(xiàng)。
在有ARP包IP包更新表項(xiàng)時清零。
Ipaddr ethaddr對應(yīng)IP和MAC地址
State維護(hù)一個狀態(tài)機(jī)
Netif對應(yīng)的接口
釋放表項(xiàng)****etharp_free_entry
設(shè)置表項(xiàng)狀態(tài)為EMPTY即可,注意如果有掛起的包也需要釋放。
查找表項(xiàng)etharp_find_entry
查找已有的表項(xiàng),或者沒有則找一個空閑的位置存新的信息。
如果找不到空閑位置則釋放最早的掛起的表項(xiàng)騰出位置。
更新表項(xiàng)etharp_update_arp_entry
調(diào)用etharp_update_arp_entry查找表項(xiàng),
如果有表項(xiàng)有掛起數(shù)據(jù)包則發(fā)送該掛起的IP包ethernet_output
靜態(tài)添加表項(xiàng)etharp_add_static_entry
需要配置宏ETHARP_SUPPORT_STATIC_ENTRIES
調(diào)用etharp_update_arp_entry手動添加表項(xiàng)
靜態(tài)釋放表項(xiàng)etharp_remove_static_entry
需要配置宏ETHARP_SUPPORT_STATIC_ENTRIES
調(diào)用etharp_find_entry查找表項(xiàng)再釋放
清除所有表項(xiàng)etharp_cleanup_netif
遍歷清除etharp_free_entry
查找地址etharp_find_addr
調(diào)用etharp_find_entry,根據(jù)IP地址查找MAC地址
根據(jù)索引查找地址****etharp_get_entry
直接根據(jù)ARP表索引返回對應(yīng)的表項(xiàng)信息
超時處理
對于ARP表項(xiàng),需要有一個有效時間,如果長時間未有對應(yīng)的ARP包或者IP包則需要釋放表項(xiàng)。
etharp_tmr
定時器前面已經(jīng)介紹過,etharp_tmr會以默認(rèn)1S的間隔調(diào)用。
遍歷所有表項(xiàng)
如果某個表項(xiàng)超過ARP_MAXAGE(默認(rèn)300S)沒有更新時,就會釋放。
定時器是在etharp_find_entry,etharp_query, etharp_update_arp_entry時清零的,也就是說超過300S沒收收到對應(yīng)的地址的IP包和ARP包就認(rèn)為超時需要釋放。
如果表項(xiàng)處于ETHARP_STATE_PENDING狀態(tài)且超過ARP_MAXAGE(默認(rèn)是5)時也要釋放表項(xiàng)。即比如一開始給某個IP發(fā)包,但是MAC地址不知道,于是發(fā)了ARP請求包,但是此時還沒有收到響應(yīng),所以設(shè)置ARP表項(xiàng)為ETHARP_STATE_PENDING狀態(tài),同時掛起待發(fā)送的包,等收到ARP響應(yīng)了再發(fā)這個掛起的包。掛起的超時時間就是ARP_MAXAGE,實(shí)際值要根據(jù)etharp_tmr間隔來,間隔是1S則實(shí)際是1x2x5=10S。
這里x2是因?yàn)椤?/p>
如果處于ETHARP_STATE_PENDING狀態(tài)則發(fā)送,ARP請求etharp_request,直到ARP_MAXAGE超時釋放表項(xiàng)。
如果是以下狀態(tài)則間隔1S切換到下一狀態(tài)
ETHARP_STATE_STABLE_REREQUESTING_1->ETHARP_STATE_STABLE_REREQUESTING_2->ETHARP_STATE_STABLE
數(shù)據(jù)流
主動廣播
手動發(fā)請求etharp_gratuitous
即廣播問IP地址是本機(jī)的MAC地址是多少,
為什么這里問的是自己的IP不是別人的呢?因?yàn)檫@是自己的IP或者狀態(tài)變了,實(shí)際是廣播一下告訴別人。
修改IP,LINK UP時會手動發(fā)一次請求,比如如下接口調(diào)用時
netif_do_set_ipaddr
netif_set_up
netif_set_link_up
ARP包輸入處理
etharp_input
根據(jù)收到的ARP包,不管是請求還是響應(yīng)包,都可以從源IP地址和源MAC地址獲取信息,更新ARP表。比如ARP廣播請求哪怕不是發(fā)給自己的也可以知道網(wǎng)絡(luò)上有源IP地址和源MAC的設(shè)備,可以更新ARP表,下次如果要給這個IP發(fā)包就可以直接發(fā),注意如果源IP地址不是單播地址也不處理。如是請求自己的ARP包就進(jìn)行響應(yīng)。
發(fā)包
etharp_output->
etharp_output_to_arp_index
在IP包上添加MAC地址,
相應(yīng)的接口直接查看源碼
etharp_query
etharp_raw
etharp_request_dst
etharp_request
ACD檢測
沖突地址檢測ACD
存在多個主機(jī)使用同一IP地址的問題,在RFC5227中定義了ACD的概念,其中定義了兩種ARP報文:ARP Probe和ARP Announcement。ARP Probe用于探測當(dāng)前廣播域是否有其他主機(jī)使用某個IP地址,ARP Probe報文的發(fā)送者IP地址字段是全0,這是為了避免ARP污染(因?yàn)锳RP請求報文會在廣播域內(nèi)廣播到每個主機(jī)上,所以主機(jī)收到帶發(fā)送者IP地址的ARP報文后都會創(chuàng)建緩存表項(xiàng),太多ARP報文會造成域內(nèi)主機(jī)上的緩存浪費(fèi))。而前面代碼中可以看出對于發(fā)送端即源IP地址為0的并不會更新到緩存。
即etharp_update_arp_entry的
如下處理ip4_addr_isany(ipaddr)
/* non-unicast address? */
if (ip4_addr_isany(ipaddr) ||
ip4_addr_isbroadcast(ipaddr, netif) ||
ip4_addr_ismulticast(ipaddr)) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: will not add non-unicast IP address to ARP cachen"));
return ERR_ARG;
}
ARP Announcement報文用于通告域內(nèi)主機(jī)自己的IP地址和MAC地址,特征是其目標(biāo)硬件地址字段全0,該報文不希望某個特定的主機(jī)回應(yīng),只需要域內(nèi)主機(jī)創(chuàng)建起ARP表項(xiàng)即可。
也是對應(yīng)上述代碼的處理。
需要使能宏LWIP_ACD
對應(yīng)兩個接口
etharp_acd_probe
etharp_acd_announce
其他代碼位于acd.c中
四.調(diào)試
#define ETHARP_DEBUG LWIP_DBG_ON 使能調(diào)試打印
ping一下設(shè)備可以看到打印如下(這里的printf不支持某些格式所以一些打印不正常)
可以借助wireshark抓包分析。
ethernet_input: dest:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, src:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, type:%hx
etharp_find_entry: found empty entry 0
etharp_find_entry: selecting empty entry 0
etharp_request: sending ARP request.
etharp_raw: sending raw ARP packet.
ethernet_output: sending packet 0x28214a18
etharp_query: queued packet 0x28214ae8 on ARP entry %hu
ethernet_input: dest:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, src:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, type:%hx
etharp_update_arp_entry: %hu.%hu.%hu.%hu - %02hx:%02hx:%02hx:%02hx:%02hx:%02hx
etharp_find_entry: found matching entry 0
etharp_update_arp_entry: updating stable entry %hd
ethernet_output: sending packet 0x28214ae8
etharp_input: incoming ARP reply
etharp_timer
ethernet_input: dest:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, src:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, type:%hx
ethernet_output: sending packet 0x28214ae8
etharp_timer
etharp_timer
ethernet_input: dest:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, src:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, type:%hx
ethernet_output: sending packet 0x28214ae8
etharp_timer
ct: dest:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, src:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, type:%hx
ethernet_output: sending packet 0x28214ae8
etharp_timer
ethernet_input: dest:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, src:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx, type:%hx
etharp_update_arp_entry: %hu.%hu.%hu.%hu - %02hx:%02hx:%02hx:%02hx:%02hx:%02hx
etharp_find_entry: found matching entry 0
etharp_update_arp_entry: updating stable entry %hd
etharp_input: incoming ARP request
etharp_raw: sending raw ARP packet.
ethernet_output: sending packet 0x28214a18
etharp_timer
etharp_timer
etharp_timer
etharp_timer
etharp_timer
etharp_timer
五.總結(jié)
主要了解ARP表項(xiàng)的更新,以及超時處理。
審核編輯:湯梓紅
-
以太網(wǎng)
+關(guān)注
關(guān)注
40文章
5419瀏覽量
171590 -
TCP
+關(guān)注
關(guān)注
8文章
1353瀏覽量
79054 -
ARP
+關(guān)注
關(guān)注
0文章
50瀏覽量
14740 -
LwIP
+關(guān)注
關(guān)注
2文章
86瀏覽量
27146 -
驅(qū)動開發(fā)
+關(guān)注
關(guān)注
0文章
130瀏覽量
12072
發(fā)布評論請先 登錄
相關(guān)推薦
評論