嵌入式LwIP ARP協(xié)議1

一、ARP協(xié)議簡介  

ARP,全稱 Address Resolution Protocol,譯作地址解析協(xié)議,ARP 協(xié)議與底層網(wǎng)絡(luò)接口密切相關(guān)。TCP/IP 標(biāo)準(zhǔn)分層結(jié)構(gòu)中,把 ARP 劃分為了網(wǎng)絡(luò)層的重要組成部分。?當(dāng)一個主機(jī)上的應(yīng)用程序要向目標(biāo)主機(jī)發(fā)送數(shù)據(jù)時,它只知道目標(biāo)主機(jī)的 IP 地址,而在協(xié)議棧底層接口發(fā)送數(shù)據(jù)包時,需要將該 IP 地址轉(zhuǎn)換為目標(biāo)主機(jī)對應(yīng)的 MAC 地址,這樣才能在數(shù)據(jù)鏈路上選擇正確的通道將數(shù)據(jù)包傳送出去,在整個轉(zhuǎn)換過程中發(fā)揮關(guān)鍵作用的就是 ARP 協(xié)議了。?本次的學(xué)習(xí)內(nèi)容有:

ARP?協(xié)議的原理;

ARP?緩存表及其創(chuàng)建、維護(hù)、查詢;

ARP?報(bào)文結(jié)構(gòu)。

1.1、物理地址與網(wǎng)絡(luò)地址

  網(wǎng)卡的 48 位 MAC 地址都保存在網(wǎng)卡的內(nèi)部存儲器中,TCP/IP 協(xié)議有32bit 的 IP 地址(網(wǎng)絡(luò)地址),網(wǎng)絡(luò)層發(fā)送數(shù)據(jù)包時只知道目的主機(jī)的 IP 地址,而底層接口(如以太網(wǎng)驅(qū)動程序)必須知道對方的硬件地址才能將數(shù)據(jù)發(fā)送出去。

  為了解決地址映射的問題,ARP 協(xié)議提供了一種地址動態(tài)解析的機(jī)制,在32 bit的 IP 地址和采用不同網(wǎng)絡(luò)技術(shù)的硬件地址之間提供動態(tài)映射,為上層將底層的物理地址差異屏蔽起來,這樣上層的因特網(wǎng)協(xié)議便可以靈活的使用 IP 地址進(jìn)行通信。

1.2、ARP協(xié)議的本質(zhì)

  ARP?協(xié)議使用目標(biāo)主機(jī)的?IP?地址,查詢其對應(yīng)的?MAC?地址,保證底層鏈路上數(shù)據(jù)包通信的進(jìn)行。

  假如我們的主機(jī)(192.168.1.78)需要向開發(fā)板(192.168.1.37)發(fā)送一個 IP 數(shù)據(jù)包,當(dāng)發(fā)送數(shù)據(jù)時,主機(jī)會在自己的 ARP 緩存表中尋找是否有目標(biāo)IP地址。如果找到了,也就知道了目標(biāo) MAC 地址為(00-80-48-12-34-56),此時主機(jī)直接把目標(biāo) MAC 地址寫入以太網(wǎng)幀首部發(fā)送就可以了;如果在 ARP 緩存表中沒有找到相對應(yīng)的 IP 地址,此時比較不幸,我們的數(shù)據(jù)需要被延遲發(fā)送,隨后主機(jī)會先在網(wǎng)絡(luò)上發(fā)送一個廣播(ARP 請求,以太網(wǎng)目的地址為 FF-FF-FF-FF-FF-FF),廣播的 ARP 請求表示同一網(wǎng)段內(nèi)的所有主機(jī)將會收到這樣一條信息:“192.168.1.37 的 MAC 地址是什么?請回答”。網(wǎng)絡(luò) IP 地址為 192.168.1.37(開發(fā)板)的主機(jī)接收到這個幀后,它有義務(wù)做出這樣的回答(ARP 應(yīng)答):“192.168.1.37 的 MAC 地址是(00-80-48-12-34-56)”。 這樣,主機(jī)就知道了開發(fā)板的 MAC 地址,先前被延遲的數(shù)據(jù)包就可以發(fā)送了,此外,主機(jī)會將這個地址對保存在緩存表中以便后續(xù)數(shù)據(jù)包發(fā)送時使用。?ARP?的實(shí)質(zhì)就是對緩存表的建立、更新、查詢等操作。

