協(xié)議設(shè)計(jì)
場(chǎng)景
layer3 網(wǎng)絡(luò)中(網(wǎng)絡(luò)層),IP 協(xié)議中 IPv4 使用32位地址
layer2 網(wǎng)絡(luò)中(鏈路層),機(jī)器間通訊尋址是用 MAC 48位地址,ARP 協(xié)議在這一層游走。
那么當(dāng) layer3 下發(fā)一個(gè)數(shù)據(jù)包時(shí),硬件需要知道目標(biāo)設(shè)備的 MAC 地址才能確定接收方,但報(bào)文中只給了一個(gè) IP 地址,硬件設(shè)備玩不來(lái) layer3 那一套,老頭子們就整出一套 ARP 協(xié)議。
報(bào)文內(nèi)容
我們直接通過(guò) tshark 抓包后的字段信息學(xué)習(xí),更清晰易懂
有耐心的同學(xué)可以去 RFC 看看字段定義
Request
Address Resolution Protocol (request)
Hardware type: Ethernet (1)
Protocol type: IPv4 (0x0800)
Hardware size: 6
Protocol size: 4
Opcode: request (1)
Sender MAC address: Dell_aa:aa:aa (80:18:44:aa:aa:aa)
Sender IP address: 10.0.2.123
Target MAC address: 00:00:00_00:00:00 (00:00:00:00:00:00)
Target IP address: 10.0.2.251
Fields:
- Protocol: 請(qǐng)求轉(zhuǎn)換的地址協(xié)議類(lèi)型,此處未 IPv4
- Size: 地址字段的字節(jié)數(shù)
- Sender: 發(fā)起方
- Target MAC address: all 0 broadcast
- Target IP address: 請(qǐng)求轉(zhuǎn)換的地址
Reply
Address Resolution Protocol (reply)
Hardware type: Ethernet (1)
Protocol type: IPv4 (0x0800)
Hardware size: 6
Protocol size: 4
Opcode: reply (2)
Sender MAC address: Dell_cc:cc:cc (f4:8e:38:cc:cc:cc)
Sender IP address: 10.0.2.251
Target MAC address: Dell_aa:aa:aa (80:18:44:aa:aa:aa)
Target IP address: 10.0.2.123
字段含義同上,建議閱讀下方偽代碼,就基本能理解 arp 的工作原理了。
Received arp frame
If I have Hardware type (mac addr) == False: exit
If I have Protocol (IPv4) == False: exit
# 上面兩個(gè)都沒(méi)的話(huà),沒(méi)得玩
Set Merge_flag = False
# 更新舊記錄,設(shè)置flag
If <protocol type, sender protocol address> in arp_table:
Update it
Set Merge_flag = True
# 如果我是被請(qǐng)求方
If target target protocal(ip) address == mine:
If Merge_flag == False:
# 新紀(jì)錄,直接添加
arp_table.append(<protocol type, sender protocol address, sender hardware address>)
If Opcode == Request:
# 直接在原報(bào)文中互換字段,設(shè)置新值后發(fā)送。巧妙~
Swap Mac and IP
Set Sender info = mine
Set Opcode = reply
Send this arp
拓展信息: MAC Address Table

