關於LWIP網路協議在嵌入式設備使用越來越廣泛,還是要好好學習一下,之前也看過一些資料,總是學了又忘(可能實踐的太少了吧!!)。所以本文重新整理一下筆記。共同進步! (一)ARP基礎知識 (1)ARP協議的本質: ARP協議的基本功能是使用目標主機的IP地址,查詢其對應的MAC地址,來進行底層鏈 ...
關於LWIP網路協議在嵌入式設備使用越來越廣泛,還是要好好學習一下,之前也看過一些資料,總是學了又忘(可能實踐的太少了吧!!)。所以本文重新整理一下筆記。共同進步!
(一)ARP基礎知識
(1)ARP協議的本質:
ARP協議的基本功能是使用目標主機的IP地址,查詢其對應的MAC地址,來進行底層鏈路上數據包的通信工作。其中,ARP表的功能就是 記錄IP地址 與 MAC地址 的對應關係的表格。
在乙太網中,ARP數據包與IP數據包是兩個獨立的部分,他們都是封裝在乙太網中進行傳送的。ARP數據包分為兩類一個是ARP請求包,另一個是arp應答包 。
-
所謂ARP請求包:就是它是通過 廣播 的方式在乙太網中進行傳輸,然後希望能得到目標主機的相應。已知IP地址,請求MAC地址。
-
顯然,ARP應答包的功能,就是收到ARP請求包的主機,會解析請求包的IP地址與本機IP地址做比較,若符號,則返回一個APR應答包,包含了請求的IP地址與對應的MAC地址。這樣,源主機就知道目標主機的MAC地址了,並把它加入到自己的ARP表中。
(2)ARP表的建立過程:
-
階段一:當系統初始化時,ARP表為空。主機會廣播自己的 <IP,MAC>,這個數據包叫 無彙報 ARP請求包。其他主機收到後,會把這個數據加入到自己的ARP表中。
-
階段二:當主機要發送一個IP數據包時,要先檢查自己的ARP表有沒有目標主機的MAC地址?要有,好,直接發送。要沒有,就要廣播一個ARP請求包,然後其他主機接收後,若和自己匹配,則返回一個ARP應答包。源主機就得到了這個IP對應的MAC地址。 如果該表項的緩衝隊列上有未發送的數據,相應的數據會被髮送出去(後面結合代碼詳解)
-
階段三:由於網路硬體狀態可能隨時改變,所以ARP還需要採用一定的定時機制來保證 緩存表中地址的 有效性。要有定時機制。
(二)lwip中關於ARP的數據結構
etharp.c/h 文件中 實現了ARP協議的全部數據結構和函數定義。主要是ARP緩存表和ARP報文。
(1)ARP表
-
ARP表是由緩存表項(entry)組成。LWIP只描述緩存表項的數據結構叫做 etharp_entry 。單個緩存表項的結構如下:
struct etharp_entry { struct etharp_q_entry *q; //**數據包緩衝隊列指針**; struct pbuf *q; // ip_addr_t ipaddr; //目標IP地址 struct netif *netif; //對應的網路介面信息 struct eth_addr ethaddr; //目標MAC地址 u8_t state; //該entry 表項的狀態 u8_t ctime; //該entry的時間信息 };
- 第一個成員:"*q" 指向緩存表項的數據包緩存隊列。因為當主機發送一個IP數據包時候,發現緩存表中並沒有對應的MAC地址,那該怎麼辦呢?於是在得到對應的MAC地址之前,主機會新建立一個緩存表項,然後把要發送的數據 掛在這個緩存隊列指針上。當接收到ARP應答包後,再發送出去。
struct etharp_q_entry 結構是一個鏈表,包含一個*next 指針和一個 指向pbuf 數據包的指針。系統為 etharp_q_entry 結構開闢了一些 MEMP_APR_QUEUE類型的記憶體池。
-
state有四種狀態,分別為empty 狀態、Pending狀態、stable狀態、stable狀態且發送了一個ARP請求。
初始時,是以數組形式定義了10條ARP表項。這都是空的,沒有記錄任何信息。
enum etharp_state { ETHARP_STATE_EMPTY = 0, //empty狀態 ETHARP_STATE_PENDING, ETHARP_STATE_STABLE, ETHARP_STATE_STABLE_REREQUESTING };
-
pending狀態:不穩定狀態,此時只是找到了IP地址,正在尋找MAC地址;
-
stable狀態:當Pending狀態的表項接收到ARP應答後,就會變成stable穩定狀態
-
stabl_rerequesting狀態:系統定時更新ARP表項,當時間到了之後,會向目標主機發送一個ARP請求,來驗證表項的有效性,在驗證期間就會變成 stable_rerequesting狀態。
-
-
ctime 成員:用來計時,系統會刪除到時的 ARP表項。內核每5秒一次調用eth_tmr()函數,他會為每個 ARP表項 的ctime 值加1,當改值大於系統規定的值時,就會產生相應的動作。
void etharp_tmr(void) { u8_t i; for (i = 0; i < ARP_TABLE_SIZE; ++i) { u8_t state = arp_table[i].state; if (state != ETHARP_STATE_EMPTY) //表項不為空,說明被使用。 { arp_table[i].ctime++; if ((arp_table[i].ctime >= ARP_MAXAGE) || //表項大於生存時間20分鐘 ((arp_table[i].state == ETHARP_STATE_PENDING) && //達到pending 最大時間10s (arp_table[i].ctime >= ARP_MAXPENDING))) { etharp_free_entry(i); //刪除表項 } else if (arp_table[i].state == ETHARP_STATE_STABLE_REREQUESTING) { /* Reset state to stable, so that the next transmitted packet will re-send an ARP request. */ arp_table[i].state = ETHARP_STATE_STABLE; } } } }
(2)ARP報文
1. 前面提到的ARP請求和應答是組裝在一個ARP數據包中發送的。如下圖所示是一個APR包的組成;
乙太網目的地址(MAC) 乙太網源地址(MAC) 幀類型 硬體協議 協議類型 硬體地址長度 協議地址長度 OP 發送方乙太網地址 發送方IP 接收方乙太網地址 接收方IP 6位元組 6 2 2 2 1 1 2 6 4 6 4 前面 14個位元組是乙太網首部,後面28個位元組是ARP數據包。
-
幀類型:對於ARP包是0X806,對於IP包是0X0800。
-
硬體協議:發送方想要知道的硬體介面類型,對於乙太網是 1
-
協議類型:表示要映射的協議地址類型,為0X0800,代表映射為IP地址
-
操作欄位op:表示數據包類型。ARP請求包為 1,ARP應答包為2.
-
後面欄位含義較為明顯,不再贅述。
乙太網首部用結構eth_hdr表示
struct eth_hdr { PACK_STRUCT_FIELD(struct eth_addr dest); //乙太網目的地址,6位元組 PACK_STRUCT_FIELD(struct eth_addr src); //乙太網源地址,6位元組。 PACK_STRUCT_FIELD(u16_t type); //幀類型 } PACK_STRUCT_STRUCT;
(PACK_STRUCT_FIELD巨集定義來禁止編譯器自動對齊)
ARP數據包部分用結構etharp_hdr 表示:
struct etharp_hdr { PACK_STRUCT_FIELD(u16_t hwtype); PACK_STRUCT_FIELD(u16_t proto); PACK_STRUCT_FIELD(u8_t hwlen); PACK_STRUCT_FIELD(u8_t protolen); PACK_STRUCT_FIELD(u16_t opcode); PACK_STRUCT_FIELD(struct eth_addr shwaddr); PACK_STRUCT_FIELD(struct ip_addr2 sipaddr); PACK_STRUCT_FIELD(struct eth_addr dhwaddr); PACK_STRUCT_FIELD(struct ip_addr2 dipaddr); } PACK_STRUCT_STRUCT;
-
結合源碼,來看看ARP請求包是怎麼發送出去的。ARP請求包是 調用etharp_requeset()實現。
etharp_request(struct netif *netif, ip_addr_t *ipaddr) { return etharp_raw(netif, (struct eth_addr *)netif->hwaddr, ðbroadcast, (struct eth_addr *)netif->hwaddr, &netif->ip_addr, ðzero, ipaddr, ARP_REQUEST); }
好家伙,裡面原來是調用是了etharp_raw()。那我們看看etharp_raw()具體實現。
重頭戲來了!
/** * @param netif the lwip network interface on which to send the ARP packet * @param ethsrc_addr the source MAC address for the ethernet header * @param ethdst_addr the destination MAC address for the ethernet header * @param hwsrc_addr the source MAC address for the ARP protocol header * @param ipsrc_addr the source IP address for the ARP protocol header * @param hwdst_addr the destination MAC address for the ARP protocol header * @param ipdst_addr the destination IP address for the ARP protocol header * @param opcode the type of the ARP packet * @return ERR_OK if the ARP packet has been sent * ERR_MEM if the ARP packet couldn't be allocated * any other err_t on failure */ 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 ip_addr_t *ipsrc_addr, const struct eth_addr *hwdst_addr, const ip_addr_t *ipdst_addr, const u16_t opcode) { struct pbuf *p; err_t result = ERR_OK; struct eth_hdr *ethhdr; //乙太網幀首部結構體指針 struct etharp_hdr *hdr; //ARP數據包結構體指針 /* 先在記憶體堆中,為ARP包分配空間 */ p = pbuf_alloc(PBUF_RAW, SIZEOF_ETHARP_PACKET, PBUF_RAM); //14+28個位元組 /* 若分配失敗,返回err */ if (p == NULL) { return ERR_MEM; } ethhdr = (struct eth_hdr *)p->payload; //ethhdr指向乙太網幀首部區域 hdr = (struct etharp_hdr *)((u8_t*)ethhdr + SIZEOF_ETH_HDR); //hdr 指向arp數據包首部區域 hdr->opcode = htons(opcode); //填寫op欄位。 下麵是繼續填寫數據包中欄位: /* Write the ARP MAC-Addresses */ ETHADDR16_COPY(&hdr->shwaddr, hwsrc_addr); ETHADDR16_COPY(&hdr->dhwaddr, hwdst_addr); /* Write the Ethernet MAC-Addresses */ ETHADDR16_COPY(ðhdr->src, ethsrc_addr); IPADDR2_COPY(&hdr->sipaddr, ipsrc_addr); IPADDR2_COPY(&hdr->dipaddr, ipdst_addr); hdr->hwtype = PP_HTONS(HWTYPE_ETHERNET); hdr->proto = PP_HTONS(ETHTYPE_IP); /* set hwlen and protolen */ hdr->hwlen = ETHARP_HWADDR_LEN; hdr->protolen = sizeof(ip_addr_t); ethhdr->type = PP_HTONS(ETHTYPE_ARP); //乙太網幀類型ARP包 /* 發送!!send ARP query */ result = netif->linkoutput(netif, p); /* 釋放!free ARP query packet */ pbuf_free(p); p = NULL; return result; }
到此為止,總結就是 先分配記憶體,然後填充這個記憶體中各個欄位的數據信息(保存在pbuf 中),然後再調用netif->linkoutput()底層數據包發送函數,最後再釋放掉pbuf。