前言
從前面的文章,我們知道,ARP協(xié)議的核心是ARP緩存表,而ARP協(xié)議的實(shí)質(zhì)就是對緩存表項(xiàng)(entry)的建立、更新、查詢等操作。
那么,LwIP中是是怎么實(shí)現(xiàn)ARP協(xié)議的呢?
ARP緩存表的數(shù)據(jù)結(jié)構(gòu)
LwIP使用一個(gè)arp_table數(shù)組描述ARP緩存表,數(shù)組的內(nèi)容是表項(xiàng)的內(nèi)容,每個(gè)表項(xiàng)都必須記錄一對IP地址與MAC地址的映射關(guān)系,此外還有一些基本的信息,如表項(xiàng)的狀態(tài)、生命周期(生存時(shí)間)以及對應(yīng)網(wǎng)卡的基本信息,LwIP使用一個(gè)etharp_entry結(jié)構(gòu)體對表項(xiàng)進(jìn)行描述。
而且LwIP預(yù)先定義了緩存表的大小,ARP_TABLE_SIZE默認(rèn)為10,也就是最大能存放10個(gè)表項(xiàng),由于這個(gè)表很小,LwIP對表的操作直接采用遍歷方式,遍歷每個(gè)表項(xiàng)并且更改其中的內(nèi)容。
static struct etharp_entry arp_table[ARP_TABLE_SIZE];
struct etharp_q_entry
{
struct etharp_q_entry *next;
struct pbuf *p;
};
struct etharp_entry
{
#if ARP_QUEUEING
/** 指向此ARP表項(xiàng)上掛起的數(shù)據(jù)包隊(duì)列的指針. */
struct etharp_q_entry *q;
#else /* ARP_QUEUEING */
/** 指向此ARP表項(xiàng)上的單個(gè)掛起數(shù)據(jù)包的指針. */
struct pbuf *q;
#endif
ip4_addr_t ipaddr; //記錄目標(biāo)IP地址
struct netif *netif; //對應(yīng)網(wǎng)卡信息
struct eth_addr ethaddr; //記錄與目標(biāo)IP地址對應(yīng)的MAC地址
u16_t ctime; //生存時(shí)間
u8_t state; //表項(xiàng)的狀態(tài)
};
因?yàn)锳PR協(xié)議在沒找到MAC地址的時(shí)候是不會發(fā)送數(shù)據(jù)的,因此這些數(shù)據(jù)會暫時(shí)存儲在ARP表項(xiàng)中,因此LwIP實(shí)現(xiàn)了ARP表項(xiàng)掛載數(shù)據(jù)的結(jié)構(gòu),etharp_q_entry指向的是數(shù)據(jù)包緩存隊(duì)列,etharp_q_entry是一個(gè)結(jié)構(gòu)體,LwIP為了方便管理pbuf數(shù)據(jù)包,直接再一次封裝這個(gè)結(jié)構(gòu)體,讓數(shù)據(jù)包能形成隊(duì)列的形式,其實(shí)簡單理解為數(shù)據(jù)包就行了。而q指向的就是一個(gè)pbuf數(shù)據(jù)包。
ARP表項(xiàng)的pbuf
ARP表項(xiàng)的pbuf隊(duì)列
除此之外,ARP表項(xiàng)還有很重要的信息,那就是IP地址 MAC地址,狀態(tài)、生存時(shí)間等信息。
而對于ARP表項(xiàng)的狀態(tài),LwIP還枚舉了多種不同的狀態(tài):
/** ARP states */
enum etharp_state {
ETHARP_STATE_EMPTY = 0,
ETHARP_STATE_PENDING,
ETHARP_STATE_STABLE,
ETHARP_STATE_STABLE_REREQUESTING_1,
ETHARP_STATE_STABLE_REREQUESTING_2
#if ETHARP_SUPPORT_STATIC_ENTRIES
, ETHARP_STATE_STATIC
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
};
ARP緩存表在初始化的時(shí)候,所有的表項(xiàng)都會被初始化為ETHARP_STATE_EMPTY,也就是空狀態(tài),表示這些表項(xiàng)能被使用,在需要添加表項(xiàng)的時(shí)候,LwIP內(nèi)核就會遍歷ARP緩存表,找到合適的表項(xiàng),進(jìn)行添加。如果ARP表項(xiàng)處于ETHARP_STATE_PENDING狀態(tài),表示ARP已經(jīng)發(fā)出了一個(gè)ARP請求包,但是還未收到目標(biāo)IP地址主機(jī)的應(yīng)答,處于這個(gè)狀態(tài)的緩存表項(xiàng)是有等待時(shí)間的,它通過宏定義ARP_MAXPENDING指定,默認(rèn)為5秒鐘,如果從發(fā)出ARP請求包后的5秒內(nèi)還沒收到應(yīng)答,那么該表項(xiàng)又會被刪除;而如果收到應(yīng)答后,ARP就會更新緩存表的信息,記錄目標(biāo)IP地址與目標(biāo)MAC地址的映射關(guān)系并且開始記錄表項(xiàng)的生存時(shí)間,同時(shí)該表項(xiàng)的狀態(tài)會變成ETHARP_STATE_STABLE狀態(tài)。當(dāng)要發(fā)送數(shù)據(jù)包的時(shí)候,而此時(shí)表項(xiàng)為ETHARP_STATE_PENDING狀態(tài),那么這些數(shù)據(jù)包就會暫時(shí)被掛載到表項(xiàng)的數(shù)據(jù)包緩沖隊(duì)列上,直到表項(xiàng)的狀態(tài)為ETHARP_STATE_STABLE,才進(jìn)行發(fā)送數(shù)據(jù)包。對于狀態(tài)為ETHARP_STATE_STABLE的表項(xiàng),這些表項(xiàng)代表著ARP記錄了IP地址與MAC地址的映射關(guān)系,能隨意通過IP地址進(jìn)行數(shù)據(jù)的發(fā)送,但是這些表項(xiàng)是具有生存時(shí)間的,通過宏定義ARP_MAXAGE指定,默認(rèn)為5分鐘,在這些時(shí)間,LwIP會不斷維護(hù)這些緩存表以保持緩存表的有效。當(dāng)表項(xiàng)是ETHARP_STATE_STABLE的時(shí)候又發(fā)送一個(gè)ARP請求包,那么表項(xiàng)狀態(tài)會暫時(shí)被設(shè)置為ETHARP_STATE_STABLE_REREQUESTING_1,然后被設(shè)置為ETHARP_STATE_STABLE_REREQUESTING_2狀態(tài),這些是一個(gè)過渡狀態(tài),當(dāng)收到ARP應(yīng)答后,表項(xiàng)又會被設(shè)置為ETHARP_STATE_STABLE,這樣子能保持表項(xiàng)的有效。
所以ARP緩存表是一個(gè)動態(tài)更新的過程,為什么要動態(tài)更新呢?因?yàn)?a target="_blank">以太網(wǎng)的物理性質(zhì)并不能保證數(shù)據(jù)傳輸?shù)氖强煽康?。以太網(wǎng)發(fā)送數(shù)據(jù)并不會知道對方是否已經(jīng)介紹成功,而兩臺主機(jī)的物理線路不可能一直保持有效暢通,那么如果不是動態(tài)更新的話,主機(jī)就不會知道另一臺主機(jī)是否在工作中,這樣子發(fā)出去的數(shù)據(jù)是沒有意義的。
比如兩臺主機(jī)A和B,一開始兩臺主機(jī)都是處于連接狀態(tài),能正常進(jìn)行通信,但是某個(gè)時(shí)刻主機(jī)B斷開了,但是主機(jī)A不會知道主機(jī)B是否正常運(yùn)行,因?yàn)橐蕴W(wǎng)不會提示主機(jī)B已經(jīng)斷開,那么主機(jī)A會一直按照MAC地址發(fā)送數(shù)據(jù),而此時(shí)在物理鏈路層就已經(jīng)是不通的,那么這些數(shù)據(jù)是沒有意義的,而如果ARP動態(tài)更新的話,主機(jī)A就會發(fā)出ARP請求包,如果得不到主機(jī)B的回應(yīng),則說明無法與主機(jī)B進(jìn)行通信,那么就會刪除ARP表項(xiàng),就無法進(jìn)行通信。
ARP緩存表的超時(shí)處理
ARP表項(xiàng)的生存時(shí)間是5分鐘,而ARP請求的等待時(shí)間是5秒鐘,當(dāng)這些時(shí)間到達(dá)后,就會更新ARP表項(xiàng),如果在物理鏈路層無法連通則會刪除表項(xiàng)。這就需要ARP層有一個(gè)超時(shí)處理函數(shù)對ARP進(jìn)行管理,這些操作都是根據(jù)ARP表項(xiàng)的ctime字段進(jìn)行的,它記錄著對應(yīng)表項(xiàng)的生存時(shí)間,而超時(shí)處理函數(shù)是etharp_tmr(),它是一個(gè)周期性的超時(shí)處理函數(shù),每隔1秒就調(diào)用一次,當(dāng)ctime的值大于指定的時(shí)間,就會刪除對應(yīng)的表項(xiàng)。
LwIP中實(shí)現(xiàn)的函數(shù)是:etharp_tmr(void)。
由于LwIP的ARP表是比較小的,LwIP采用直接遍歷ARP緩存表,更新ARP表的內(nèi)容,而當(dāng)表項(xiàng)的時(shí)間大于表項(xiàng)的生存時(shí)間(5分鐘),或者表項(xiàng)狀態(tài)是ETHARP_STATE_PENDING處于等待目標(biāo)主機(jī)回應(yīng)ARP請求包,并且等待的時(shí)間超過ARP_MAXPENDING(5秒),那么LwIP就認(rèn)為這些表項(xiàng)是無效了,就調(diào)用etharp_free_entry()函數(shù)刪除表項(xiàng)。
void
etharp_tmr(void)
{
int i;
LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer\\n"));
/* 遍歷ARP表,從ARP表中刪除過期的表項(xiàng) */
for (i = 0; i < ARP_TABLE_SIZE; ++i) {
u8_t state = arp_table[i].state;
if (state != ETHARP_STATE_EMPTY
#if ETHARP_SUPPORT_STATIC_ENTRIES
&& (state != ETHARP_STATE_STATIC)
#endif /* ETHARP_SUPPORT_STATIC_ENTRIES */
) {
arp_table[i].ctime++;
/* 等待表項(xiàng)穩(wěn)定或者表項(xiàng)已經(jīng)過期*/
if ((arp_table[i].ctime >= ARP_MAXAGE) ||
((arp_table[i].state == ETHARP_STATE_PENDING) &&
(arp_table[i].ctime >= ARP_MAXPENDING)))
{
LWIP_DEBUGF(ETHARP_DEBUG, ("etharp_timer: expired %s entry %d.\\n",
arp_table[i].state >= ETHARP_STATE_STABLE ? "stable" : "pending", i));
/* clean up entries that have just been expired */
etharp_free_entry(i);
}
else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_1)
{
/* 過渡階段 */
arp_table[i].state = ETHARP_STATE_STABLE_REREQUESTING_2;
}
else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING_2)
{
/* 進(jìn)入ETHARP_STATE_STABLE狀態(tài) */
arp_table[i].state = ETHARP_STATE_STABLE;
}
else if (arp_table[i].state == ETHARP_STATE_PENDING)
{
/*仍然掛起,重新發(fā)送ARP請求 */
etharp_request(arp_table[i].netif, &arp_table[i].ipaddr);
}
}
}
}
發(fā)送ARP請求包
發(fā)送ARP請求包的時(shí)候,需要填充已知的目標(biāo)IP地址、源MAC地址、源IP地址等,并且需要該ARP包進(jìn)行廣播出去,所以以太網(wǎng)首部的目標(biāo)MAC地址為FF-FF-FF-FF-FF-FF
。
LwIP先調(diào)用etharp_request()函數(shù)進(jìn)行發(fā)送ARP請求包,在etharp_request()函數(shù)中會調(diào)用etharp_request_dst()函數(shù)進(jìn)行發(fā)送,此時(shí)指定的目標(biāo)MAC地址是ethbroadcast,而在etharp_request_dst()函數(shù)中會調(diào)用etharp_raw()進(jìn)行發(fā)送ARP請求包,層層調(diào)用,并且每層的參數(shù)都是越來越多的,這樣子封裝對于上層程序來說更加好處理,在etharp_raw()函數(shù)中,會對ARP數(shù)據(jù)包進(jìn)行封裝,然后再封裝到以太網(wǎng)數(shù)據(jù)幀中,最終調(diào)用以太網(wǎng)底層發(fā)送函數(shù)進(jìn)行將以太網(wǎng)數(shù)據(jù)幀發(fā)送出去。
LwIP的實(shí)現(xiàn)函數(shù)是etharp_raw()
。
/* --------------------------------------------------------------------------------------------- */
err_t
etharp_request(struct netif *netif, const ip4_addr_t *ipaddr)
{
return etharp_request_dst(netif, ipaddr, ebroadcast);
}
/* --------------------------------------------------------------------------------------------- */
const struct eth_addr ethbroadcast = {{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}}; //FF-FF-FF-FF-FF-FF
const struct eth_addr ethzero = {{0, 0, 0, 0, 0, 0}}; //00-00-00-00-00-00
static err_t
etharp_request_dst(struct netif *netif, const ip4_addr_t *ipaddr, const struct eth_addr *hw_dst_addr)
{
return etharp_raw(netif,
(struct eth_addr *)netif->hwaddr,
hw_dst_addr,
(struct eth_addr *)netif->hwaddr,
netif_ip4_addr(netif),
ezero,
ipaddr,
ARP_REQUEST);
}
/* --------------------------------------------------------------------------------------------- */
* @param netif 用于發(fā)送ARP數(shù)據(jù)包的lwip網(wǎng)絡(luò)接口
* @param ethsrc_addr 以太網(wǎng)頭的源MAC地址
* @param ethdst_addr 以太網(wǎng)頭的目標(biāo)MAC地址
* @param hwsrc_addr ARP協(xié)議頭的源MAC地址
* @param ipsrc_addr ARP協(xié)議頭的源IP地址
* @param hwdst_addr ARP協(xié)議頭的目標(biāo)MAC地址
* @param ipdst_addr ARP協(xié)議頭的目標(biāo)IP地址
* @param opcode ARP數(shù)據(jù)包的類型
* @return ERR_OK 如果已發(fā)送ARP數(shù)據(jù)包
* 如果無法分配ARP數(shù)據(jù)包,則為ERR_MEM
static err_t
etharp_raw(struct netif *netif, const struct eth_addr *ethsrc_addr,
const struct eth_addr *ethdst_addr,
const struct eth_addr *hwsrc_addr, const ip4_addr_t *ipsrc_addr,
const struct eth_addr *hwdst_addr, const ip4_addr_t *ipdst_addr,
const u16_t opcode)
{
struct pbuf *p;
err_t result = ERR_OK;
struct etharp_hdr *hdr;
LWIP_ASSERT("netif != NULL", netif != NULL);
/* 申請ARP報(bào)文的內(nèi)存空間 */
p = pbuf_alloc(PBUF_LINK, SIZEOF_ETHARP_HDR, PBUF_RAM);
if (p == NULL) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE | LWIP_DBG_LEVEL_SERIOUS,
("etharp_raw: could not allocate pbuf for ARP request.\\n"));
ETHARP_STATS_INC(etharp.memerr);
return ERR_MEM;
}
LWIP_ASSERT("check that first pbuf can hold struct etharp_hdr",
(p->len >= SIZEOF_ETHARP_HDR));
hdr = (struct etharp_hdr *)p->payload;
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_raw: sending raw ARP packet.\\n"));
hdr->opcode = lwip_htons(opcode);
LWIP_ASSERT("netif->hwaddr_len must be the same as ETH_HWADDR_LEN for etharp!",
(netif->hwaddr_len == ETH_HWADDR_LEN));
/* 填寫源MAC地址與目標(biāo)MAC地址 */
SMEMCPY(&hdr->shwaddr, hwsrc_addr, ETH_HWADDR_LEN);
SMEMCPY(&hdr->dhwaddr, hwdst_addr, ETH_HWADDR_LEN);
/* 以太網(wǎng)首部源ip地址與目標(biāo)ip地址 */
IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->sipaddr, ipsrc_addr);
IPADDR_WORDALIGNED_COPY_FROM_IP4_ADDR_T(&hdr->dipaddr, ipdst_addr);
//填寫ARP首部硬件類型與協(xié)議類型
hdr->hwtype = PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET);
hdr->proto = PP_HTONS(ETHTYPE_IP);
/* 填寫ARP數(shù)據(jù)包硬件地址長度與協(xié)議地址長度 */
hdr->hwlen = ETH_HWADDR_LEN;
hdr->protolen = sizeof(ip4_addr_t);
/* 發(fā)送請求包 */
#if LWIP_AUTOIP
if (ip4_addr_islinklocal(ipsrc_addr))
{
ethernet_output(netif, p, ethsrc_addr, ebroadcast, ETHTYPE_ARP);
}
else
#endif /* LWIP_AUTOIP */
{
ethernet_output(netif, p, ethsrc_addr, ethdst_addr, ETHTYPE_ARP);
}
ETHARP_STATS_INC(etharp.xmit);
/* 釋放內(nèi)存 */
pbuf_free(p);
p = NULL;
return result;
}
ARP數(shù)據(jù)包處理
以太網(wǎng)是有自己獨(dú)立的尋址方式(MAC地址),而對于TCP/IP的上層協(xié)議(如TCP協(xié)議、IP協(xié)議),它們是以IP地址作為網(wǎng)絡(luò)的標(biāo)識,如果沒有IP地址則無法進(jìn)行收發(fā)數(shù)據(jù)。當(dāng)數(shù)據(jù)通過網(wǎng)卡中接收回來的時(shí)候,LwIP內(nèi)核就需要將數(shù)據(jù)進(jìn)行分解,如果是IP數(shù)據(jù)報(bào)則遞交給IP協(xié)議去處理,如果是ARP數(shù)據(jù)包則交由ARP協(xié)議去處理。
真正讓LwIP內(nèi)核去處理接收到的數(shù)據(jù)包是ethernet_input()函數(shù)。代碼太多了,簡單截取部分代碼。
err_t
ethernet_input(struct pbuf *p, struct netif *netif)
{
struct eth_hdr *ethhdr;
u16_t type;
LWIP_ASSERT_CORE_LOCKED();
//校驗(yàn)數(shù)據(jù)長度
if (p->len <= SIZEOF_ETH_HDR) {
ETHARP_STATS_INC(etharp.proterr);
ETHARP_STATS_INC(etharp.drop);
MIB2_STATS_NETIF_INC(netif, ifinerrors);
goto free_and_return;
}
if (p->if_idx == NETIF_NO_INDEX) {
p->if_idx = netif_get_index(netif);
}
/* ethhdr指針指向以太網(wǎng)幀頭部,并且強(qiáng)制轉(zhuǎn)換成eth_hdr結(jié)構(gòu) */
ethhdr = (struct eth_hdr *)p->payload;
//獲取類型
type = ethhdr->type;
if (ethhdr->dest.addr[0] & 1)
{
/* 這可能是多播或廣播數(shù)據(jù)包,如果目標(biāo)IP地址的第一個(gè)字節(jié)的bit0是1,
那么有可能是多播或者是廣播數(shù)據(jù)包,所以,還需要進(jìn)行判斷,
如果是多播的,就將pbuf標(biāo)記為鏈路層多播。 */
if (ethhdr->dest.addr[0] == LL_IP4_MULTICAST_ADDR_0) {
if ((ethhdr->dest.addr[1] == LL_IP4_MULTICAST_ADDR_1) &&
(ethhdr->dest.addr[2] == LL_IP4_MULTICAST_ADDR_2))
{
/* 將pbuf標(biāo)記為鏈路層多播 */
p->flags |= PBUF_FLAG_LLMCAST;
}
}
else if (eth_addr_cmp(ehdr->dest, ebroadcast))
{
/* 將pbuf標(biāo)記為鏈路層廣播 */
p->flags |= PBUF_FLAG_LLBCAST;
}
}
switch (type) {
/* 如果是IP數(shù)據(jù)報(bào) */
case PP_HTONS(ETHTYPE_IP):
if (!(netif->flags & NETIF_FLAG_ETHARP)) {
goto free_and_return;
}
/* 去掉太網(wǎng)首部 */
if (pbuf_remove_header(p, next_hdr_offset))
{
goto free_and_return;
}
else
{
/* 遞交到IP層處理 */
ip4_input(p, netif);
}
break;
//對于是ARP包
case PP_HTONS(ETHTYPE_ARP):
if (!(netif->flags & NETIF_FLAG_ETHARP))
{
goto free_and_return;
}
/* 去掉太網(wǎng)首部 */
if (pbuf_remove_header(p, next_hdr_offset))
{
ETHARP_STATS_INC(etharp.lenerr);
ETHARP_STATS_INC(etharp.drop);
goto free_and_return;
}
else
{
/* 傳遞到ARP協(xié)議處理 */
etharp_input(p, netif);
}
break;
default:
ETHARP_STATS_INC(etharp.proterr);
ETHARP_STATS_INC(etharp.drop);
MIB2_STATS_NETIF_INC(netif, ifinunknownprotos);
goto free_and_return;
}
return ERR_OK;
free_and_return:
pbuf_free(p);
return ERR_OK;
}
ARP數(shù)據(jù)包的處理
重點(diǎn)來了,我們主要是講解對收到的ARP數(shù)據(jù)包處理
ARP數(shù)據(jù)包的處理函數(shù)為etharp_input(),在這里它完成兩個(gè)任務(wù):
如果收到的是ARP應(yīng)答包,說明本機(jī)之前發(fā)出的ARP請求包有了回應(yīng),就根據(jù)應(yīng)答包更新自身的ARP緩存表;
如果收到的是ARP請求包,如果包中的目標(biāo)IP地址與主機(jī)IP地址匹配,除了記錄原主機(jī)的IP與MAC地址,更新自身的ARP表外,還要向源主機(jī)發(fā)送一個(gè)ARP應(yīng)答包。但是如果如果包中目標(biāo)IP地址與主機(jī)IP地址不匹配,則盡可能記錄源主機(jī)的IP與MAC地址,更新自身的ARP表,并丟棄該請求包,為什么說是盡可能呢,因?yàn)橹鳈C(jī)的ARP緩存表是有限的,不可能記錄太多的ARP表項(xiàng),所以在有空閑的表項(xiàng)時(shí)才記錄,如果沒有空閑的表項(xiàng),ARP覺得它自己已經(jīng)盡力了,也記不住那么多表項(xiàng)。
void
etharp_input(struct pbuf *p, struct netif *netif)
{
struct etharp_hdr *hdr;
/* these are aligned properly, whereas the ARP header fields might not be */
ip4_addr_t sipaddr, dipaddr;
u8_t for_us;
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("netif != NULL", (netif != NULL), return;);
hdr = (struct etharp_hdr *)p->payload;
/* 判斷ARP包的合法性,判斷ARP包的合法性,已經(jīng)類型是否為以太網(wǎng)、硬件地址長度是否為ETH_HWADDR_LEN、
協(xié)議地址長度是否為sizeof(ip4_addr_t)以及協(xié)議是否為ARP協(xié)議,如果都滿足則表示ARP包合法。 */
if ((hdr->hwtype != PP_HTONS(LWIP_IANA_HWTYPE_ETHERNET)) ||
(hdr->hwlen != ETH_HWADDR_LEN) ||
(hdr->protolen != sizeof(ip4_addr_t)) ||
(hdr->proto != PP_HTONS(ETHTYPE_IP))) {
ETHARP_STATS_INC(etharp.proterr);
ETHARP_STATS_INC(etharp.drop);
pbuf_free(p);
return;
}
ETHARP_STATS_INC(etharp.recv);
//拷貝源IP地址與目標(biāo)IP地址
IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&sipaddr, &hdr->sipaddr);
IPADDR_WORDALIGNED_COPY_TO_IP4_ADDR_T(&dipaddr, &hdr->dipaddr);
/* 看看主機(jī)網(wǎng)卡是否配置了IP地址 */
if (ip4_addr_isany_val(*netif_ip4_addr(netif))) {
for_us = 0;
}
else
{
/* 判斷ARP數(shù)據(jù)包的目標(biāo)IP地址與主機(jī)IP地址是否一樣 */
for_us = (u8_t)ip4_addr_cmp(&dipaddr, netif_ip4_addr(netif));
}
/* 更新ARP緩存表項(xiàng) */
etharp_update_arp_entry(netif, &sipaddr, &(hdr->shwaddr),
for_us ? ETHARP_FLAG_TRY_HARD : ETHARP_FLAG_FIND_ONLY);
/* 更新完畢,根據(jù)包的類型處理 */
switch (hdr->opcode)
{
/* ARP請求包 */
case PP_HTONS(ARP_REQUEST):
if (for_us) {
/* 是請求自己的,那就要做出應(yīng)答 */
etharp_raw(netif,
(struct eth_addr *)netif->hwaddr, &hdr->shwaddr,
(struct eth_addr *)netif->hwaddr, netif_ip4_addr(netif),
&hdr->shwaddr, &sipaddr,
ARP_REPLY);
}
/* 不是給自己的,如果不是給自己的,原因有兩種,一種是網(wǎng)卡自身尚未配置IP地址,這樣子就只打印相關(guān)調(diào)試信息。
另一種是ARP包中的目標(biāo)IP地址與主機(jī)IP地址不符合,也不用做出回應(yīng),直接丟棄即可,并輸出相關(guān)調(diào)試信息*/
else if (ip4_addr_isany_val(*netif_ip4_addr(netif)))
{
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: we are unconfigured, ARP request ignored.\\n"));
}
else
{
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_input: ARP request was not for us.\\n"));
}
break;
/* 對于ARP應(yīng)答包 不用處理,前面已經(jīng)更新ARP表項(xiàng)了*/
case PP_HTONS(ARP_REPLY):
break;
default:
ETHARP_STATS_INC(etharp.err);
break;
}
pbuf_free(p);
}
更新ARP表項(xiàng)
etharp_update_arp_entry()函數(shù)是用于更新ARP緩存表的,它會在收到一個(gè)ARP數(shù)據(jù)包的時(shí)候被調(diào)用,它會先查找一個(gè)ARP表項(xiàng),如果沒有找到這個(gè)ARP表項(xiàng)的記錄,就會去新建一個(gè)ARP表項(xiàng),然后重置ARP表項(xiàng)的參數(shù)(狀態(tài)、網(wǎng)卡。IP地址與對應(yīng)的MAC地址以及生存時(shí)間等),然后檢測ARP表項(xiàng)中是否掛載數(shù)據(jù)包,如果有就將這些數(shù)據(jù)包發(fā)送出去。
表項(xiàng)的更新方式,動態(tài)表項(xiàng)有兩種方式,分別為ETHARP_FLAG_TRY_HARD和ETHARP_FLAG_FIND_ONLY。前者表示無論如何都要創(chuàng)建一個(gè)表項(xiàng),如果ARP緩存表中沒有空間了,那就需要回收較老的表項(xiàng),將他們刪除,然后建立新的表項(xiàng)。而如果是后者,就讓內(nèi)核盡量更新表項(xiàng),如果ARP緩存表中沒有空間了,那么也無能為力,實(shí)在是添加不了新的表項(xiàng)。
static err_t
etharp_update_arp_entry(struct netif *netif, const ip4_addr_t *ipaddr, struct eth_addr *ethaddr, u8_t flags)
{
s16_t i;
if (ip4_addr_isany(ipaddr) ||
ip4_addr_isbroadcast(ipaddr, netif) ||
ip4_addr_ismulticast(ipaddr)) {
return ERR_ARG;
}
/* 查找或者創(chuàng)建ARP表項(xiàng),并且返回索引值 */
i = etharp_find_entry(ipaddr, flags, netif);
/* 如果索引值不合法,更新ARP表項(xiàng)失敗 */
if (i < 0) {
return (err_t)i;
}
/* 設(shè)置表項(xiàng)狀態(tài)為ETHARP_STATE_STABLE */
arp_table[i].state = ETHARP_STATE_STABLE;
/* 記錄網(wǎng)卡 */
arp_table[i].netif = netif;
/* 插入ARP索引樹 */
mib2_add_arp_entry(netif, &arp_table[i].ipaddr);
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_update_arp_entry: updating stable entry %"S16_F"\\n", i));
/* 更新緩存表中的MAC地址 */
SMEMCPY(&arp_table[i].ethaddr, ethaddr, ETH_HWADDR_LEN);
/* 重置生存時(shí)間 */
arp_table[i].ctime = 0;
/* 如果表項(xiàng)上與未發(fā)送的數(shù)據(jù)包,那就將這些數(shù)據(jù)包發(fā)送出去 */
#if ARP_QUEUEING //使用隊(duì)列方式
while (arp_table[i].q != NULL)
{
struct pbuf *p;
/* 定義q指向ARP表項(xiàng)中的數(shù)據(jù)包緩存隊(duì)列 */
struct etharp_q_entry *q = arp_table[i].q;
/* 指向下一個(gè)數(shù)據(jù)包節(jié)點(diǎn) */
arp_table[i].q = q->next;
/* 獲取pbuf數(shù)據(jù)包 */
p = q->p;
/* 釋放MEMP_ARP_QUEUE類型的內(nèi)存塊 */
memp_free(MEMP_ARP_QUEUE, q);
#else
if (arp_table[i].q != NULL) {
struct pbuf *p = arp_table[i].q;
arp_table[i].q = NULL;
#endif
/* 發(fā)送緩存隊(duì)列的數(shù)據(jù)包 */
ethernet_output(netif, p, (struct eth_addr *)(netif->hwaddr), ethaddr, ETHTYPE_IP);
/* free the queued IP packet */
pbuf_free(p);
}
return ERR_OK;
}
ARP數(shù)據(jù)包處理流程
ARP數(shù)據(jù)包發(fā)送
我們知道一個(gè)數(shù)據(jù)包從底層傳遞進(jìn)來的流程是怎么樣的,如果是ARP數(shù)據(jù)包就會給ARP去處理,如果是IP數(shù)據(jù)報(bào)就使用ip4_input()函數(shù)傳遞到上層,這些處理在后面的章節(jié)講解。那么如果上層協(xié)議想要發(fā)送數(shù)據(jù),也肯定需要經(jīng)過ARP協(xié)議將IP地址映射為MAC地址才能完成發(fā)送操作,IP數(shù)據(jù)報(bào)通過ip4_output()函數(shù)將上層數(shù)據(jù)包傳遞到ARP協(xié)議處理,關(guān)于IP協(xié)議是怎么樣傳遞的我們暫且不說,那么ARP通過etharp_output()函數(shù)接收到IP數(shù)據(jù)報(bào)后,就會進(jìn)行發(fā)送,ARP會先從數(shù)據(jù)包中進(jìn)行分析,看看這個(gè)IP數(shù)據(jù)報(bào)是單播數(shù)據(jù)包還是多播或者是廣播數(shù)據(jù)包,然后進(jìn)行不同的處理:
對于多播或者是廣播數(shù)據(jù)包,這種處理就很簡單,直接將數(shù)據(jù)包丟給網(wǎng)卡就行了(調(diào)用ethernet_output()函數(shù))。
對于單播包的處理稍微麻煩一點(diǎn),ARP協(xié)議需要根據(jù)IP地址找到對應(yīng)的MAC地址,然后才能正確發(fā)送,如果找不到MAC地址的話,還要延遲發(fā)送數(shù)據(jù)包,ARP協(xié)議首先會創(chuàng)建一個(gè)ARP表項(xiàng),然后將數(shù)據(jù)包掛到ARP表項(xiàng)對應(yīng)的緩存隊(duì)列上,與此同時(shí)會發(fā)出一個(gè)ARP請求包,等待目標(biāo)主機(jī)的回應(yīng)后再發(fā)送IP數(shù)據(jù)報(bào)。
此處需要注意的是,對于PBUFF_ERF、PBUF_POOL、PBUF_RAM類型的數(shù)據(jù)包是不允許直接掛到ARP表項(xiàng)對應(yīng)的緩存隊(duì)列上的,因?yàn)榇藭r(shí)內(nèi)核需要等待目標(biāo)主機(jī)的ARP應(yīng)答,而這段時(shí)間里,這些數(shù)據(jù)有可能會被上層改動,這是不允許的,所以LwIP需要將這些pbuf數(shù)據(jù)包拷貝到新的空間,等待發(fā)送。
etharp_output()函數(shù)被IP層的ip4_output()函數(shù)調(diào)用,IP層傳遞一個(gè)數(shù)據(jù)包到ARP中,etharp_output()會根據(jù)數(shù)據(jù)包的目標(biāo)IP地址選擇不同的處理。
err_t
etharp_output(struct netif *netif, struct pbuf *q, const ip4_addr_t *ipaddr)
{
const struct eth_addr *dest;
struct eth_addr mcastaddr;
const ip4_addr_t *dst_addr = ipaddr;
if (ip4_addr_isbroadcast(ipaddr, netif))
{
/* 如果是廣播數(shù)據(jù)包,目標(biāo)MAC地址設(shè)置為FF-FF-FF-FF-FF-FF-FF */
dest = (const struct eth_addr *)ebroadcast;
}
else if (ip4_addr_ismulticast(ipaddr))
{
/* 如果是多播數(shù)據(jù)包,目標(biāo)MAC地址設(shè)置為多播地址:01-00-5E-XX-XX-XX */
mcastaddr.addr[0] = LL_IP4_MULTICAST_ADDR_0;
mcastaddr.addr[1] = LL_IP4_MULTICAST_ADDR_1;
mcastaddr.addr[2] = LL_IP4_MULTICAST_ADDR_2;
mcastaddr.addr[3] = ip4_addr2(ipaddr) & 0x7f;
mcastaddr.addr[4] = ip4_addr3(ipaddr);
mcastaddr.addr[5] = ip4_addr4(ipaddr);
/* destination Ethernet address is multicast */
dest = &mcastaddr;
}
else
{
/* 如果是單播目標(biāo)地IP地址 */
netif_addr_idx_t i;
/* 判斷目標(biāo)IP地址是否與主機(jī)處于同一子網(wǎng)上,
如果不是,則修改IP地址,發(fā)向網(wǎng)關(guān),請求網(wǎng)關(guān)轉(zhuǎn)發(fā),
則需要修改IP地址,IP地址為網(wǎng)關(guān)的IP地址,目的是為了讓網(wǎng)關(guān)進(jìn)行轉(zhuǎn)發(fā)。*/
if (!ip4_addr_netcmp(ipaddr, netif_ip4_addr(netif), netif_ip4_netmask(netif)) &&
!ip4_addr_islinklocal(ipaddr))
{
#if LWIP_AUTOIP
struct ip_hdr *iphdr = LWIP_ALIGNMENT_CAST(struct ip_hdr *, q->payload);
if (!ip4_addr_islinklocal(&iphdr->src))
#endif
{
#ifdef LWIP_HOOK_ETHARP_GET_GW
dst_addr = LWIP_HOOK_ETHARP_GET_GW(netif, ipaddr);
if (dst_addr == NULL)
#endif
{
/* 判斷一下網(wǎng)關(guān)地址是否有效 */
if (!ip4_addr_isany_val(*netif_ip4_gw(netif)))
{
/* 發(fā)送到默認(rèn)網(wǎng)關(guān),讓網(wǎng)關(guān)進(jìn)行轉(zhuǎn)發(fā) */
dst_addr = netif_ip4_gw(netif);
}
else
{
/* 沒有默認(rèn)網(wǎng)關(guān)可用,返回錯(cuò)誤 */
return ERR_RTE;
}
}
}
}
/* 遍歷ARP緩存表 */
for (i = 0; i < ARP_TABLE_SIZE; i++)
{
if ((arp_table[i].state >= ETHARP_STATE_STABLE) &&
#if ETHARP_TABLE_MATCH_NETIF
(arp_table[i].netif == netif) &&
#endif
(ip4_addr_cmp(dst_addr, &arp_table[i].ipaddr)))
{
/* 如果找到目標(biāo)IP地址對應(yīng)的表項(xiàng),直接發(fā)送 */
ETHARP_SET_ADDRHINT(netif, i);
return etharp_output_to_arp_index(netif, q, i);
}
}
/* 如果沒有找到與目標(biāo)IP地址對應(yīng)的ARP表項(xiàng) */
return etharp_query(netif, dst_addr, q);
}
/* 而對于多播、廣播數(shù)據(jù)包,直接能得到對應(yīng)的MAC地址,可以進(jìn)行發(fā)送*/
return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), dest, ETHTYPE_IP);
}
在上一個(gè)函數(shù)中,會調(diào)用etharp_output_to_arp_index()這個(gè)函數(shù),因?yàn)槭茿RP找到了IP地址與MAC地址對應(yīng)的表項(xiàng),從而能直接進(jìn)行發(fā)送,除此之外,ARP還需要更新ARP表項(xiàng),我們知道,LwIP中的ARP表項(xiàng)生存時(shí)間是5分鐘(300秒),那么在APP表項(xiàng)的生存時(shí)間即將到來的時(shí)候,ARP需要更新表項(xiàng),為什么要在發(fā)送數(shù)據(jù)的時(shí)候更新呢?因?yàn)槿绻话l(fā)送數(shù)據(jù),那就沒必要更新ARP表項(xiàng),這樣子表項(xiàng)在生存時(shí)間到來的時(shí)候就會被系統(tǒng)刪除,回收ARP表項(xiàng)空間,而一直使用的ARP表項(xiàng)需要是誰更新,更新的方式也有兩種:
如果ARP表項(xiàng)還差15秒就過期了,LwIP會通過廣播的方式發(fā)送一個(gè)ARP請求包,試圖得到主機(jī)的回應(yīng)。
而如果ARP表項(xiàng)還差30秒就過期了,那么LwIP會通過單播的方式向目標(biāo)主機(jī)發(fā)送一個(gè)請求包并試圖得到回應(yīng)。
在這種情況下發(fā)送ARP請求包的時(shí)候,表項(xiàng)的狀態(tài)會由ETHARP_STATE_STABLE變成ETHARP_STATE_STABLE_REREQUESTING_1,如果目標(biāo)主機(jī)回應(yīng)了,那就更新ARP緩存表中的表項(xiàng)。
當(dāng)然,如果還沒那么快到期的話,那就直接調(diào)用ethernet_output()函數(shù)將數(shù)據(jù)包傳遞給網(wǎng)卡進(jìn)行發(fā)送。
#define ARP_MAXAGE 300
/* 即將到期的時(shí)間 */
#define ARP_AGE_REREQUEST_USED_UNICAST (ARP_MAXAGE - 30)
#define ARP_AGE_REREQUEST_USED_BROADCAST (ARP_MAXAGE - 15)
static err_t
etharp_output_to_arp_index(struct netif *netif, struct pbuf *q, netif_addr_idx_t arp_idx)
{
/* 如果arp表項(xiàng)即將過期:LwIP會發(fā)送一個(gè)ARP請求包,但只有當(dāng)它的狀態(tài)是ETHARP_STATE_STABLE才能請求*/
if (arp_table[arp_idx].state == ETHARP_STATE_STABLE)
{
/* 還差15秒到期 */
if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_BROADCAST)
{
/* 使用廣播方式發(fā)出請求包 */
if (etharp_request(netif, &arp_table[arp_idx].ipaddr) == ERR_OK)
{
arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
}
}
/* 還差30秒到期 */
else if (arp_table[arp_idx].ctime >= ARP_AGE_REREQUEST_USED_UNICAST)
{
/* 發(fā)出單播請求(持續(xù)15秒),以防止不必要的廣播 */
if (etharp_request_dst(netif, &arp_table[arp_idx].ipaddr, &arp_table[arp_idx].ethaddr) == ERR_OK)
{
arp_table[arp_idx].state = ETHARP_STATE_STABLE_REREQUESTING_1;
}
}
}
return ethernet_output(netif, q, (struct eth_addr *)(netif->hwaddr), &arp_table[arp_idx].ethaddr, ETHTYPE_IP);
}
而如果在ARP緩存表中沒有找到目標(biāo)IP地址對應(yīng)的表項(xiàng),LwIP就會調(diào)用etharp_query()函數(shù),那么ARP協(xié)議就會創(chuàng)建一個(gè)表項(xiàng),這也是ARP協(xié)議的核心處理,對于剛創(chuàng)建的表項(xiàng),它在初始化網(wǎng)卡信息后會被設(shè)置為ETHARP_STATE_PENDING狀態(tài),與此同時(shí)一個(gè)ARP請求包將被廣播出去,這個(gè)時(shí)候的表項(xiàng)是無法發(fā)送數(shù)據(jù)的,只有等待到目標(biāo)主機(jī)回應(yīng)了一個(gè)ARP應(yīng)答包才能發(fā)送數(shù)據(jù),那么這些數(shù)據(jù)在這段時(shí)間中將被掛到表項(xiàng)的等待隊(duì)列上,在ARP表項(xiàng)處于ETHARP_STATE_STABLE狀態(tài)完成數(shù)據(jù)的發(fā)送。
函數(shù)的處理邏輯是很清晰的,首先調(diào)用etharp_find_entry()函數(shù)在ARP緩存表中查找表項(xiàng),如果沒有找到就嘗試創(chuàng)建表項(xiàng)并且返回表項(xiàng)的索引,當(dāng)然ARP緩存表中可能存在表項(xiàng),可能為新創(chuàng)建的表項(xiàng)(ETHARP_STATE_EMPTY),也可能為ETHARP_STATE_PENDING或者ETHARP_STATE_STABLE狀態(tài)。如果是新創(chuàng)建的表項(xiàng),那么表項(xiàng)肯定沒有其他信息,LwIP就會初始化一些信息,如網(wǎng)卡,然后就將表項(xiàng)設(shè)置為ETHARP_STATE_PENDING狀態(tài)。
掛載的這些數(shù)據(jù)在等待到目標(biāo)主機(jī)產(chǎn)生ARP應(yīng)答的時(shí)候會發(fā)送出去,此時(shí)的發(fā)送就是延時(shí)了,所以在沒有ARP表項(xiàng)的時(shí)候,發(fā)送數(shù)據(jù)會產(chǎn)生延時(shí),在指定等待ARP應(yīng)答時(shí)間內(nèi)如果等不到目標(biāo)主機(jī)的應(yīng)答,那么這個(gè)表項(xiàng)將被系統(tǒng)回收,同時(shí)數(shù)據(jù)也無法發(fā)送出去。
err_t
etharp_query(struct netif *netif, const ip4_addr_t *ipaddr, struct pbuf *q)
{
struct eth_addr *srcaddr = (struct eth_addr *)netif->hwaddr;
err_t result = ERR_MEM;
int is_new_entry = 0;
s16_t i_err;
netif_addr_idx_t i;
/* 檢是否為單播地址 */
if (ip4_addr_isbroadcast(ipaddr, netif) ||
ip4_addr_ismulticast(ipaddr) ||
ip4_addr_isany(ipaddr)) {
return ERR_ARG;
}
/* 在ARP緩存中查找表項(xiàng),如果沒有則嘗試創(chuàng)建表項(xiàng) */
i_err = etharp_find_entry(ipaddr, ETHARP_FLAG_TRY_HARD, netif);
/* 沒有發(fā)現(xiàn)表項(xiàng)或者沒有創(chuàng)建表項(xiàng)成功 */
if (i_err < 0) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: could not create ARP entry\\n"));
if (q) {
LWIP_DEBUGF(ETHARP_DEBUG | LWIP_DBG_TRACE, ("etharp_query: packet dropped\\n"));
ETHARP_STATS_INC(etharp.memerr);
}
return (err_t)i_err;
}
LWIP_ASSERT("type overflow", (size_t)i_err < NETIF_ADDR_IDX_MAX);
//找到對應(yīng)的表項(xiàng)或者創(chuàng)建表項(xiàng)成功
i = (netif_addr_idx_t)i_err;
/* 將新表項(xiàng)標(biāo)記為待處理 */
if (arp_table[i].state == ETHARP_STATE_EMPTY)
{
is_new_entry = 1;
//設(shè)置表項(xiàng)的狀態(tài)
arp_table[i].state = ETHARP_STATE_PENDING;
/* 記錄網(wǎng)卡 */
arp_table[i].netif = netif;
}
/* 是否有新的表項(xiàng) */
if (is_new_entry || (q == NULL))
{
/* 發(fā)送ARP請求包*/
result = etharp_request(netif, ipaddr);
if (result != ERR_OK)
{
}
if (q == NULL) {
return result;
}
}
LWIP_ASSERT("q != NULL", q != NULL);
/* 表項(xiàng)狀態(tài)是否穩(wěn)定 */
if (arp_table[i].state >= ETHARP_STATE_STABLE)
{
ETHARP_SET_ADDRHINT(netif, i);
/* 發(fā)送數(shù)據(jù)包 */
result = ethernet_output(netif, q, srcaddr, &(arp_table[i].ethaddr), ETHTYPE_IP);
}
else if (arp_table[i].state == ETHARP_STATE_PENDING)
{
/* 如果表項(xiàng)是ETHARP_STATE_PENDING狀態(tài) */
/* 將給數(shù)據(jù)包'q'排隊(duì) */
struct pbuf *p;
int copy_needed = 0;
/* 如果q包含必須拷貝的pbuf,請將整個(gè)鏈復(fù)制到一個(gè)新的PBUF_RAM */
p = q;
while (p)
{
LWIP_ASSERT("no packet queues allowed!", (p->len != p->tot_len) || (p->next == 0));
if (PBUF_NEEDS_COPY(p))
{
//需要拷貝
copy_needed = 1;
break;
}
p = p->next;
}
if (copy_needed)
{
/* 將整個(gè)數(shù)據(jù)包復(fù)制到新的pbuf中 */
p = pbuf_clone(PBUF_LINK, PBUF_RAM, q);
}
else
{
/* 引用舊的pbuf就足夠了 */
p = q;
pbuf_ref(p);
}
/* packet could be taken over? */
if (p != NULL) {
#if ARP_QUEUEING /* 如果使用隊(duì)列 */
struct etharp_q_entry *new_entry;
/* 分配一個(gè)新的arp隊(duì)列表項(xiàng) */
new_entry = (struct etharp_q_entry *)memp_malloc(MEMP_ARP_QUEUE);
if (new_entry != NULL)
{
unsigned int qlen = 0;
new_entry->next = 0;
new_entry->p = p;
if (arp_table[i].q != NULL)
{
/* 隊(duì)列已經(jīng)存在,將新數(shù)據(jù)包插入隊(duì)列后面 */
struct etharp_q_entry *r;
r = arp_table[i].q;
qlen++;
while (r->next != NULL)
{
r = r->next;
qlen++;
}
r->next = new_entry;
}
else
{
/* 隊(duì)列不存在,數(shù)據(jù)包就是隊(duì)列的第一個(gè)節(jié)點(diǎn) */
arp_table[i].q = new_entry;
}
#if ARP_QUEUE_LEN
if (qlen >= ARP_QUEUE_LEN) {
struct etharp_q_entry *old;
old = arp_table[i].q;
arp_table[i].q = arp_table[i].q->next;
pbuf_free(old->p);
memp_free(MEMP_ARP_QUEUE, old);
}
#endif
result = ERR_OK;
}
else
{
/* 申請內(nèi)存失敗 */
pbuf_free(p);
result = ERR_MEM;
}
#else
/* 如果只是掛載單個(gè)數(shù)據(jù)包,那么始終只為每個(gè)ARP請求排隊(duì)一個(gè)數(shù)據(jù)包,就需要釋放先前排隊(duì)的數(shù)據(jù)包*/
if (arp_table[i].q != NULL)
{
pbuf_free(arp_table[i].q);
}
arp_table[i].q = p;
result = ERR_OK;
#endif
} else {
ETHARP_STATS_INC(etharp.memerr);
result = ERR_MEM;
}
}
return result;
}
ARP發(fā)送流程
總得來說,整個(gè)ARP的工作流程是很清晰的。
-
MAC地址
+關(guān)注
關(guān)注
1文章
51瀏覽量
11811 -
ARP
+關(guān)注
關(guān)注
0文章
50瀏覽量
14799 -
ip地址
+關(guān)注
關(guān)注
0文章
303瀏覽量
17141 -
LwIP
+關(guān)注
關(guān)注
2文章
88瀏覽量
27394
發(fā)布評論請先 登錄
相關(guān)推薦
基于DWC_ether_qos的以太網(wǎng)驅(qū)動開發(fā)-LWIP的ARP模塊介紹
![基于DWC_ether_qos的以太網(wǎng)驅(qū)動開發(fā)-<b class='flag-5'>LWIP</b>的<b class='flag-5'>ARP</b>模塊介紹](https://file1.elecfans.com/web2/M00/A4/F1/wKgaomUHqQyAReY7AACPaMWkHrQ113.jpg)
如何手動往esp32 arp列表中添加自定義的arp綁定信息?
淺談如何防治ARP病毒
請問戰(zhàn)艦LWIP移植是怎么實(shí)現(xiàn)內(nèi)存管理的?
LwIP是什么意思
掌握LwIP中ARP的實(shí)現(xiàn)原理與作用
ARP報(bào)文及其在Lwip的實(shí)現(xiàn)
STM32移植LWIP問題
ARP協(xié)議攻擊及其解決方案
地址解析協(xié)議(ARP),地址解析協(xié)議(ARP)是什么意思
TCPIP協(xié)議棧的實(shí)現(xiàn)lwip
ARP是什么意思?ARP是什么協(xié)議?ARP協(xié)議用于什么地方
arp攻擊原理_arp攻擊怎么解決
![<b class='flag-5'>arp</b>攻擊原理_<b class='flag-5'>arp</b>攻擊怎么解決](https://file.elecfans.com/web1/M00/DA/36/pIYBAF_8C6OAPCcYAACG4A1zQpQ181.jpg)
靜態(tài) ARP 表項(xiàng)的潛在問題
![靜態(tài) <b class='flag-5'>ARP</b> 表項(xiàng)的潛在問題](https://file1.elecfans.com/web2/M00/FF/FC/wKgaomanCrKAS0oDAAVkungNO-Q531.png)
利用LWIP 2.2實(shí)現(xiàn)以太網(wǎng)的DHCP功能
![利用<b class='flag-5'>LWIP</b> 2.2<b class='flag-5'>實(shí)現(xiàn)</b>以太網(wǎng)的DHCP功能](https://file1.elecfans.com/web1/M00/F5/E1/wKgaoWdFbTSAIHhMAAAP1D3-lE0157.jpg)
評論