二、數(shù)據(jù)結(jié)構(gòu)

  頭文件etharp.h 文件實(shí)現(xiàn)了以太網(wǎng)中 ARP 協(xié)議的全部數(shù)據(jù)結(jié)構(gòu),ARP 協(xié)議實(shí)現(xiàn)過程中有兩個重要的數(shù)據(jù)結(jié)構(gòu),即 ARP 緩存表和 ARP 報(bào)文。

2.1、ARP表

  ARP協(xié)議的實(shí)質(zhì)就是對緩存表的建立、更新、查詢等操作。ARP 緩存表由緩存表項(xiàng)(entry)組成,每個表項(xiàng)記錄了一組 IP 地址和 MAC 地址綁定信息,還包含了與數(shù)據(jù)包發(fā)送控制、緩存表項(xiàng)管理相關(guān)的狀態(tài)、控制信息。LwIP中描述緩存表項(xiàng)的數(shù)據(jù)結(jié)構(gòu)叫 etharp_entry,如下所示:

structetharp_entry

{

  struct etharp_q_entry *q?????????????? //數(shù)據(jù)包緩沖隊(duì)列指針

  struct ip_addr ipaddr????????????????? //目標(biāo) IP 地址

  struct eth_addr ethaddr??????????????? //MAC 地址

  enum etharp_state state?? ????????????? //描述該 entry 的狀態(tài)

u8_t

ctime??????????????????? ???????? //描述該 entry 的時間信息

  struct netif *netif?????????? ???????? //對應(yīng)網(wǎng)絡(luò)接口信息

}?

  描述緩沖隊(duì)列的數(shù)據(jù)結(jié)構(gòu)叫做 etharp_q_entry,該結(jié)構(gòu)的定義如下:

structetharp_q_entry

{

  structetharp_q_entry *next????? //指向下一個緩沖數(shù)據(jù)包

  struct pbuf

*p?????????????????? //指向數(shù)據(jù)包 pbuf

}?

用一個圖來看看 etharp_q_entry 結(jié)構(gòu)在緩存表數(shù)據(jù)隊(duì)列中的作用,如圖所示:

        [if !vml]

[endif]

  state 是個枚舉類型,它描述該緩存表項(xiàng)的狀態(tài),LwIP 中定義一個緩存表項(xiàng)可能有三種不同的狀態(tài),用枚舉型 etharp_state 進(jìn)行描述。

enumetharp_state

{

ETHARP_STATE_EMPTY

= 0,?????? //empty 狀態(tài)

ETHARP_STATE_PENDING,????????????????? //pending 狀態(tài)

ETHARP_STATE_STABLE?????????? //stable 狀態(tài)

}?

  編譯器為ARP表預(yù)先定義了ARP_TABLE_SIZE(10)個表項(xiàng)空間,因此ARP緩存表內(nèi)部最多只能存放ARP_TABLE_SIZE條IP 地址與MAC地址配對信息。

static struct etharp_entry