交換機(jī)有很多個(gè)端口,當(dāng)需要轉(zhuǎn)發(fā)幀給未知 mac 地址時(shí),會(huì) arp flood 所有的端口,這顯然是比較浪費(fèi)資源的。
因此交換機(jī)會(huì)維護(hù)一個(gè) mac address table,當(dāng)從 a 端口收到來(lái)自 h 地址的廣播幀時(shí),會(huì)將 a-h 作為一條記錄加進(jìn) mac address table。當(dāng)后續(xù)有發(fā)往 h 地址的廣播時(shí),就能直接將廣播幀轉(zhuǎn)發(fā)給 a 端口。
Arp Spoof 就是利用這一機(jī)制,不停向交換機(jī)發(fā)送虛假的 mac 地址,塞滿(mǎn)它的 table,讓它在轉(zhuǎn)發(fā)時(shí)不得不 flood 所有端口,是比較常見(jiàn)的攻擊手段。
Tshark 抓包
Case1 - 同子網(wǎng)
- host-a: 10.0.0.123
- gateway:
- host-b: 10.0.0.125
-
拓?fù)浣Y(jié)構(gòu)圖
Tips:
- layer2 switch 是二層交換機(jī),只有二層網(wǎng)絡(luò)功能的交換機(jī)
- layer3 switch 是含有部分路由功能(三層網(wǎng)絡(luò))的交換機(jī)
host-a ping host-b 捕捉 ARP 協(xié)議
# 清除 arp 緩存內(nèi)容
host-a$ sudo ip -s neigh flush all
host-b$ sudo ip -s neigh flush all
# 抓包開(kāi)始
host-a$ sudo tshark -i eno1 -f 'arp host 10.0.0.125 or icmp'
host-a$ ping 10.0.0.125 -c1
# 抓包結(jié)果
# host-a 發(fā)出 arp request,尋求 host-b mac addr
1 0.000000000 Dell_aa:aa:aa → Broadcast ARP 42 Who has 10.0.0.125? Tell 10.0.0.123
# host-b 響應(yīng)了
2 0.000158010 Dell_bb:bb:bb → Dell_aa:aa:aa ARP 60 10.0.0.125 is at 80:18:44:f0:ea:38
# 開(kāi)始通訊
3 0.000168966 10.0.0.123 → 10.0.0.125 ICMP 98 Echo (ping) request id=0x2017, seq=1/256, ttl=64
4 0.000328873 10.0.0.125 → 10.0.0.123 ICMP 98 Echo (ping) reply id=0x2017, seq=1/256, ttl=64 (request in 3)
# 來(lái)自網(wǎng)絡(luò)中其他節(jié)點(diǎn)的廣播~~
5 1.008209509 Dell_cc:cc:cc → Broadcast ARP 60 Who has 10.0.0.4? Tell 10.0.0.251
# 很奇怪,怎么 host-b 又發(fā)了一個(gè) unicast
6 5.150301642 Dell_bb:bb:bb → Dell_aa:aa:aa ARP 60 Who has 10.0.0.123? Tell 10.0.0.125
7 5.150311419 Dell_aa:aa:aa → Dell_bb:bb:bb ARP 42 10.0.0.123 is at 80:18:44:aa:aa:aa
現(xiàn)象及疑問(wèn)
發(fā)現(xiàn)一個(gè)有趣的現(xiàn)象,原本設(shè)想只會(huì)抓到兩個(gè)包(request from 123 + reply from 125),但實(shí)際抓到了 4 個(gè)(125 主動(dòng)發(fā)起一次 request)。
ARP RFC 中提到,在完成 reply 后 target host 節(jié)點(diǎn)應(yīng)該是記下了 source mac 地址了,為何還發(fā)起了一次查詢(xún) 10.0.0.123 的。
猜想
有可能是以下原因:
- ARP spoofing (我的網(wǎng)絡(luò)挺安全?。?/li>
- Directed ARP (跨子網(wǎng),路由會(huì)向相鄰路由發(fā)起 ARP)
- ARP 的 Refresh 行為,通過(guò)發(fā)起 Unicast Poll
簡(jiǎn)單排除:
- 125 發(fā)起的是 Unicast ARP Request,且兩臺(tái)機(jī)器同子網(wǎng),不需要借助路由可直接訪(fǎng)問(wèn),因此可以排除交換機(jī)行為
驗(yàn)證
Linux APR:
前兩種情況都與我們的網(wǎng)絡(luò)環(huán)境不相符,所以搜了下 Linux arp(7) table cache refresh 機(jī)制。
When there is no positive feedback for an existing mapping after some
time (see the /proc interfaces below), a neighbor cache entry is
considered stale. Positive feedback can be gotten from a higher
layer; for example from a successful TCP ACK.
- arp 將一個(gè) ip 標(biāo)記為 stale 后,會(huì)在間隔 delay_first_probe_time(5s) 后發(fā)起 Request 探針。
- arp 會(huì)為可用 record 基于 base_reachable_time_ms(30s) 參數(shù)生成一個(gè)隨機(jī)有效時(shí)間。
# 查看第一次 probe 發(fā)起的延遲
host-b$ cat /proc/sys/net/ipv4/neigh/eno1/delay_first_probe_time
5
# 發(fā)現(xiàn)跟我們抓包時(shí)的間隔時(shí)間很像,修改成 10 試一試
host-b# echo 10 > cat /proc/sys/net/ipv4/neigh/eno1/delay_first_probe_time
# 真的間隔變成 10s,這里就不放結(jié)果了~~信我就是了!
# 再看一下記錄的狀態(tài)轉(zhuǎn)換
# 清理兩端的 apr cache
host-a$ ping host-b -c1
host-b$ ip neigh
10.0.0.123 dev eno1 lladdr 80:18:44:f0:bb:7c DELAY
# host-b probe request 發(fā)起后
host-b$ ip neigh
10.0.0.123 dev eno1 lladdr 80:18:44:f0:bb:7c REACHABLE
# 等一段比較長(zhǎng)時(shí)間 >60s
host-b$ ip neigh
10.0.0.123 dev eno1 lladdr 80:18:44:f0:bb:7c STALE
# 這時(shí)候從 125 發(fā)起 ping (arp 表內(nèi)已存在)
host-b$ ping host-a -c1
# host-a 收到 ping,但沒(méi)有 arp request
# host-b 上的記錄變?yōu)?DELAY
host-b$ ip neigh
10.0.0.123 dev eno1 lladdr 80:18:44:f0:bb:7c DELAY
# 10s 后 probe 發(fā)起并受到 reply
host-b$ ip neigh
10.0.0.123 dev eno1 lladdr 80:18:44:f0:bb:7c REACHABLE
# 以上行為與 linux arp 文檔描述一致
總結(jié)

- 同子網(wǎng)通訊,發(fā)起端廣播 arp 請(qǐng)求目標(biāo)機(jī) mac 地址
- 等待 arp 請(qǐng)求得到回應(yīng),取得 10.0.0.125 的 mac-address
- 與目標(biāo)建立通訊
- Probe request 是 linux 下行為,非 arp 協(xié)議定義行為。
Case2 - 跨子網(wǎng)
- host-a: 10.0.2.123 (改了下 ip 和 gateway)
- host-b: 10.0.0.125
- Gatway: 10.0.2.251、10.0.0.251 (同一臺(tái)三層交換機(jī))
- 拓?fù)浣Y(jié)構(gòu)圖

