ARP是Address Resolution Protocol的縮寫。中文譯做“地址解析協(xié)議”,本質(zhì)是完成網(wǎng)絡(luò)地址到物理地址的映射。從概念上講就是找到一個(gè)映射方法f,使得“物理地址 = f(網(wǎng)絡(luò)地址)”。物理地址有兩種基本類型:以太網(wǎng)類型和proNET令牌環(huán)網(wǎng)類型,網(wǎng)絡(luò)地址特指IP地址,對(duì)映射方法的要求就是高效。具體到以太網(wǎng),它使用的是動(dòng)態(tài)綁定轉(zhuǎn)換的方法。為什么不直接使用同一種地址,而要這么麻煩呢?因?yàn)門CP/IP網(wǎng)絡(luò)就是為將不同種類計(jì)算機(jī)互聯(lián)而發(fā)明的,它的體系結(jié)構(gòu)是分層的,層和層之間相互獨(dú)立,改變物理層的實(shí)現(xiàn)不會(huì)影響到網(wǎng)絡(luò)層。 32位IP地址到以太網(wǎng)48位物理地址的映射,采用動(dòng)態(tài)綁定轉(zhuǎn)換的方法會(huì)遇到許多細(xì)節(jié)問(wèn)題,例如:減少?gòu)V播,ARP包丟失,物理地址變更(更換網(wǎng)卡)、移動(dòng)(移動(dòng)設(shè)備到另一子網(wǎng))、消失(關(guān)機(jī))等。一般是設(shè)置ARP高速緩存,通過(guò)學(xué)習(xí)、老化、更新、溢出算法處理ARP映射表來(lái)解決這些問(wèn)題。其中,學(xué)習(xí)指ARP收到任何指向本節(jié)點(diǎn)IP地址的ARP/IP包,從中提取出地址對(duì),而ARP緩存中無(wú)對(duì)應(yīng)項(xiàng)時(shí),由ARP接收部分添加;老化指為每項(xiàng)設(shè)置壽命域,以便代謝掉陳舊的地址映射項(xiàng);更新指ARP提取到新的地址對(duì)時(shí),用其更新緩存里已有的對(duì)應(yīng)項(xiàng);溢出算法指當(dāng)緩存滿時(shí),采取何種方法替換舊有的地址對(duì)兒。 我找到了幾個(gè)TCP/IP源代碼,對(duì)比他們的實(shí)現(xiàn),深感差別巨大,靈活多變。有的代碼未實(shí)現(xiàn)ARP緩存,只用幾個(gè)全局變量記錄源目的IP地址和源目的MAC地址,每次通信前直接操作全局變量,這在使用51單片機(jī),進(jìn)行點(diǎn)對(duì)點(diǎn)通信時(shí)不失為一個(gè)有效的方案;而有的代碼龐大復(fù)雜,細(xì)節(jié)處理精益求精。比如實(shí)現(xiàn)了ARP高速緩存、支持多址節(jié)點(diǎn)、支持網(wǎng)管查看/動(dòng)態(tài)改變ARP相關(guān)參數(shù)、重發(fā)處理、支持IPv6等。我的看法是:ARP的本質(zhì)是地址轉(zhuǎn)換,只要抓住這個(gè)靈魂,設(shè)計(jì)的大方向就把握住了。具體實(shí)現(xiàn)過(guò)程各具特色,因人而異,沒(méi)有統(tǒng)一要求,有些功能可以不實(shí)現(xiàn),有些優(yōu)點(diǎn)不能兼得,而唯一不變的只有思想。 我參考了幾種已有的IP協(xié)議棧并結(jié)合51單片機(jī)的特點(diǎn),實(shí)現(xiàn)了自己的基于uCOS51的TCP/IP協(xié)議棧方案。它只是一種具體的實(shí)現(xiàn)范例,不同的人有不同的設(shè)計(jì)方法。我保證自己的方案可以正常使用并具有較好的完備性。 ------------------------------ |狀態(tài)|壽命ttl|IP地址 |MAC地址| 學(xué)習(xí) ------------------------------ | 0 | FF |X:X:X:X| XXXX | <--- 老化 ------------------------------ | 0 | FF |X:X:X:X| XXXX | 更新 ------------------------------ 圖1 ARP緩存表 表滿處理 如圖1所示,ARP緩存表由狀態(tài)、壽命、IP地址、MAC地址4個(gè)字段組成。狀態(tài)字段指示地址對(duì)是否有效(0-空閑 1-占用);壽命字段用于老化操作,初始存入最大值,以后由OS時(shí)間函數(shù)調(diào)用,每秒減1,直至為0清除;IP地址和MAC地址字段保存網(wǎng)絡(luò)地址和物理地址的映射。此處,沒(méi)有設(shè)計(jì)發(fā)送數(shù)據(jù)鏈表首指針和重發(fā)記數(shù)字段,我把重發(fā)操作交給上層軟件統(tǒng)一處理,這是本程序的特色。圍繞ARP緩存表,完成了4種操作:學(xué)習(xí)、老化、更新、表滿處理,詳見(jiàn)偽代碼清單。使用OS的Shell命令ls可以查看ARP表的內(nèi)容,但不支持修改,這個(gè)功能對(duì)測(cè)試很有用。(顯示內(nèi)容舉例如圖2所示) %ls ARP table: status TTL IP address MAC address ================================================= 01 78 172.18.92.86 0050BABD4C7E % 圖2 ARP緩存表顯示內(nèi)容舉例 表滿處理 | v ARP請(qǐng)求 --------- ----------- ----------> | | 學(xué)習(xí)/更新 | | <- - - - - 老化--->| ARP表 |<------------| ARP處理 | | | | | - - - - - > --------- ----------- <---------- ^ ARP應(yīng)答 |學(xué)習(xí)/更新 --------- | | | IP_in | | | --------- 圖3 ARP處理過(guò)程 0 8 16 24 31 --------------------------------------------------------------------- | 硬件類型 | 協(xié)議類型 | --------------------------------------------------------------------- |硬件地址長(zhǎng)度(HLEN)|協(xié)議長(zhǎng)度(PLEN)| 操作 | --------------------------------------------------------------------- | 發(fā)送方首部(八位組0-3) | --------------------------------------------------------------------- | 發(fā)送方首部(八位組4-5) | 發(fā)送方IP地址(八位組0-1) | --------------------------------------------------------------------- | 發(fā)送方IP地址(八位組2-3) | 目標(biāo)首部(八位組0-1) | --------------------------------------------------------------------- | 目標(biāo)首部(八位組2-5) | --------------------------------------------------------------------- | 目標(biāo)IP地址(八位組0-3) | --------------------------------------------------------------------- 圖4 ARP包結(jié)構(gòu) 如圖3,整個(gè)ARP處理過(guò)程,我主要用5個(gè)函數(shù)實(shí)現(xiàn)。ARP初始化(ARP_init)、ARP請(qǐng)求(ARP_request)、ARP應(yīng)答(ARP_answer)、ARP回應(yīng)處理(ARP_process)、IP包接收預(yù)處理(IP_in)。在實(shí)現(xiàn)網(wǎng)卡驅(qū)動(dòng)程序后,所有ARP處理操作就是填寫ARP包(ARP包結(jié)構(gòu)見(jiàn)圖4),詳見(jiàn)偽代碼清單。 ARP_init完成ARP表初始化,概括說(shuō)就是ARP表state字段清0。 ARP_request完成ARP請(qǐng)求操作。ARP協(xié)議要求程序根據(jù)子網(wǎng)掩碼判斷IP地址是否屬于同一子網(wǎng),如果在同一子網(wǎng)內(nèi),ARP請(qǐng)求目的MAC地址,否則請(qǐng)求默認(rèn)網(wǎng)關(guān)MAC地址。 ARP_answer比較簡(jiǎn)單,只要交換ARP請(qǐng)求包地址內(nèi)容,填寫自己的MAC地址和很少的改動(dòng)后發(fā)送即可。 ARP_process完成ARP回應(yīng)回來(lái)的信息處理。主要進(jìn)行ARP表的學(xué)習(xí)和更新。 IP_in完成IP包接收預(yù)處理,用于提取地址映射信息,以便主動(dòng)學(xué)習(xí)和及時(shí)更新。我的程序不會(huì)主動(dòng)學(xué)習(xí)不是發(fā)給自己IP地址的MAC地址信息,因?yàn)锳RP表在51中的容量有限,只有頻繁用到的地址對(duì)才應(yīng)該存放在里面,否則一旦出現(xiàn)“顛簸”,ARP表就失效了。 有的ARP實(shí)現(xiàn)方案采用數(shù)據(jù)驅(qū)動(dòng)方式,參數(shù)可配置,使用統(tǒng)一的程序,通過(guò)加載不同的配置數(shù)據(jù),執(zhí)行不同的操作。這樣做使程序版本統(tǒng)一,不同的應(yīng)用只要加載不同的配置數(shù)據(jù)即可,不用更換程序,有利于后期維護(hù)。但是考慮到51資源緊張和安全性,我的方案只能顯示ARP表不允許修改其內(nèi)容,用戶可發(fā)揮想象力在此處增加新功能。另外,ARP程序應(yīng)該記住上一次發(fā)過(guò)的請(qǐng)求,以避免重發(fā),但同樣考慮到資源緊張,也免了。其實(shí)無(wú)所謂,重發(fā)就重發(fā)了。表滿處理采用有損性能的加速算法,快速有效。另外,本程序不能直接用于嵌入式網(wǎng)關(guān)產(chǎn)品。 uCOS51操作系統(tǒng)本身提供了良好的內(nèi)存管理功能,我利用它設(shè)置了大中小三種緩沖區(qū)存放不同類型的數(shù)據(jù)包。內(nèi)存使用前申請(qǐng),使用后釋放,有效利用了資源。 系統(tǒng)特點(diǎn)是:1.搶占式優(yōu)先級(jí);2.消息驅(qū)動(dòng);3.串行服務(wù)器模式。 系統(tǒng)優(yōu)點(diǎn)是:1.等待時(shí)不耗費(fèi)CPU資源;2.有超時(shí)保護(hù),不會(huì)死鎖;3.思路清晰易懂。 系統(tǒng)基于中斷驅(qū)動(dòng),使用Int0做網(wǎng)卡中斷輸入口。ISR寄存器只用到4位:OVW 收溢出錯(cuò)/TXE 發(fā)被中斷錯(cuò)/PTX 發(fā)送成功/PRX 接收成功。TCP/IP協(xié)議棧做成任務(wù),脫離內(nèi)核。整體框架如圖5、6、7所示。主程序框架見(jiàn)偽代碼清單(RxSem和TxSem初始化為0) ---------- |網(wǎng)卡中斷| ---------- | V ---------- |> |發(fā)信號(hào)量| | 收完/收溢出錯(cuò) |SemPost |---->-------------- RxSemPost ---------- |> | | 發(fā)完/發(fā)被中斷錯(cuò) ---------->-------------- TxSemPost 圖5 網(wǎng)卡中斷處理程序 進(jìn)入 | ------ V | | 發(fā) ---------- | 低優(yōu)先級(jí) ------> | 等待 |<--- | |TxQPend |<--------------------- ----- | ---------- | | | | | TxQFIFO非空 | | | | V | ---<---| |---<--- | ---------- | 數(shù)據(jù)源 | | 各任務(wù)發(fā)送來(lái)的數(shù)據(jù) | | 發(fā)送包 | | | | | ---------- | ----- | | | TxQFIFO | V | | --------------------- | | | 釋放內(nèi)存 | | | |(包已存入網(wǎng)卡RAM里)| | | --------------------- | | | ----- | | V | | | | ----------- | | | | 等待 |<-- | (等效發(fā)送包被拋棄) | |TxSemPend|<----------- | | ----------- | | | | 發(fā)完/超時(shí) | | | V | | | Y ---------------- ----------- | -<---| 發(fā)送成功嗎? | |重發(fā)第n次| | |(無(wú)錯(cuò)且不超時(shí))| | n<N | | ---------------- ----------- | | N /^\ | V N | | ------------------>------ | |已發(fā)了N次嗎?|---------->-------- --------------- Y 圖6 發(fā)送流程圖 進(jìn)入 | ----- V | | 收 ----------- | 高優(yōu)先級(jí) ------------------>| 等待 |<-- | --------->|RxSemPend|<--------------- | | ----------- /|\ /|\ | | | 收到包 或 | | | | V 收錯(cuò) 或 | | | | | 超時(shí) | | | | ----------- | ---------- | | |存并清ISR| | |復(fù)位網(wǎng)卡| ----------- | ----------- | ---------- |RxSemPost| | | | /^\ /^\ ----------- | V | | | | | -------------------- | | | | | |超時(shí)且無(wú)新包且無(wú)錯(cuò)| Y| | | | | | (防死鎖) |->- | | | | -------------------- | | /|\ |(不執(zhí)行 | N | | | |RxSemPost) V | | | | ------------ Y | | | | | 收溢出錯(cuò) |--->--------- | | | | ISR之OVW | | | Y | N ------------ | ------------------ | N | |網(wǎng)卡中還有包嗎?| V | | CURR!=BNRY+1 | ------------------------ Y | ------------------ |讀出包頭,查有無(wú)邏輯錯(cuò)|--->------- | ------------------------ /|\ | N | V | ------------------------ ---------- |按包長(zhǎng)度申請(qǐng)合適的大中| |釋放內(nèi)存| |小號(hào)內(nèi)存,并存入整個(gè)包| ---------- |,再調(diào)整BNRY | /^\ /^\ ------------------------ | | | | | V | | N ---------------------------- | ---<---|是否是發(fā)給自己IP地址的包?| | ---------------------------- | | Y | V | ------------ | | 包分發(fā) | | ------------ | | | V | ---------------------------- | | | | | | V -------------------------- IP_in過(guò)濾 | | V V V | ARP ICMP(Ping) UDP TCP | | | | | | ---------------------------- | | 串行處理 | | (32bitMCU可設(shè)計(jì)成并發(fā)模式) |---------<------------- 圖7 接收流程圖 我仔細(xì)檢查了幾遍,似乎比較完備了,各種情況下均可以正常工作。在超負(fù)荷流量下,只會(huì)拋包,不會(huì)死機(jī)。當(dāng)然,由于本人接觸資料有限和個(gè)人局限性,肯定有錯(cuò)誤和疏漏之處,希望大家提出意見(jiàn)和建議。 偽代碼清單: ARP_init() //ARP緩存初始化 { for(i=0;i<ARPTabSize;i++) ARPTable[i].status=0; }
ARP_request(目的IP地址) //ARP請(qǐng)求 { //判斷IP地址是否屬于同一子網(wǎng)的任務(wù)交給上層軟件處理 //(由它決定請(qǐng)求網(wǎng)卡IP地址還是默認(rèn)網(wǎng)關(guān)IP地址), //這有利于減少代碼量。
//申請(qǐng)小號(hào)內(nèi)存 pARP=OSMemGet();
//填以太網(wǎng)幀 以太網(wǎng)協(xié)議=0x0806;//ARP協(xié)議 目的MAC地址=0xffff;//廣播地址 源MAC地址=自己的MAC地址;
//填A(yù)RP表 硬件類型=0x0001; 協(xié)議類型=0x0800; 硬件地址長(zhǎng)度=0x06; 協(xié)議長(zhǎng)度=0x04; 操作=0x0001;//請(qǐng)求 發(fā)送方首部=自己的MAC地址; 發(fā)送方IP地址=源IP地址; 目標(biāo)首部=0x0000; 目標(biāo)IP地址=目的IP地址; //填充PAD 沒(méi)有內(nèi)容處填充0;
//發(fā)送ARP包至TxQFIFO緩存 OSQSend(QID,*pARP); }
ARP_answer(*pARP) //ARP應(yīng)答 { 學(xué)習(xí)/更新ARP緩存表; //修改收到的ARP包,形成ARP應(yīng)答 //填以太網(wǎng)幀 目的MAC地址=對(duì)方(網(wǎng)卡/網(wǎng)關(guān))發(fā)來(lái)的源MAC地址; 源MAC地址=自己的MAC地址;
//填A(yù)RP表 目標(biāo)首部=發(fā)送方首部;發(fā)送方首部=自己的MAC地址; 交換發(fā)送方IP地址和目標(biāo)IP地址; 操作=0x0002;//ARP應(yīng)答
//發(fā)送ARP包至TxQFIFO緩存 OSQSend(QID,*pARP); }
ARP_process(*pARP) //ARP應(yīng)答處理 { //更新 for(i=0;i<ARPTabSize;i++){ if(ARPTab[i].status==1){ if(ARPTab[i].IPAdr==收到的ARP應(yīng)答包源IP地址){ ARPTab[i].ttl=最大壽命; ARPTab[i].IPAdr=收到的包的源IP地址; ARPTab[i].MACAdr=收到的包的源MAC地址; return; } } } //學(xué)習(xí) for(i=0;i<ARPTabSize;i++){ if(ARPTab[i].status==0){ ARPTab[i].status=1; ARPTab[i].ttl=最大壽命; ARPTab[i].IPAdr=收到的包的源IP地址; ARPTab[i].MACAdr=收到的包的源MAC地址; return; } }
//表滿處理,有損性能的快速算法 ARPTab[index].status=1; //注:index為全局變量,保存ARP緩存表項(xiàng)索引。每次處理加1取模。 ARPTab[index].ttl=最大壽命; index++; if(index>=ARPTabSize) index=0; }
IP_in(*pIP) //IP包過(guò)濾(ARP地址學(xué)習(xí)) 注:這里處理的是IP包,偽代碼與上面程序相似,但源代碼差別很大。 { //更新 for(i=0;i<ARPTabSize;i++){ if(ARPTab[i].status==1){ if(ARPTab[i].IPAdr==收到的IP包源IP地址){ ARPTab[i].ttl=最大壽命; ARPTab[i].IPAdr=收到的包的源IP地址; ARPTab[i].MACAdr=收到的包的源MAC地址; return; } } } //學(xué)習(xí) for(i=0;i<ARPTabSize;i++){ if(ARPTab[i].status==0){ ARPTab[i].status=1; ARPTab[i].ttl=最大壽命; ARPTab[i].IPAdr=收到的包的源IP地址; ARPTab[i].MACAdr=收到的包的源MAC地址; return; } }
//表滿處理,有損性能的快速算法 ARPTab[index].status=1; //注:index為全局變量,保存ARP緩存表項(xiàng)索引。每次處理加1取模。 ARPTab[index].ttl=最大壽命; index++; if(index>=ARPTabSize) index=0; }
timer() //軟定時(shí)器任務(wù),用于ARP老化 { for(;;){ taskDelay(1秒); for(i=0;i<ARPTabSize;i++){ if(ARPTab[i].status==1){ if(ARPTab[i].ttl==0) ARPTab[i].status=0; else ARPTab[i].ttl--; } } }
主程序框架: initNIC //初始化網(wǎng)卡 //創(chuàng)建資源 TxSem和RxSem信號(hào)量 TxQFIFO隊(duì)列 大中小內(nèi)存設(shè)立 //創(chuàng)建任務(wù) 收 發(fā) 。 。 。 參考文獻(xiàn): 1!队肨CP/IP進(jìn)行網(wǎng)際互連》(第3版)第一、二、三卷 DOUGLAS E.COMER著 電子工業(yè)出版社
|