详细的网络协议之ARP请求_封装

对于 ARP 协议,你想知道的,可能包括以下几点:

  • ARP 是啥
  • 为啥需要 ARP
  • ARP 是怎么工作的
  • ARP 报文格式长啥样
  • ARP 安全吗
  • 有哪些命令可以操作 ARP
  • ARP 有哪些内核配置参数

带着这些问题,下面我们就来一一解答。

 

ARP 是啥


ARP 全称为 Address Resolution Protocol ,即地址解析协议,主要用于根据 IP 地址求出主机所对应的物理地址(或者叫 MAC 地址)。

它还有个孪生兄弟叫 RARP(Reverse ARP),即反向地址解析协议,也就是根据主机 MAC 地址求出对应的 IP 地址,但是这个协议不常用,通常只用在无盘系统中。

ARP 只用于 IPv4,IPv6 使用邻居发现协议( Neighbor Discovery Protocol, NDP) 来代替 ARP 的功能。

 

为啥需要 ARP


在网络通信中,主机和主机之间的通信需要根据 OSI 模型进行数据包的封装和解封装,这里面不仅需要封装源目的 IP 地址,也需要源目的 MAC 地址,那么 MAC 地址从哪里来呢,一般情况下,上层应用只知道 IP 地址,而并不关心 MAC 地址,所以就需要通过一个协议来获知目的 MAC 地址,完成数据的封装,这个协议就是 ARP 协议该干的活。

 

ARP 是怎么工作的


在介绍 ARP 如何工作之前,需要知道一个概念 ARP 缓存表 。在每台安装有 TCP/IP 协议的电脑或网络设备里都有一个 ARP 缓存表,表中记录的是 IP 地址和 MAC 地址的映射关系,比如:

详细的网络协议之ARP请求_封装_02

以上表中的主机 A 向主机 B 发送数据为例,说一下 ARP 的工作流程:

  1. 当发送数据时,主机 A 会在自己的 ARP 缓存表中寻找是否有目标 IP 地址,如果找到,就把对应的目标 MAC 地址封装进帧里进行发送。
  2. 如果没有找到,主机 A 就会向网络中发送一个广播(ARP request),和主机 A 同网段内的所有主机都会收到这个请求,该请求的目标 MAC 地址是"FF.FF.FF.FF.FF.FF",目标 IP 是主机 B 的 IP。
  3. 只有主机 B 会接收这个请求,并且向主机 A 做出回应(ARP response),而其他主机接收到请求之后发现目标 IP 不是自己,就会选择丢弃。主机 B 从请求中获得主机 A 的 MAC 地址和 IP 地址,所以会以单播的方式进行回应,同时更新自己的 ARP 缓存表。
  4. 主机 A 接收到主机 B 的响应之后,也会更新自己的 ARP 缓存,下次再访问主机 B 时,就直接从 ARP 缓存里查找即可。

ARP缓存超时: 既然是缓存表,意味着有时效性。ARP 缓存表采用老化机制,在一段时间内如果表中的某一条目没有使用,就会被删除,这样可以减少缓存表的长度,加快查询速度。

缓存时间,一般是 20 分钟 ,但 RFC 也有规定一些不完整条目是 3 分钟,什么是不完整条目,比如下面这条,对一个不存在的主机执行了一次 ARP 请求,就是一条不完整的条目:

# arp -a
?(10.0.0.99) at <incomplete> on eth0

 

ARP 报文格式长啥样


首先看一个以太网帧中,ARP 报文处在一个什么位置。

以下是一个以太网帧的简化图,这里只是为了说明 ARP 报文在整个以太网帧中所处的位置,所以这个以太网帧并不是完整的。

详细的网络协议之ARP请求_数据_03

我们看到,以太网帧通过一个 2 字节的帧类型来表示它后面的数据负载(payload)部分具体是什么类型的数据,如果帧类型是 16进制的 0x0800,则表示 payload 是 IP 报文,如果是 0x0806,就表示 ARP 报文,包括 ARP 请求报文和应答报文。当然,这里还有其他类型的数据,比如 ARAP,我们就不一一列出了。

下面,继续走进 ARP 报文内部,看看它包含哪些字段:

详细的网络协议之ARP请求_封装_04

 

  • 硬件类型:指出硬件地址类型,对于以太网,该值是 1
  • 协议类型:指出网络层协议类型,对于 IPv4,该值是 0x0800
  • 硬件大小和协议大小:分别指出后面的硬件地址和协议地址的大小,对于使用 IPv4 的 ARP 报文,该值分别是 6 和 4
  • Op:指出该报文是 ARP 请求(值为 1)、ARP 应答(2)、RARP 请求(3) 或 RARP 应答(4)
  • 后面的 4 个字段和前面 4 个字段对应,分别表示源目的 MAC 和源目的 IP 地址

我们通过 Wireshark 抓包看看。

ARP 请求报文:

详细的网络协议之ARP请求_LVS_05

ARP 应答报文:

详细的网络协议之ARP请求_LVS_06

可以看到 Op 字段,对于 ARP 请求和应答报文分别为 1 和 2,其他字段大家也可以看看,比较直观。

 

