大家好,我是風箏。
各位同學肯定見過關於網路的面試題,什麼TCP協定和UDP的區別啦,IP協定工作在哪層啊等等,這都是網路中定義的各種協定。這些標準化的協定就是網路分層模型標準化的核心部分。要想搞懂網路,必須搞明白其中的幾種主要的網路協定。
今天我們就開始介紹網路世界的協定。介紹的順序大致是從網路模型由底向上來,包括資料鏈路層的乙太網協定,網路層的 ARP協定、RARP協定、IP協定、ICMP協定,傳輸層的 TCP協定、UDP 協定,以及應用層的 HTTP/HTTPS協定。
我們假設封包都是在乙太網傳輸,所以乙太網協定一直是貫穿始終的,因為最終所有的封包都會被封裝成乙太網幀,所以其他幾種協定的介紹過程中,會穿插這乙太網協定,不用單獨介紹,自然而然就理解了。
今天我們就徹底講清楚 ARP 協定(地址解析協定), 我把 ARP 協定比作封包在網路世界的臨門一腳,當封包到達了一個區域網中,區域網中有那麼多機器,踢開哪臺機器的大門,就靠 ARP 協定了。為什麼這麼說,一會兒你就明白了。
其實 ARP 協定可以簡單概括為幾句話:
好的,講完了。開個玩笑,當然沒有,最好還是繼續看下邊的內容。
在 OSI 模型中,通常認為 ARP和RARP屬於資料鏈路層協定,因為它們不使用IP協定。而在TCP/IP 協定棧中,將 ARP歸於網路層,和IP協定在同一層。
只要知道對方的 IP 地址或域名,就能將資料傳送過去,這是我們常識性的理解。而且在平常的使用過程中也確實是這樣的。比如我們開發過程中,建立一個 socket 連線,只要知道目標 IP 和 埠就可以了。
真實的網路世界中是這樣的嗎,確實 IP 地址是必不可少的,但是還有另外一種地址也是必不可少的,那就是 MAC 地址。
最終,在資料到達乙太網鏈路層,會被包裝成乙太網資料框,而資料框中決定最終去向的是就是目標 MAC 地址,注意嘍,是 MAC 地址(硬體地址),而不是 IP 地址。
可以把 IP 比作現實世界中一個房子的標記,比如北京市朝陽區XX路xx小區1號樓1單元801,而 MAC 地址則是這個房子的經緯度(116.354856,39.942009),雖然我們看前面的一串標示更容易理解,但是當我們要導航去那裡的時候還是要靠經緯度。
但是如何在一大群機器中確定目標 IP 對應的 MAC 地址呢,資料已經到了區域網,這麼多大門該開哪一扇呢,這就是 ARP 該做的事兒了。
什麼是 MAC 地址
MAC地址(Media Access Control Address),直譯為媒體存取控制位址,也稱為區域網地址(LAN Address),乙太網地址(Ethernet Address)或實體地址(Physical Address),它是一個用來確認網路裝置位置的位址。在OSI模型中,第三層網路層負責IP地址,第二層資料鏈結層則負責MAC位址。MAC地址用於在網路中唯一標示一個網路卡,一臺裝置若有一或多個網路卡,則每個網路卡都需要並會有一個唯一的MAC地址。
MAC 地址長度為6位元組,48bit,用16進位制的6個元組表示,例如 3c:22:fb:64:4f:1d
,其中有一個特殊的地址就是所有位元位都是1的地址 ff:ff:ff:ff:ff:ff
,表示是一個廣播地址,意思就是指訊息要發給區域網中的所有網路卡。
直接的原因就是資料鏈路層要將資料框傳送到目的端,必須要知道目的端的 MAC 地址,這是由網路模型的架構設計決定的。
下面這張圖是網路 4 層模型以及資料從傳送端到接收端的首發過程。傳送是從上向下經過層層包裝,最後形成一個資料框(最常用的乙太網幀),之後這個資料在紛繁複雜的網路世界中勇往直前,到達接收端,接收端從下向上層層拆包,最後供應用層使用。
上面這張圖很有用的,在學習網路知識(不管是基礎概念還是各種協定)的時候可以為我們提供一個宏觀的視角。
網路中的裝置數以億計,一個傳送終端怎麼可能知道每一臺接收終端的 MAC 地址呢,顯然,這是不可能的。但是 IP 是知道的,比如我們存取 github,github 每一臺伺服器的 MAC 地址(準確的說是一個網路卡的 MAC 地址,因為一臺機器上可能有多個網路卡)我們是不知道的,但是它的域名我們都知道,通過域名得到 IP 地址,這是 DNS 的功能,大多數同學都很清楚,所以間接的也就相當於我們知道了 github 的IP。
但是,為什麼我們在做應用層開發的時候不需要關注 MAC 地址。
根本沒有什麼歲月靜好,只是有人在替我們負重前行罷了。在這裡幫我們負重前行的就是 ARP 協定。
這裡面有一個關鍵點,最終在網路上傳輸的包,必定是一個資料框,最常用的就是乙太網幀。
如果我們完全將這一過程當做一個黑盒,可以對照理解為一個程式中封裝好的方法,例如 getMacAddressByIP(Long ip)
,當我們需要得到 MAC 地址時,直接呼叫這個方法就可以了。
ARP 協定全稱地址解析協定,用來將 IP 地址解析出 MAC 地址,還有一個與之相對的協定 RARP,全稱叫做逆地址解析協定,用來將 MAC 地址解析出 IP 地址。
ARP 就是工作在一個區域網中的,
當一臺路由器或者一個具有路由轉發功能的主機想要通過一個 IP 地址得到對應的 MAC 地址時,就可以使用 ARP 協定了,這個裝置向它所在的目標子網中傳送一個廣播的 ARP 請求,請求中帶著這個 IP 地址,意思是說,在這個網路中的各位,誰的 IP 是這個,請回復我一下,並告訴我你的 MAC 地址。收到這個請求的主機對這個 ARP 包進行解析,如果發現攜帶的 IP 正好是自己的,就返回一個 ARP 回覆,回覆中帶上自己的 MAC 地址。
使用 ARP 協定後,目的主機將自己的 IP 地址和 MAC 地址返回給源主機,源主機將 MAC 地址加到乙太網幀中,構造成完整的幀格式,再將資料框通過鏈路層發出。
最終資料框到達目的主機,鏈路層通過資料框中的目的 MAC 地址判斷資料框是不是發給自己的,如果是的話,則接收資料框,並經過層層解析,最終交給應用層對應的程式處理。
乙太網目的地址:目的端 MAC 地址,6位元組。
乙太網源地址:傳送端的 MAC 地址,6位元組。
幀型別:標記資料部分的型別,如果是 IP 資料包,值為 0x0800,如果是 ARP 資料包,值為 0x0806,2位元組。
資料:以太幀搭載的資料。只要是在乙太網上傳送資料,最終都會被鏈路層封裝成乙太網資料框,所以資料部分即可以是 IP 資料包、ARP 協定包、ICMP 封包等,乙太網資料框資料部分最小長度是 46 位元組,最大長度由 MTU 決定,是 1500 位元組。
CRC:資料檢驗碼,用來在接收端檢驗接收的資料是不是無差錯的,4位元組。
既然大家都是程式設計師,我們將這個資料框抽象成一個實體類表示,幫助我們理解。
public class EthernetFrame {
/**
* 目的 MAC 地址,6位元組,48bit
*/
private Byte[] destinationMacAddress = new Byte[6];
/**
* 源 MAC 地址,6位元組,48bit
*/
private Byte[] sourceMacAddress = new Byte[6];
/**
* 標記搭載的資料型別,
* 如果是 IP 資料包,值為 0x0800
* 如果是 ARP 資料包,值為 0x0806
*/
private Byte[] type = new Byte[2];
/**
* 資料
* 乙太網的資料長度為 46~1500位元組。
* 最短 46 位元組,不夠的要填充(pad)
* 最長 1500 位元組,乙太網的 MTU 決定的
*/
private Byte[] data = new Byte[46];
/**
* 檢驗碼 4 位元組,在幀尾部
*/
private Byte[] crc = new Byte[4];
}
以下是 ARP 協定的格式,以及一對完整的 ARP 請求資料框和應答資料框。
硬體型別:2位元組,用來表示硬體地址的型別,為 1 表示乙太網地址。
協定型別:2位元組,用來表示要對映的協定地址型別。ARP 不僅可以表示要將 IP 轉換為 MAC ,還允許其他的轉換關係,例如將另外一種非 IP 地址轉換為 MAC 地址。當它的值為0x0800
表示 IP 地址,與包含IP資料包的乙太網資料框中的型別欄位的值相同。
硬體地址長度:1位元組,用來表示硬體地址的長度,單位是位元組。在乙太網中就是 MAC 地址的長度,值為6,也就表示 MAC 地址長度為 6 位元組。
協定地址長度:1位元組,用來表示協定地址的長度,單位是位元組。在乙太網中,如果協定型別是 IP,也就是要將 IP 轉換為 MAC 時,它的值是 4 ,也就是4位元組,表示 IP 地址的長度是 4 位元組。
操作欄位:2位元組,用來表示當前操作的型別。值為1,表示 ARP 請求;值為2,表示 ARP 應答;值為3,表示 RARP 請求;值為4,表示 RARP 應答。此欄位是用來區分請求和應答的必需欄位。
傳送端乙太網地址:6位元組,用來表示傳送端的乙太網 MAC 地址。
傳送端 IP 地址:4位元組,用來表示傳送端的 IP 地址。
目的乙太網地址:6位元組,用來表示目的端的 MAC 地址。
目的 IP 地址:4位元組,用來表示目的端的 IP 地址。
整個 ARP 協定部分共 28位元組,但是在乙太網中,一個乙太網資料框資料最小長度為 46,所以,後面要有18位元組的資料填充。
操作欄位為1表示這是一個 ARP 請求,ARP 請求是個廣播訊息,請求資料中沒有目的 MAC 地址,因為還不知道嘛,我們要找的就是它。之後,ARP 應答訊息(操作欄位是2)將本機的 MAC 地址放到源MAC地址中,並且將 ARP 請求中的MAC地址替換為目的 MAC 地址,用來告知 ARP 傳送端。
還是用一個實現類來表示 ARP 協定中的各個欄位。
public class Arp {
/**
* 硬體型別,2位元組,值為1表示乙太網
*/
private Byte[] hardwareType = new Byte[2];
/**
* 協定型別,2位元組,要對映的協定地址型別
* 0x0800 表示要轉換的源協定為 IP 協定
*/
private Byte[] protocolType = new Byte[2];
/**
* 硬體地址長度,1位元組,表示硬體地址的長度,單位為位元組
* 硬體地址長度在乙太網表示 MAC 地址的長度,值為6,也就是 6 位元組
*
*/
private Byte[] hardwareAddressLength = new Byte[1];
/**
* 協定地址長度,1位元組,表示協定地址的長度,單位為位元組
* 在乙太網轉換 IP 為 MAC 地址時,就表示為 IP 地址的長度,也就是4位元組
*/
private Byte[] protocolAddressLength = new Byte[1];
/**
* 操作欄位,2位元組,表示操作型別
* 1:ARP 請求
* 2:ARP 應答
* 3:RARP 請求
* 4:RARP 應答
*/
private Byte[] op = new Byte[2];
/**
* 傳送端 MAC 地址,6位元組
*/
private Byte[] sourceMacAddress = new Byte[6];
/**
* 傳送端 IP 地址,4位元組
*/
private Byte[] sourceIpAddress = new Byte[4];
/**
* 目的端 MAC 地址,6位元組
* 在 ARP 請求中,這個地址為空
*/
private Byte[] destinationMacAddress = new Byte[6];
/**
* 目的端 IP 地址,4位元組
* 接收 ARP 廣播的主機通過 IP 地址判斷是否回覆 ARP 應答(IP 地址的所有者)
*/
private Byte[] destinationIpAddress = new Byte[4];
}
開啟 Wireshark ,開始抓包,等待一會兒,然後停止抓包。你電腦中的各個程式會偷偷的傳送和應答很多 ARP 包,
在 Wireshark 中過濾 arp.opcode == 1
的 ARP 請求,然後找到其中一個。用 Wireshark 分析包特別直觀,我們用滑鼠點選上方的某個欄位時,下面會自動將這個欄位的值標記出來,這樣就可以清楚的看到這個欄位在整個封包中的位置了。例如下圖選中了 ARP 包中的 Opcode欄位(操作欄位),下面的 00 01
被標記了,這是用 16 進位製表示的,00
表示一個位元組,01
表示一個位元組,表示這個操作欄位是2個位元組,值是1,也就是ARP 請求。
簡單分析一下這個包
圖中 1、2、3 三個部分分別是乙太網幀概要資訊、乙太網資料框首部、乙太網資料框資料內容。在第一部分概要資訊可以看到這個幀的總大小是 60 bytes,乙太網資料框頭部的14 位元組,加上內容部分最少46位元組,剛好是 60 位元組,其實還有一個 CRC (資料檢驗碼),只不過本地網路卡會自動剝離掉,所以在 Wireshark 中是看不到的。
乙太網資料框首部
上圖是乙太網資料框首部資訊。
1、目的 MAC 地址,都是 ff
,表示這是個廣播請求,ARP 請求本身就是廣播的。
2、源 MAC 地址,本機的 MAC 地址。
3、幀型別,ARP 型別。
4、因為乙太網幀資料部分最小是 46 位元組,ARP 只有28位元組,所以要填充18位元組。
ARP 請求資訊
從上到下依次是硬體型別、協定型別、硬體地址長度、協定地址長度(一般就是IP地址)、操作欄位、傳送端 MAC 地址、傳送端 IP 地址、目標 MAC 地址、目標 IP 地址。
使用 wireshark 的 filter arp.opcode == 2
可過濾出 APR 請求。
是不是完全聽明白了,回頭發現,就是我開頭總結的那幾點內容。
自己學容易,寫出來真難啊,如果對各位有一點點幫助當然最好了。
如果覺得還不錯的話,給個推薦吧!
公眾號「古時的風箏」,Java 開發者,專注 Java 及周邊生態。堅持原創乾貨輸出,你可選擇現在就關注我,或者看看歷史文章再關注也不遲。長按二維條碼關注,跟我一起變優秀!