arp_table[ARP_TABLE_SIZE]? //定義 ARP 緩存表

  ETHARP_STATE_EMPTY?狀態(tài):初始化的時候?yàn)閑mpty狀態(tài)。

  ETHARP_STATE_PENDING狀態(tài):表示該表項(xiàng)處于不穩(wěn)定狀態(tài),此時該表項(xiàng)只記錄到了IP 地址,但是還未記錄到對應(yīng)的MAC地址。?很可能的情況是,LwIP 內(nèi)核已經(jīng)發(fā)出一個關(guān)于該 IP地址的 ARP 請求到數(shù)據(jù)鏈路上,但是還未收到 ARP應(yīng)答。

  ETHARP_STATE_STABLE 狀態(tài):當(dāng) ARP表項(xiàng)被更新后,它就記錄了一對完整的IP 地址和MAC地址。

  在ETHARP_STATE_PENDING 狀態(tài)下會設(shè)定超時時間(10秒),當(dāng)計(jì)數(shù)超時后,對應(yīng)的表項(xiàng)將被刪除;在ETHARP_STATE_STABLE狀態(tài)下也會設(shè)定超時時間(20分鐘),當(dāng)計(jì)數(shù)超時后,對應(yīng)的表項(xiàng)將被刪除。

  網(wǎng)絡(luò)接口結(jié)構(gòu)指針 netif,該結(jié)構(gòu)中包含了網(wǎng)絡(luò)接口的 MAC地址和IP地址等信息,在發(fā)送數(shù)據(jù)包的時候,這些信息都起著至關(guān)重要的作用。?

  ctime為每個表項(xiàng)的計(jì)數(shù)器,周期性的去調(diào)用一個etharp_tmr函數(shù),這個函數(shù)以5秒為周期被調(diào)用,在這個函數(shù)中,它會將每個ARP 緩存表項(xiàng)的 ctime 字段值加 1,當(dāng)相應(yīng)表項(xiàng)的生存時間計(jì)數(shù)值 ctime 大于系統(tǒng)規(guī)定的某個值時,系統(tǒng)將刪除對應(yīng)的表項(xiàng)。


//穩(wěn)定狀態(tài)表項(xiàng)的最大生存時間計(jì)數(shù)值:240*5s=20min

#defineARP_MAXAGE?????? 240

//PENDING狀態(tài)表項(xiàng)的最大生存時間計(jì)數(shù)值:2*5s=10s

#defineARP_MAXPENDING?? 2

void etharp_tmr(void)

{

u8_t

i;

  for (i = 0? i

< ARP_TABLE_SIZE? ++i) //對每個表項(xiàng)操作,包括空閑狀態(tài)的表項(xiàng){

arp_table[i].ctime++? //先將表項(xiàng) ctime 值加1

//如果表項(xiàng)是 stable 狀態(tài),且生存值大于 ARP_MAXAGE,

//或者是 pending 狀態(tài)且其生存值大于 ARP_MAXPENDING,則刪除表項(xiàng)

    if(((arp_table[i].state == ETHARP_STATE_STABLE) && //stable 狀態(tài)

(arp_table[i].ctime >= ARP_MAXAGE))||

((arp_table[i].state == ETHARP_STATE_PENDING) && //pending 狀態(tài)

(arp_table[i].ctime >= ARP_MAXPENDING)) )

{

      if(arp_table[i].q != NULL)?? //如果表項(xiàng)上的數(shù)據(jù)隊(duì)列中有數(shù)據(jù){

free_etharp_q(arp_table[i].q)????? //則釋放隊(duì)列中的所有數(shù)據(jù)

arp_table[i].q = NULL????????????? //隊(duì)列設(shè)置為空

}

arp_table[i].state = ETHARP_STATE_EMPTY? //將表項(xiàng)狀態(tài)改為未用

}//if

}//for

}

2.2、ARP報(bào)文

  ?ARP?請求和?ARP?應(yīng)答,它們都是被組裝在一個?ARP?數(shù)據(jù)包中發(fā)送的,一個典型的?ARP?包的組成結(jié)構(gòu)如圖所示:

[if !vml]