Tips:
- 跨子網(wǎng)時(shí),ping 包會(huì)發(fā)給默認(rèn)網(wǎng)關(guān),網(wǎng)關(guān)幫忙轉(zhuǎn)發(fā)
- 改了 ip 需要調(diào)通交換機(jī)上的 vlan,該例子中兩個(gè)網(wǎng)關(guān)是同一臺(tái)物理交換機(jī)
需要做如下配置:
- host-a 的 ip 和 gateway 修改成 10.0.2.123 和 10.0.2.251
- layer2 switch 要將 host-a 所連端口的 vlan10 改成 vlan12(10.0.2.xxx 網(wǎng)段所屬vlan)
- layer3 switch 要允許 vlan12 的幀通過(guò) layer2 switch 所在的端口(port-channel or port)
抓包
host-a ping host-b,捕捉 ARP 協(xié)議
# 清除 arp 緩存內(nèi)容
host-a$ sudo ip -s neigh flush all
host-b$ sudo ip -s neigh flush all
# 因?yàn)闊o(wú)法在交換機(jī)上抓包,所以在兩個(gè)節(jié)點(diǎn)分別抓包
host-a$ sudo tshark -i eno1 -f 'arp host 10.0.0.125 or arp host 10.0.0.251 or arp host 10.0.2.251 or icmp'
host-b$ sudo tshark -i eno1 -f 'arp host 10.0.0.125 or arp host 10.0.0.251 or arp host 10.0.2.251 or icmp'
host-a$ ping 10.0.0.125 -c1
# host-a Dell_aa:aa:aa 抓包結(jié)果
# 1.arp 請(qǐng)求網(wǎng)關(guān) 10.0.2.251 mac 地址
# 2.網(wǎng)關(guān)回復(fù) arp
# 3.icmp 目的地址是 host-b。但其數(shù)據(jù)幀目的地其實(shí)是網(wǎng)關(guān),網(wǎng)關(guān)再轉(zhuǎn)發(fā)給 host-b
# 4.收到 icmp 回復(fù)
1 0.000000000 Dell_aa:aa:aa → Broadcast ARP 42 Who has 10.0.2.251? Tell 10.0.2.123
2 0.010917102 Dell_cc:cc:cc → Dell_aa:aa:aa ARP 60 10.0.2.251 is at f4:8e:38:cc:cc:cc
3 5.005604716 10.0.2.123 → 10.0.0.125 ICMP 98 Echo (ping) request id=0x24a8, seq=1/256, ttl=64
4 5.006708884 10.0.0.125 → 10.0.2.123 ICMP 98 Echo (ping) reply id=0x24a8, seq=1/256, ttl=63 (request in 3)
# host-b Dell_bb:bb:bb 抓包結(jié)果
# 1.收到 icmp 請(qǐng)求
# 2.arp 請(qǐng)求網(wǎng)關(guān) 10.0.0.251 mac 地址
# 3.網(wǎng)關(guān)回復(fù) arp
# 4.回復(fù) icmp,數(shù)據(jù)幀是發(fā)給網(wǎng)關(guān),網(wǎng)關(guān)負(fù)責(zé)轉(zhuǎn)發(fā)
1 0.000000000 10.0.2.123 → 10.0.0.125 ICMP 98 Echo (ping) request id=0x24a8, seq=1/256, ttl=63
2 0.000028673 Dell_bb:bb:bb → Broadcast ARP 42 Who has 10.0.0.251? Tell 10.0.0.125
3 0.000937934 Dell_cc:cc:cc → Dell_bb:bb:bb ARP 60 10.0.0.251 is at f4:8e:38:cc:cc:cc
4 0.000948037 10.0.0.125 → 10.0.2.123 ICMP 98 Echo (ping) reply id=0x24a8, seq=1/256, ttl=64 (request in 1)
總結(jié)
- 跨網(wǎng)段通訊,arp 會(huì)請(qǐng)求默認(rèn)網(wǎng)關(guān) mac 地址,由網(wǎng)關(guān)負(fù)責(zé)轉(zhuǎn)發(fā)數(shù)據(jù)幀
- layer2 數(shù)據(jù)幀和 layer3 數(shù)據(jù)包的 destination 可能是不同節(jié)點(diǎn),容易混淆
- layer2:mac layer3:ip layer4:port,區(qū)分各層網(wǎng)絡(luò)的雙端通訊標(biāo)識(shí),能在日常調(diào)試中幫助理解網(wǎng)絡(luò)問(wèn)題
