Playing and Learning ARP in Action

动手学习计算机网络系列(一)

实验准备

这是我的工具:

  • 一台linux机器,内核支持ip转发,arp代理。带无线网卡和有线网卡。比如一台笔记本电脑。
  • linux机器装好scapy,tcpdump
  • 网线,不一定是双绞线,现代网卡都应该支持双向通信模式的转换。
  • 一台windows台式机,带有线网卡和接口。

一点scapy的小基础

scapy是一种基于python的包操作工具,在python上实现了一种DSL(领域特定语言)。定义一个包很简单。比如一个=Ether=:

Ether(dst="ff:ff:ff:ff:ff:ff")

封包里的字段可以=ls=来查看:

ls(Ether)

当我们想让一个Ethernet包封装一个IP包时,仅仅使用一个=/=:

ip = Ether()/IP()

当我们想发包时,如果想在第三层上网络层以上发包:

send(ip)

但你要发一个ARP包(在第二层上发包)的话,要用=srp=或=srp1=。

arp如何工作

计算机仅仅知道ip地址是无法通信的,ip数据报在封装到数据链路层中时需要加上比如以太网报头,报头中应该含有数据链路层能理解的地址即MAC地址。ARP就是着么一种将IP转换成MAC地址的协议。

如下例子,当我们Ping一台机器时,如果该ip在arp缓存中有,就可以直接找到mac地址,如果没有,就会广播一个请求询问对应ip的mac地址。

~/Work/project/arp-pos ⮀ sudo arp -d 192.168.1.113
~/Work/project/arp-pos ⮀ ping -c 1 192.168.1.113
PING 192.168.1.113 (192.168.1.113) 56(84) bytes of data.
64 bytes from 192.168.1.113: icmp_seq=1 ttl=64 time=142 ms

--- 192.168.1.113 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 142.484/142.484/142.484/0.000 ms

 ~ ⮀ sudo tcpdump -vv -eqtnni wlan0 arp
tcpdump: listening on wlan0, link-type EN10MB (Ethernet), capture size 65535 bytes
11:11:11:11:11:11 > ff:ff:ff:ff:ff:ff, ARP, length 42: Ethernet (len 6), IPv4 (len 4), Request who-has 192.168.1.113 tell 192.168.1.106, length 28
22:22:22:22:22:22 > 11:11:11:11:11:11, ARP, length 42: Ethernet (len 6), IPv4 (len 4), Reply 192.168.1.113 is-at 22:22:22:22:22:22, length 28

抓包观察

让我们看看机器获取IP的过程。这里我们用tcpdump,你可以在任何linux发行版的源里找到它。当然,也可以用wireshark。

 ~ ⮀ sudo tcpdump -e -i wlan0 port bootps or port bootpc or arp
tcpdump: WARNING: wlan0: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on wlan0, link-type EN10MB (Ethernet), capture size 65535 bytes
21:54:19.202074 11:11:11:11:11:11 (oui Unknown) > Broadcast, ethertype IPv4 (0x0800), length 371: 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from 11:11:11:11:11:11 (oui Unknown), length 329
21:54:19.277875 ec:88:8f:b4:d6:68 (oui Unknown) > 11:11:11:11:11:11 (oui Unknown), ethertype IPv4 (0x0800), length 590: 192.168.1.1.bootps > 192.168.1.101.bootpc: BOOTP/DHCP, Reply, length 548
21:54:19.352068 11:11:11:11:11:11 (oui Unknown) > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.102 tell 0.0.0.0, length 28
21:54:20.720541 11:11:11:11:11:11 (oui Unknown) > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.102 tell 0.0.0.0, length 28
21:54:22.155087 11:11:11:11:11:11 (oui Unknown) > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.102 tell 0.0.0.0, length 28
21:54:24.232076 11:11:11:11:11:11 (oui Unknown) > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.102 tell 192.168.1.102, length 28
21:54:26.234201 11:11:11:11:11:11 (oui Unknown) > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.102 tell 192.168.1.102, length 28
21:54:27.502047 11:11:11:11:11:11 (oui Unknown) > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 192.168.1.1 tell 192.168.1.101, length 28
21:54:27.514492 ec:88:8f:b4:d6:68 (oui Unknown) > 11:11:11:11:11:11 (oui Unknown), ethertype ARP (0x0806), length 42: Reply 192.168.1.1 is-at ec:88:8f:b4:d6:68 (oui Unknown), length 28