[endif]

  以太網(wǎng)目的地址和以太網(wǎng)源地址:分別表示以太網(wǎng)目的MAC地址和源MAC地址,目的地址全1時是特殊地址以太網(wǎng)廣播地址。在 ARP 表項(xiàng)建立前,源主機(jī)只知道目的主機(jī)的 IP 地址,并不知道其 MAC 地址,所以在數(shù)據(jù)鏈路上,源主機(jī)只有通過廣播的方式將 ARP請求數(shù)據(jù)包發(fā)送出去,同一網(wǎng)段上的所有以太網(wǎng)接口都會接收到廣播的數(shù)據(jù)包。

  楨類型:ARP-0x0806、IP-0x0800、PPPoE-0x8864。

  硬件類型:表示發(fā)送方想要知道的硬件類型。

  協(xié)議類型:表示要映射的協(xié)議地址類型,0x0800-表示要映射為IP地址 。

  硬件地址長度和協(xié)議地址長度:以太網(wǎng)ARP請求和應(yīng)答分別為6和4,代表MAC地址長度和IP地址長度。

  op:指出ARP數(shù)據(jù)包的類型,ARP請求(1),ARP應(yīng)答(2)。

  在以太網(wǎng)的數(shù)據(jù)幀頭部中和 ARP 數(shù)據(jù)包中都有發(fā)送端的以太網(wǎng)MAC 地址。對于一個 ARP 請求包來說,除接收方以太網(wǎng)地址外的所有字段都應(yīng)該被填充相應(yīng)的值。當(dāng)接收方主機(jī)收到一份給自己的 ARP 請求報(bào)文后,它就把自己的硬件地址填進(jìn)去,然后將該請求數(shù)據(jù)包的源主機(jī)信息和目的主機(jī)信息交換位置,并把操作字段 op 置為 2,最后把該新構(gòu)建的數(shù)據(jù)包發(fā)送回去,這就是 ARP 應(yīng)答。

  在?ARP?中用了一大堆的數(shù)據(jù)結(jié)構(gòu)和宏來描述上圖的結(jié)構(gòu)。

#ifndefETHARP_HWADDR_LEN

#define ETHARP_HWADDR_LEN 6 //以太網(wǎng)物理地址長度

#endif

PACK_STRUCT_BEGIN//我們移植時實(shí)現(xiàn)的結(jié)構(gòu)體封裝宏

structeth_addr

{

//定義以太網(wǎng) MAC 地址結(jié)構(gòu)體 eth_addr,禁止編譯器自對齊

PACK_STRUCT_FIELD(u8_t

addr[ETHARP_HWADDR_LEN])?

}

PACK_STRUCT_STRUCT?

PACK_STRUCT_END

PACK_STRUCT_BEGIN//定義以太網(wǎng)數(shù)據(jù)幀首部結(jié)構(gòu)體 eth_hdr,禁止編譯器自對齊

structeth_hdr

{

PACK_STRUCT_FIELD(struct eth_addr dest)? //以太網(wǎng)目的地址(6 字節(jié))

PACK_STRUCT_FIELD(struct eth_addr src)? //以太網(wǎng)源地址(6 字節(jié))

PACK_STRUCT_FIELD(u16_t

type)? //幀類型(2 字節(jié))

}

PACK_STRUCT_STRUCT?

PACK_STRUCT_END

//定義以太網(wǎng)幀頭部長度宏,其中 ETH_PAD_SIZE 已定義為 0

#defineSIZEOF_ETH_HDR (14 + ETH_PAD_SIZE)

PACK_STRUCT_BEGIN//定義 ARP 數(shù)據(jù)包結(jié)構(gòu)體 etharp_hdr,禁止編譯器自對齊

structetharp_hdr