有哪些命令可以操作 ARP


① arp

用法:

# arp (选项)(参数)

-a <主机IP>:显示 arp 缓冲区中的所有条目
-e:以 Linux 的显示风格显示 arp 缓冲区中的条目
-v:显示详细的 arp 缓冲区条目,包括缓冲区条目的统计信息
-i <接口>:显示指定接口的 arp 缓存条目
-s <主机IP><MAC>:配置主机的 IP 地址与 MAC 地址的静态映射
-d <主机>:删除 arp 条目
-f <文件>:从文件 /etc/ethers(默认)或者指定文件中记录的 arp 条目配置静态映射

② ip neigh

​ip neigh​​​ 是和 ​​arp​​​ 相同功能但不同用法的另一套命令,全称为 ​​ip neighbour​​。

用法:

# ip neigh help
ip neigh { add | del | change | replace }
{ ADDR [ lladdr LLADDR ] [ nud STATE ] | proxy ADDR } [ dev DEV ]
ip neigh { show | flush } [ proxy ] [ to PREFIX ] [ dev DEV ] [ nud STATE ][ vrf NAME ]

# 其中几个选项的意思解释
- to ADDR: 协议地址,指定主机的 IP 地址
- dev DEV: 指定网口
- lladdr LLADDR: 指定 MAC 地址
- nud STATE: 状态值,有以下几个值:
- permanent:永久有效
- noarp:有效,不会验证此条目,但可以在其生命周期到期时将其删除
- reachable:有效,直到可达性超时到期
- stale:有效但可疑
- all:列出所有状态

# 常用操作

1. 列出所有 arp 条目
# ip neigh show

2. 添加 arp 静态条目
# ip neigh add 192.168.10.11 lladdr 00:01:02:03:04:05 dev eth0

3. 删除指定 IP 或者网口的 arp 条目
# ip neigh del 192.168.10.11 dev eth0

4. 刷新 arp 表
# ip neigh flush 192.168.10.11

# 此外还有两个命令,可以更改 arp 条目
# ip neighbour change - 更改
# ip neighbour replace - 添加新的或更改

③ arping

​arping​​​ 和 ​​ping​​ 相似,主要是通过发送 ARP 请求查看 IP 和 MAC 的映射关系,以及探测 IP 的冲突问题。更多的用法可以参见这篇文章 xxx

④ arpwatch

​arpwatch​​ 主要用于监听网络中的 ARP 数据的变化关系并记录,可以将监听到的变化通过 E-mail 发送。

用法:

# arpwatch [-d][-f <记录文件>][-i <接口>][-r <记录文件>]

监听 eth0 的 ARP 信息
# arpwatch -i eth0

将监听到的 ARP 记录存储到文件
# arpwatch -i eth0 -f /var/arpwatch/arp.dat

从指定文件中读取 ARP 记录,而不是从网络中监听
# arpwatch -r /var/arpwatch/arp.dat

 

ARP 有哪些内核配置参数


从上文我们知道,ARP 是一个无状态协议,就是说它不管自己是否发过请求,也不管应答是否合理,再加上广播的特性,有时候通信上会出现一些匪夷所思的问题,系统会通过以下几个配置参数来规避这些问题。

要说清楚这些参数,我们先上个图:

详细的网络协议之ARP请求_LVS_07

在这样一个局域网的图示中,B 向 A 的 eth0 接口发送 ARP 请求,由于是广播,并且 IP 地址针对的是整个主机,而不是特定的某个接口,所以,A 的两个口 eth0 和 eth1 都会接收请求并发出应答,而 B 则以最后应答的 MAC 地址为准。

这就会带来第一个问题,如果 B 本来想获取 eth0 的 MAC 地址,但却得到 eth1 的,这就会导致后续发送数据的不准确。

解决这个问题可以用两个内核参数,这两个参数都是在 ARP 应答上做文章,让它能够正确应答。

① arp_ignore

  • 0 - (默认值): 回应任何网络接口(网卡)上对任何本机IP地址的arp查询请求。比如eth0=192.168.0.1/24,eth1=10.1.1.1/24,那么即使eth0收到来自10.1.1.2这样地址发起的对10.1.1.1 的arp查询也会给出正确的回应;而原本这个请求该是出现在eth1上,也该有eth1回应的。
  • 1 - 只回答目标IP地址是本机上来访网络接口(网卡)IP地址的ARP查询请求 。比如eth0=192.168.0.1/24,eth1=10.1.1.1/24,那么即使eth0收到来自10.1.1.2这样地址发起的对192.168.0.1的查询会回应,而对10.1.1.1 的arp查询不会回应。
  • 2 -只回答目标IP地址是本机上来访网络接口(网卡)IP地址的ARP查询请求,且来访IP(源IP)必须与该网络接口(网卡)上的IP(目标IP)在同一子网段内 。比如eth0=192.168.0.1/24,eth1=10.1.1.1/24,eth1收到来自10.1.1.2这样地址发起的对192.168.0.1的查询不会回应,而对192.168.0.2发起的对192.168.0.1的arp查询会回应。
  • 3 - do not reply for local addresses configured with scope host,only resolutions for global and link addresses are replied。(不知道怎么翻译合适,网上有一个参考但我认为无法理解它的含义:不回应该网络界接口的arp请求,而只对设置的唯一和连接地址做出回应)
  • 4-7 - 保留未使用
  • 8 -不回应所有(本机地址)的arp查询