可以看到,先是dhcp的过程,下次我们再讲。紧接着我们的机器发出一个ARP探针(Probe),哦,发了好几次来确认没有机器使用=192.168.1.1=,于是接着发了两个announcement宣告拥有了这个ip。接着询问=192.168.1.1=的MAC地址并收到来自=192.168.1.1=的回复。

一个广播的ARP请求

为了实验正确,我们先删除arp缓存内容:

~/Work/project/arp ⮀ sudo arp -d 10.210.96.193
~/Work/project/arp ⮀ sudo arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
  10.210.96.193                    (incomplete)                              eth0

首先,我们作为=10.210.96.200=,想知道=10.210.96.193=的MAC地址,我们广播一个ARP请求:

    我是10.210.96.200,我的MAC地址是11:11:11:11:11:11,我想知道10.210.96.193的IP地址是什么。

让我们先探索下ARP包的结构。

>>> ls(ARP)
hwtype     : XShortField          = (1)
ptype      : XShortEnumField      = (2048)
hwlen      : ByteField            = (6)
plen       : ByteField            = (4)
op         : ShortEnumField       = (1)
hwsrc      : ARPSourceMACField    = (None)
psrc       : SourceIPField        = (None)
hwdst      : MACField             = ('00:00:00:00:00:00')
pdst       : IPField              = ('0.0.0.0')

参见ARP协议封包结构

首先我们构造一个ARP请求。该请求工作在Ethernet上(0x0001),协议类型是IPv4(0x0800), 是一个请求(0x0001=或=who-has),发送者的MAC地址是=11:11:11:11:11:11=, 发送者的协议地址是=10.210.96.200=,想要知道MAC地址的协议地址是=10.210.96.193=。根据以太网包字段,该消息通过广播发送。

arp_request = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(hwtype=0x0001,ptype=0x0800,op=0x0001,hwsrc='11:11:11:11:11:11', psrc='10.210.96.200', pdst='10.210.96.193')

scapy会自动为我们设置=Ether=的某些字段,所以我们不用都指定。

>>> arp_request.show()
###[ Ethernet ]###
  dst= ff:ff:ff:ff:ff:ff
  src= 11:11:11:11:11:11
  type= 0x806
###[ ARP ]###
     hwtype= 0x1
     ptype= 0x800
     hwlen= 6
     plen= 4
     op= who-has
     hwsrc= 11:11:11:11:11:11
     psrc= 10.210.96.200
     hwdst= 00:00:00:00:00:00
     pdst= 10.210.96.193

紧接着发送它:

>>> recv = srp(arp_request)
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
>>> recv
(<Results: TCP:0 UDP:0 ICMP:0 Other:1>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
>>> recv[0].show()
0000 Ether / ARP who has 10.210.96.193 says 10.210.96.200 ==> Ether / ARP is at 3c:e5:a6:d2:39:ad says 10.210.96.193 / Padding

可见我们得到了它的MAC地址。检查ARP缓存:

~/Work/project/arp ⮀ sudo arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
10.210.96.193            ether   3c:e5:a6:d2:39:ad   C                     eth0

Bingo!

一个ARP应答

首先要设置静态IP。将一台win7机器设置为=172.16.0.17=.

接着我们要发一个不请自答的ARP reply,这一技术常用来进行ARP Poison。

>>> arp_reply = Ether(src='11:11:11:11:11:11', dst='C8:1F:66:05:E2:6A')/ARP(hwtype=0x0001,ptype=0x0800,op=0x0002,hwsrc='11:11:11:11:11:11', hwdst='C8:1F:66:05:E2:6A', psrc='172.16.0.28', pdst='172.16.0.17')
recv = srp(arp_reply, timeout=1, iface='eth0')

查看我们的监听:

 ~ ⮀ sudo  tcpdump -nni eth0 arp 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
17:22:10.465636 ARP, Reply 172.16.0.28 is-at 11:11:11:11:11:11, length 28

然而检查另一台机器的arp缓存可能并没有=172.16.0.28=的项。这是因为操作系统可能不欢迎不请自来的arp应答。

我们试着使用不请自来的arp请求看看:

>>> arp_request = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(hwtype=0x0001,ptype=0x0800,op=0x0001,hwsrc='11:11:11:11:11:11', psrc='172.16.0.22', pdst='172.16.0.17')
>>> recv = srp(arp_request)

则可以在另一台机器上的缓存表中发现=172.16.0.22=的项。说明操作系统默默接受了不请自来的请求。

为了查看ARP响应的效果,我们同时打开两边机器,创造一个请求。先清空windows机器的缓存表。

我们让windows机器ping=172.16.0.30=。同时开始抓包和准备应答(在win7上ping的同时执行下列命令):

>>> arp_reply = Ether(src='11:11:11:11:11:11', dst='C8:1F:66:05:E2:6A')/ARP(hwtype=0x0001,ptype=0x0800,op=0x0002,hwsrc='11:11:11:11:11:11', hwdst='C8:1F:66:05:E2:6A', psrc='172.16.0.30', pdst='172.16.0.17')
>>> recv = srp(arp_reply, timeout=1, iface='eth0')Begin emission:
Finished to send 1 packets.
...
Received 3 packets, got 0 answers, remaining 1 packets

可以观察到包的前两个正是我们想要的。

~ ⮀ sudo  tcpdump -nni eth0 arp 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
17:28:55.041968 ARP, Request who-has 172.16.0.30 tell 172.16.0.17, length 46
17:28:55.165561 ARP, Reply 172.16.0.30 is-at 11:11:11:11:11:11, length 28

同时发现win7的arp缓存中已经有了=172.16.0.30=。

ARP Announcement

这是一种特殊的ARP请求,目标协议地址填入发送者的协议地址,将目标硬件地址设为0.

或者,是一种特殊的ARP响应,目标协议地址和目标硬件地址都是发送者的协议和目标硬件地址。

ARP announcement意在更新其它收到这个包的机器的ARP缓存。这种免费的ARP(不请自来)常用来在机器启动或换网卡时通知其它机器。也用来做负载平衡。

>>> arp_announcement = Ether(src='11:11:11:11:11:11', dst="ff:ff:ff:ff:ff:ff")/ARP(hwtype=0x0001,ptype=0x0800,op=0x0001,hwsrc='11:11:11:11:11:11', hwdst='00:00:00:00:00:00', psrc='172.16.0.40', pdst='172.16.0.40')
>>> recv = srp1(arp_announcement, timeout=2)
Begin emission:
Finished to send 1 packets.

Received 0 packets, got 0 answers, remaining 1 packets

 ~ ⮀ sudo  tcpdump -enni eth0 arp 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
22:15:39.645293 11:11:11:11:11:11 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 172.16.0.40 tell 172.16.0.40, length 28

遗憾的是,win7的ARP缓存没有接受=172.16.0.40=.

一个响应Announcement:

>>> arp_announcement = Ether(src='11:11:11:11:11:11', dst="ff:ff:ff:ff:ff:ff")/ARP(hwtype=0x0001,ptype=0x0800,op=0x0002,hwsrc='11:11:11:11:11:11', hwdst='11:11:11:11:11:11', psrc='172.16.0.40', pdst='172.16.0.40')
>>> recv = srp1(arp_announcement, timeout=2)

 ~ ⮀ sudo  tcpdump -enni eth0 arp 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
22:18:21.655256 11:11:11:11:11:11 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Reply 172.16.0.40 is-at 11:11:11:11:11:11, length 28

ARP probe

ARP探针是一个全0 IP地址的广播请求,用来检测IPv4地址冲突。在开始使用一个IP地址之前,实现这个规范(RFC5227)的主机必须通过ARP探针测试地址是否被使用。

具体过程参见RFC5227.

A host probes to see if an address is already in use by broadcasting an ARP Request for the desired address. The client MUST fill in the 'sender hardware address' field of the ARP Request with the hardware address of the interface through which it is sending the packet. The 'sender IP address' field MUST be set to all zeroes; this is to avoid polluting ARP caches in other hosts on the same link in the case where the address turns out to be already in use by another host. The 'target hardware address' field is ignored and SHOULD be set to all zeroes. The 'target IP address' field MUST be set to the address being probed. An ARP Request constructed this way, with an all-zero 'sender IP address', is referred to as an 'ARP Probe'.

类似这样:

>>> arp_probe = (Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(psrc='0.0.0.0', pdst='172.16.0.17'))
>>> arp_probe.show()
###[ Ethernet ]###
  dst= ff:ff:ff:ff:ff:ff
  src= 11:11:11:11:11:11
  type= 0x806
###[ ARP ]###
     hwtype= 0x1
     ptype= 0x800
     hwlen= 6
     plen= 4
     op= who-has
     hwsrc= 11:11:11:11:11:11
     psrc= 0.0.0.0
     hwdst= 00:00:00:00:00:00
     pdst= 172.16.0.17
>>> recv = srp(arp_probe, iface='eth0')
Begin emission:
Finished to send 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets

开始监听然后发送:

 ~ ⮀ sudo  tcpdump -nni eth0 arp 
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
22:08:56.885470 ARP, Request who-has 172.16.0.17 tell 0.0.0.0, length 28
22:08:56.885831 ARP, Reply 172.16.0.17 is-at c8:1f:66:05:e2:6a, length 46

说明有=172.16.0.17=这个地址。但可以检查一下,=0.0.0.0=并没有污染win7机器的arp缓存。

ARP mediation

在VPN中不同网络架构时使用ARP。略

ARP Stuffing

某些嵌入式系统,没有配置界面,用户无法使用地址分配协议,设备没有合适的IP地址,这时候主机人工填充一个IP地址到它的地址表,然后向设备发送特殊的包。设备于是采用这个IP地址和主机通信。

ARP Suppression

禁用主机ARP,要求配置静态ARP缓存才能通信:

ip link set dev eth0 arp off
# ifconfig wlan0 -arp

ARP Proxy

同一个IP网段被某台设备分成两个部分。arp代理可以让两端的机器好像在同一以太网内工作一样。

举如下例子。

当我们有一台linux机器。其中wlan0连接无线路由,另外有一台win7机器,没有无线网卡,和linux机器的eth0直接用网线相连。

Internet-----------无线路由-----------wlan0 [Linux] eth0---------------windows 7

为了让windows7机器能够联网,我们可以设置windows7机器和linux机器在同一网段。比如设置:

先设置ip地址:

  • 路由器网关地址=192.168.1.1=
  • linux在wlan0被路由器分配地址,=192.168.1.103=,子网掩码=255.255.255.0=
  • windows7机器设置静态ip=192.168.1.114=,子网掩码=255.255.255.0=

下面我们开始设置ARP代理,在linux机器上设置内核允许代理和转发:

gentoo ~ # echo 1 > /proc/sys/net/ipv4/conf/eth0/proxy_arp
gentoo ~ # echo 1 > /proc/sys/net/ipv4/conf/wlan0/proxy_arp
gentoo ~ # echo 1 > /proc/sys/net/ipv4/ip_forward

在wlan0上对应其MAC设置想要代理的对象的静态地址(192.168.1.114),让linux对wlan0上以太网响应对=192.168.1.114=的响应,并把自己的无线网卡MAC作为响应内容。

sudo arp -i wlan0 -s 192.168.1.114 11:11:11:11:11:11 pub

可以选择是否设置下一步,即使在eth0上没有ip地址也可以。

设置eth0接口上的ip地址(默认netmask是=255.255.255.0=):

sudo ifconfig eth0 inet 192.168.1.113

添加路由:

sudo route add 192.168.1.114 eth0

linux和windows机器互ping以确保连接:

⮀ ~ ⮀ ping -c 2 192.168.1.114
PING 192.168.1.114 (192.168.1.114) 56(84) bytes of data.
64 bytes from 192.168.1.114: icmp_seq=1 ttl=128 time=0.446 ms
64 bytes from 192.168.1.114: icmp_seq=2 ttl=128 time=0.469 ms

--- 192.168.1.114 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.446/0.457/0.469/0.024 ms

对=eth0=上将自己的有线网卡MAC设置为对=192.168.1.1=的响应。

sudo arp -i eth0 -s 192.168.1.1 00:24:54:9a:05:8b pub

接着在windows7上尝试=ping=一下=192.168.1.1=:

如果在windows7机器上设置了正确的dns,就可以直接上网了。