{

PACK_STRUCT_FIELD(u16_t

hwtype)? //硬件類型(2 字節(jié))

PACK_STRUCT_FIELD(u16_t

proto)? //協(xié)議類型(2 字節(jié))

PACK_STRUCT_FIELD(u16_t

_hwlen_protolen)? //硬件+協(xié)議地址長度(2 字節(jié))

PACK_STRUCT_FIELD(u16_t

opcode)? //操作字段 op(2 字節(jié))

PACK_STRUCT_FIELD(struct eth_addr shwaddr)? //發(fā)送方 MAC 地址(6 字節(jié))

PACK_STRUCT_FIELD(struct ip_addr2 sipaddr)? //發(fā)送方 IP 地址(4 字節(jié))

PACK_STRUCT_FIELD(struct eth_addr dhwaddr)? //接收方 MAC 地址(6 字節(jié))

PACK_STRUCT_FIELD(struct ip_addr2 dipaddr)? //接收方 IP 地址(4 字節(jié))

}

PACK_STRUCT_STRUCT?

PACK_STRUCT_END

#define SIZEOF_ETHARP_HDR 28 //宏,ARP 數(shù)據(jù)包長度

//宏,包含 ARP 數(shù)據(jù)包的以太網(wǎng)幀長度

#defineSIZEOF_ETHARP_PACKET (SIZEOF_ETH_HDR +SIZEOF_ETHARP_HDR)

#define ARP_TMR_INTERVAL 5000 //定義 ARP 定時器周期為 5 秒,不同幀類型的宏定義

#defineETHTYPE_ARP 0x0806

#defineETHTYPE_IP 0x0800

//ARP 數(shù)據(jù)包中 OP 字段取值宏定義

#define ARP_REQUEST 1 //ARP 請求

#defineARP_REPLY 2?//ARP 應(yīng)答

  發(fā)送 ARP 請求數(shù)據(jù)包的函數(shù)叫 etharp_request,它通過調(diào)用 etharp_raw 函數(shù)來實(shí)現(xiàn),調(diào)用后者時,需要為它提供 ARP數(shù)據(jù)包中各個字段的值,后者直接將各個字段的值填寫到在一個 ARP 包中發(fā)送(該函數(shù)并不知道發(fā)送的是 ARP 請求還是 ARP 響應(yīng),它只管組裝并發(fā)送,所以稱之為 raw)

//函數(shù)功能:根據(jù)各個參數(shù)字段組織一個 ARP 數(shù)據(jù)包并發(fā)送

//參數(shù) netif:發(fā)送 ARP 包的網(wǎng)絡(luò)接口結(jié)構(gòu)

//參數(shù) ethsrc_addr:以太網(wǎng)幀首部中的以太網(wǎng)源地址值

//參數(shù) ethdst_addr:以太網(wǎng)幀首部中的以太網(wǎng)目的地址值

//參數(shù)hwsrc_addr:ARP 數(shù)據(jù)包中的發(fā)送方 MAC 地址

//參數(shù) ipsrc_addr:ARP 數(shù)據(jù)包中的發(fā)送方 IP 地址

//參數(shù) hwdst_addr:ARP 數(shù)據(jù)包中的接收方 MAC 地址

//參數(shù) ipdst_addr:ARP 數(shù)據(jù)包中的接收方 IP 地址

//參數(shù) opcode:ARP 數(shù)據(jù)包中的 OP 字段值,請求ARP為1,應(yīng)答ARP為2

//注:ARP 數(shù)據(jù)包中其他字段使用預(yù)定義值,例如硬件地址長度為 6,協(xié)議地址長度為 4

err_t

etharp_raw(struct netif *netif, const structeth_addr *ethsrc_addr,

const struct eth_addr *ethdst_addr, const structeth_addr *hwsrc_addr,

const struct ip_addr *ipsrc_addr, const structeth_addr *hwdst_addr,

const struct ip_addr *ipdst_addr, constu16_t opcode)