在设置参数的时候将arp_ignore 设置为1,意味着当别人的arp请求过来的时候,如果接收的网络接口卡上面没有这个ip,就不做出响应。默认是0,只要这台机器上面任何一个设备上面有这个ip,就响应arp请求,并发送mac地址。

 

在内核参数中,除了每个网口都有自己的 ​​arp_ignore​​​ 配置外,还有两个(一个是默认 default,一个是全局 all),如下。一般,如果网口自身的 ​​arp_ignore​​ 不配置的情况下,使用 default 的配置,如果 all 和网口自身都配置的情况下,使用两者中较大的那一个。

# sysctl -a | grep arp_ignore
net.ipv4.conf.default.arp_ignore=1
net.ipv4.conf.all.arp_ignore=1
net.ipv4.conf.lo.arp_ignore=1
net.ipv4.conf.eth0.arp_ignore=1
net.ipv4.conf.eth1.arp_ignore=1

关于配置这块,有临时生效和永久生效的配置方式,文末有进一步说明。

② arp_filter

这个参数和 ​​arp_ignore​​​能够达到相同的目的,但 ​​arp_filter​​会更严格一些,它在选网口应答的时候会按照路由来选。它是一个 bool 值,具体为:

  • 0:默认值,所有网口都可以响应 ARP 请求
  • 1:会根据本机路由表来验证哪个网口应该进行应答,优先选择在路由表前面的网口,也就是会有一个反向路由验证的过程。

同样,他也有以下的参数:

$ sysctl -a | grep arp_filter
net.ipv4.conf.all.arp_filter = 0
net.ipv4.conf.default.arp_filter = 0
net.ipv4.conf.eth0.arp_filter = 0
net.ipv4.conf.eth1.arp_filter = 0
net.ipv4.conf.lo.arp_filter = 0

上面两个参数解决的是 ARP 应答方向上的问题,而下面的 ​​arp_announce​​ 是解决 ARP 请求方向上的问题。它可以控制本机发出的 ARP 请求中的源 IP 地址使用哪个网口的地址。

③ arp_announce

它有以下 3 个整数值:

  • 0:默认值,可以在任意网口上发送 ARP 请求,但是请求的源 IP 地址与当前发送的网口没有关系。也就是说如果源 IP 地址与当前发送网口的地址不同(使用其他网口作为源 IP 地址),使用原本的源 IP 地址。
  • 1:要求尽量避免使用不在当前发送网口子网段内的 IP 地址作为 ARP 请求的源 IP 地址,也就是说系统会遍历所有网口的 IP,然后看哪个网口的 IP 和 ARP 请求包中的源 IP 在同一个网段内,优先选择哪个进行发送,如果都不在,就采用下面级别 2 的方式来选择。
  • 2:1 是看源 IP 地址,这种方式是看目标 IP 地址,选择与目标 IP 地址在同一个网段内的网口 IP 作为 ARP 请求的源 IP 地址,如果还是没找到,则直接选择当前网口的 IP 或者能够接受到 ARP 回应的网口 IP 来发送 ARP 请求,并设源 IP 为发送网口的 IP。

此外,还有两个参数:

④ arp_accept

默认对不在 ARP 表中的 IP 地址发出的 APR 包的处理方式

  • 0:不在 ARP 表中创建对应 IP 地址的表项
  • 1:在 ARP 表中创建对应 IP 地址的表项

⑤ arp_notify

ARP 通知链操作

  • 0:不做任何操作
  • 1:当设备或硬件地址改变时自动产生一个 ARP 请求

 

修改方法


临时修改方法:

 1. 修改/proc文件系统:

echo "1">/proc/sys/net/ipv4/conf/all/arp_ignore
echo "1">/proc/sys/net/ipv4/conf/eth0/arp_ignore
echo "2">/proc/sys/net/ipv4/conf/all/arp_announce
echo "2">/proc/sys/net/ipv4/conf/eth0/arp_announce

 2. 使用sysctl -w直接写入内存:

sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.eth0.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
sysctl -w net.ipv4.conf.eth0.arp_announce=2

永久修改需要写入配置文件:

修改/etc/sysctl.conf文件,然后sysctl -p刷新到内存

net.ipv4.conf.all.arp_ignore=1
net.ipv4.conf.eth0.arp_ignore=1
net.ipv4.conf.all.arp_announce=2
net.ipv4.conf.eth0.arp_announce=2

备注:arp_ignore和arp_announce参数分别有all,default,lo,eth0等对应不同网卡。当all和具体网卡的参数值不一致时,配置为较大值的生效。一般只需修改all和某个具体网卡的参数即可。

然后一定记得执行 ​​sysctl -p​​ 使之生效。