姓名:朱小鵬 ? ?學(xué)號:16010130023
轉(zhuǎn)載:
http://blog.sina.com.cn/s/blog_62a85b950101anvx.html
【嵌牛導(dǎo)讀】:最后要講的一個函數(shù)是update_arp_entry岩喷,該函數(shù)用于更新ARP緩存表中的表項或者在緩存表中插入一個新的表項虏肾。該函數(shù)會在收到一個IP數(shù)據(jù)包或ARP數(shù)據(jù)包后被調(diào)用浪箭。
【嵌牛鼻子】:ARP表的創(chuàng)建,更新课锌,查詢等操作
【嵌牛提問】:LWIP是怎樣進(jìn)行ARP表的創(chuàng)建,更新凸椿,查詢等操作肠槽?
【嵌牛正文】:
最后要講的一個函數(shù)是update_arp_entry,該函數(shù)用于更新ARP緩存表中的表項或者在緩存表中插入一個新的表項阔加。該函數(shù)會在收到一個IP數(shù)據(jù)包或ARP數(shù)據(jù)包后被調(diào)用饵史。該函數(shù)原型如下,
static err_t
update_arp_entry(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr *ethaddr, u8_t flags)
其中重要的兩個參數(shù)ipaddr和ethaddr分別對應(yīng)的ip地址和mac地址胜榔,函數(shù)利用這兩個地址去更新或插入ARP表項胳喷。由于這個函數(shù)代碼量較小,這里就列出源碼來講解夭织,注意這個源碼是經(jīng)過我處理的吭露,已經(jīng)去掉了編譯選項、源碼注釋尊惰、調(diào)試輸出信息等非重點部分讲竿。
static err_t
update_arp_entry(struct netif *netif, struct ip_addr *ipaddr, struct eth_addr *ethaddr, u8_t flags)
{
s8_t i;// 兩個變量泥兰,不解釋
u8_t k;
i = find_entry(ipaddr, flags);// 查找或新建一個ARP表項,返回其索引值
if (i < 0)return (err_t)i;// 如果為不合法的索引值题禀,則更新緩存表失敗
arp_table[i].state = ETHARP_STATE_STABLE; // 否則將對應(yīng)表項狀態(tài)改為stable
arp_table[i].netif = netif;// 記錄下網(wǎng)絡(luò)接口
k = ETHARP_HWADDR_LEN;// 這一段是更新緩存表項中的MAC地址
while (k > 0) {
k--;
arp_table[i].ethaddr.addr[k] = ethaddr->addr[k];
}
arp_table[i].ctime = 0;//生存時間值置0
#if ARP_QUEUEING//該ARP表項上有未發(fā)送的隊列鞋诗,則把這些隊列發(fā)送出去
while (arp_table[i].q != NULL) {// 只要緩沖鏈表中海有數(shù)據(jù)則循環(huán)
struct pbuf *p;
struct etharp_q_entry *q = arp_table[i].q;// 記錄下緩沖鏈表表頭
arp_table[i].q = q->next;// 緩沖鏈表表頭指向下一個節(jié)點
p = q->p;// 取得記錄下的緩沖鏈表表頭指向的數(shù)據(jù)包
memp_free(MEMP_ARP_QUEUE, q);// 釋放記錄下的緩沖鏈表表頭
etharp_send_ip(netif, p, (struct eth_addr*)(netif->hwaddr), ethaddr); // 發(fā)送數(shù)據(jù)包
pbuf_free(p);// 釋放數(shù)據(jù)包緩存空間
}
#endif
return ERR_OK;
}
從源程序中可以看出,update_arp_entry的流程如下:先通過調(diào)用find_entry找到對應(yīng)ipaddr對應(yīng)的表項投剥,并設(shè)置相應(yīng)的arp表項的成員(主要是state师脂, netif, ethaddr江锨, cttime)吃警,最后如果定義了ARP_QUEUEING,并且這個arp表項上有未發(fā)送的數(shù)據(jù)包的話啄育,則把這些數(shù)據(jù)全部發(fā)送出去酌心。雖然比較啰嗦,但是還是我們還是根據(jù)不同的ipaddr經(jīng)過find_entry執(zhí)行后挑豌,來看看update_arp_entry運(yùn)行的幾種不同情況安券。
首先可以肯定的是,update_arp_entry的兩個參數(shù)ipaddr和ethaddr必是互相匹配的氓英,因為它們是從源主機(jī)發(fā)來的IP包或ARP包中解析出來的侯勉,代表了源主機(jī)的MAC地址和IP地址。find_entry利用ipaddr作為參數(shù)執(zhí)行后铝阐,返回一個ARP表項索引址貌。如果該表項處于empty狀態(tài),那么該表項現(xiàn)在一定是新創(chuàng)建的徘键,此時設(shè)置該表項為stable狀態(tài)并設(shè)置該表項其他字段值后即結(jié)束练对。如果該表項是處于pending狀態(tài),由于此時已經(jīng)有了和ipaddr匹配的MAC地址返回吹害,所以該表項也被設(shè)置為stable狀態(tài)并同時設(shè)置該表項其他字段值螟凭。如果該表項是處于stable狀態(tài),其實此時只需要將ctime的值復(fù)位即可它呀,但是LWIP為了節(jié)省代碼量螺男,它還是選擇像上面的情況一樣做相同的處理,這樣雖然有些步驟是多余的纵穿,但并不影響函數(shù)功能烟号。最后都會檢查該表項是否還有數(shù)據(jù)需要發(fā)送,如果有政恍,則將所有數(shù)據(jù)包發(fā)送出去。
現(xiàn)在是時候從宏觀上來看看到底ARP是怎么一個工作流程达传,以及它在整個LWIP協(xié)議棧當(dāng)中發(fā)揮的重要作用篙耗。關(guān)于這點迫筑,不得不借鑒網(wǎng)上某位大俠的了,不過對TA的圖做了一些小小的修改(臉紅宗弯,用畫圖工具改的脯燃,Visio表示不會用)。
該圖簡潔明了的解釋了基本所有LWIP的數(shù)據(jù)包接收與發(fā)送的全過程辕棚。我們可以看到幾個熟悉的身影:etharp_query、etharp_request邓厕、update_arp_entry。在前面已經(jīng)講過了的详恼!
ARP從功能上來說可以簡單的分成兩個部分:當(dāng)有數(shù)據(jù)包輸入時,更新arp表昧互,如果是ip包則遞交給ip層,如果是arp包敞掘,則針對不同的arp包類型做相應(yīng)的響應(yīng);當(dāng)向目的ip發(fā)送一個數(shù)據(jù)包的時候玖雁,需要通過arp實現(xiàn)ip到MAC地址的映射,必要時茄菊,需要發(fā)送廣播數(shù)據(jù)包獲得目標(biāo)機(jī)器的MAC地址。
LWIP利用netif.input指向的函數(shù)接收以太網(wǎng)數(shù)據(jù)包面殖,通常這個函數(shù)是ethernet_input竖哩。注意,這里并不是說ethernet_input直接與底層硬件交互接收數(shù)據(jù)包脊僚,而是更底層的函數(shù)接收到數(shù)據(jù)包后將數(shù)據(jù)包遞交給ethernet_input相叁,ethernet_input再對其進(jìn)行處理。
以太網(wǎng)的幀類型可以是:IP辽幌,ARP增淹,甚至可以是pppoe, wlan等乌企。這里主要分析IP和ARP兩種類型的數(shù)據(jù)包虑润。ethernet_input根據(jù)以太網(wǎng)首部的類型字段判斷收到的數(shù)據(jù)包的類型,如果是IP包加酵,則將該包遞交給etharp_ip_input拳喻,如果是ARP包哭当,則將該包遞交給etharp_arp_input。
對于ip類型的數(shù)據(jù)包冗澈,etharp_ip_input首先檢查是否開啟了ETHARP_TRUST_IP_MAC這個選項钦勘,如果開啟了就是要用這個幀中的信息和update_arp_entry函數(shù)來更新arp表(利用幀首部的源mac地址和幀數(shù)據(jù)中ip報文中的源ip地址),然后丟棄以太網(wǎng)幀首部亚亲,將IP報文通過ip_input函數(shù)遞交給ip層彻采。
對于arp類型的數(shù)據(jù)包,etharp_arp_input函數(shù)首先利用數(shù)據(jù)包頭信息更新arp表的內(nèi)容捌归,
然后再判斷該ARP數(shù)據(jù)包的類型肛响,如果是ARP請求包,則首先判斷這個包是不是給自己的陨溅,如果是給自己的终惑,則在原有包的基礎(chǔ)上重組一個ARP應(yīng)答包發(fā)送出去(注意此處并沒有重新分配一個pbuf,而是借用了原來的緩沖結(jié)構(gòu))门扇。如果不是給自己的雹有,則直接忽略。如果是ARP應(yīng)答包臼寄,主要的工作就是更新arp表霸奕,但是這一步已經(jīng)在arp包剛進(jìn)來的時候就處理了,所以這里不需要再重復(fù)做吉拳,這樣ARP包的處理也完畢质帅。
LWIP利用netif.output指向的函數(shù)發(fā)送IP數(shù)據(jù)包,通常這個函數(shù)是etharp_output留攒。注意煤惩,這里并不是說etharp_output直接與底層硬件交互發(fā)送數(shù)據(jù)包,而是將數(shù)據(jù)包做相應(yīng)的處理炼邀,最終遞交給netif.linkoutput函數(shù)來發(fā)送的魄揉。
etharp_output函數(shù)接收IP層要發(fā)送的數(shù)據(jù)包,并將數(shù)據(jù)包發(fā)送出去拭宁。由于是發(fā)送ip數(shù)據(jù)包洛退,所以函數(shù)一開始需要增加緩沖區(qū)大小,大小為以太網(wǎng)的數(shù)據(jù)首部的大小杰标。然后檢查ip地址,可以分為廣播包腔剂,多播包,單播包(單播包又分為是局域網(wǎng)內(nèi)部還是局域網(wǎng)外面)谅畅。
廣播包:判斷目的IP地址是不是為全1噪服,或者是全0(老版本中使用的)粘优,如果是廣播包則目的IP的MAC地址不需要查詢arp表雹顺,直接將MAC地址設(shè)置為全1發(fā)送即可廊遍,即MAC六個字節(jié)值為0xff,0xff没酣,0xff裕便,0xff偿衰,0xff改览,0xff。
多播包:判斷目的ip地址是不是d類地址视事,即0xexxxxxxx郑口,如果是多播的話盾鳞,mac地址也是確定的腾仅,即將MAC地址01-00-5e-00-00-00的低23位設(shè)置為IP地址的低23位。對于以上的兩種數(shù)據(jù)包鹤耍,etharp_output直接調(diào)用函數(shù)etharp_send_ip將數(shù)據(jù)包發(fā)送出去。
單播包:要比較目的IP和本地IP地址喊衫,看是否是局域網(wǎng)內(nèi)的族购,不是局域網(wǎng)內(nèi)的陵珍,則將目的IP地址設(shè)置為默認(rèn)網(wǎng)關(guān)的地址,然后再統(tǒng)一調(diào)用etharp_query函數(shù)將數(shù)據(jù)包發(fā)送出去瑟幕,注意這些數(shù)據(jù)包在這種情況下可能被連接在相關(guān)ARP表項的發(fā)送鏈表上只盹,等待發(fā)送愤兵。