{

  struct pbuf *p? //數(shù)據(jù)包指針

err_t

result = ERR_OK? //返回結(jié)果

u8_t

k?

  struct eth_hdr *ethhdr? //以太網(wǎng)數(shù)據(jù)幀首部結(jié)構(gòu)體指針

  struct etharp_hdr *hdr? // ARP 數(shù)據(jù)包結(jié)構(gòu)體指針

//先在內(nèi)存堆中為 ARP 包分配空間,大小為包含 ARP 數(shù)據(jù)包的以太網(wǎng)幀總大小

p

= pbuf_alloc(PBUF_RAW, SIZEOF_ETHARP_PACKET, PBUF_RAM)?

  if(p == NULL)??//若分配失敗則返回內(nèi)存錯誤{

    return ERR_MEM?

}

  //到這里,內(nèi)存分配成功

ethhdr

= p->payload? // ethhdr 指向以太網(wǎng)幀首部區(qū)域

hdr

= (struct etharp_hdr *)((u8_t*)ethhdr +

SIZEOF_ETH_HDR)?// hdr 指向 ARP 首部

hdr->opcode

= htons(opcode)? //填寫 ARP 包的 OP 字段,注意大小端轉(zhuǎn)換

k

= ETHARP_HWADDR_LEN??????//循環(huán)填寫數(shù)據(jù)包中各個 MAC 地址字段

  while(k > 0)

{

k----?

hdr->shwaddr.addr[k]

= hwsrc_addr->addr[k]? //ARP 頭部的發(fā)送方 MAC 地址

hdr->dhwaddr.addr[k]

= hwdst_addr->addr[k]? //ARP 頭部的接收方 MAC 地址

ethhdr->dest.addr[k]

= ethdst_addr->addr[k]? //以太網(wǎng)幀首部中的目的地址

ethhdr->src.addr[k]

= ethsrc_addr->addr[k]? //以太網(wǎng)幀首部中的以太網(wǎng)源地址

}

hdr->sipaddr

= *(struct ip_addr2 *)ipsrc_addr? //填寫 ARP 頭部發(fā)送方 IP 地址

hdr->dipaddr

= *(struct ip_addr2 *)ipdst_addr? //填寫 ARP 頭部接收方 IP 地址

//下面填充一些固定字段的值

hdr->hwtype

= htons(HWTYPE_ETHERNET)? //ARP 頭部的硬件類型為 1,即以太網(wǎng)

hdr->proto

= htons(ETHTYPE_IP)? //ARP 頭部的協(xié)議類型為0x0800

//設(shè)置兩個長度字段

hdr->_hwlen_protolen=htons((ETHARP_HWADDR_LEN<<8)| sizeof(struct ip_addr));

ethhdr->type

= htons(ETHTYPE_ARP)? //以太網(wǎng)幀首部中的幀類型字段,ARP 包

result

= netif->linkoutput(netif, p)? //調(diào)用底層數(shù)據(jù)包發(fā)送函數(shù)

pbuf_free(p)? //釋放數(shù)據(jù)包

p

= NULL?

  return result? //返回發(fā)送結(jié)果

}

//特殊 MAC 地址的定義,以太網(wǎng)廣播地址

const struct eth_addr ethbroadcast = {{0xff,0xff,0xff,0xff,0xff,0xff}}?

//該值用于填充 ARP 請求包的接收方MAC 字段,無實(shí)際意義

const struct eth_addr ethzero = {{0,0,0,0,0,0}}?

//函數(shù)功能:發(fā)送 ARP 請求

//參數(shù) netif:發(fā)送 ARP 請求包的接口結(jié)構(gòu)

//參數(shù) ipaddr:請求具有該 IP 地址主機(jī)的 MAC

err_t

etharp_request(struct netif *netif, structip_addr *ipaddr)

{

 //該函數(shù)只是簡單的調(diào)用函數(shù)etharp_raw,為函數(shù)提供所有相關(guān)參數(shù)

 return etharp_raw(netif, (structeth_addr *)netif->hwaddr,ebroadcast,

(struct eth_addr *)netif->hwaddr,&netif->ip_addr,&ethzero,ipaddr,ARP_REQUEST)?

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容