1. ARP协议简介
ARP(Address Resolution Protocol)协议称为地址解析协议,用于将主机IP地址解析为主机的MAC地址,即IP-->MAC
之间一一映射。
RARP协议相反,是将MAC地址解析为IP地址,MAC——>IP
ARP解析时分两种情况:
- 解析目标和自己在同一网段。
A解析同网段的B,A根据自己的IP和子网掩码判断B和自己同网段,这时A就直接在这个网段上发一个ARP广播包寻求B的MAC地址,所有人都收到广播信息,但是B会将MAC地址回应给A,A缓存B的MAC地址。 - 解析目标和自己不在同一网段。
A根据自己的IP和子网掩码判断出B和自己不在同一个网段,这时A就向自己的网段发送一个ARP广播包用来解析网关的MAC地址,也就是路由器的接口MAC地址,然后路由器回应,A缓存回应的MAC结果。
当发送ARP请求广播后,目标设备会进行应答,其中请求数据包和应答数据包的格式非常接近。以下是请求包和应答包数据格式的一部分,完整格式请百度或者翻阅TCP/IP协议卷(一)。
其中:
- op字段是一个1-4的值,1表示该数据帧是ARP请求包,2表示该数据帧是ARP应答包,3和4则表示RARP的请求和应答包。
- src_MAC和src_IP是数据帧中的源MAC和源IP地址。这两个字段的值不一定是对应的,意思是src_IP不一定配置在src_MAC地址的接口上。
- dest_MAC和dest_IP则是目标MAC地址和目标IP地址。对于ARP请求包,dest_MAC值为"ff:ff:ff:ff:ff:ff",表示这是广播包。同样,这两个字段的值也并非是对应的。
当发送ARP请求广播包时,op的值设置为1,目标MAC设置为广播地址"ff:ff:ff:ff:ff:ff",然后在局域网内广播,这是在询问"who has DEST_IP"。每台主机都能收到该广播包,但只有设置了目标IP的主机才会应答:"Reply DEST_IP is-at DEST_MAC"。应答时使用单播包进行回应,会将op值改为2,表示这是应答包,同时将应答的MAC地址替换原来的"ff:ff:ff:ff:ff:ff",并将src和dest的字段位置进行调换。如下图:
当响应者接收到请求者的ARP请求时,它会将请求包中的源MAC和源IP缓存到ARP缓存表中。
当请求者接收到响应者的应答包时,它会将应答包中的源MAC地址和源IP地址缓存到ARP缓存表中。也就是说,一次arp请求,会让两端主机都缓存对方的IP和MAC地址。
使用ping命令或其他TCP连接时,两端都会缓存对方的ARP条目。但为了测试,可以手动使用arping命令发送一个自定义源MAC和源IP的arp请求让对方缓存自己的IP和MAC。
假如主机A上有eth0(192.168.100.54)和eth1两网卡,主机B有eth0(192.168.100.70)。
下面的命令表示,在主机A上向主机B发送一个arp广播包(如果"-c N"的N大于1,则只有第一个请求包是广播,其他是单播),其中源MAC为eth1网卡的MAC,但源IP为eth0上的IP地址192.168.100.54。这会使得主机B缓存的arp条目为192.168.100.54<-->eth1_MAC
,但实际上这并非正确的映射关系。
arping -c 1 -I eth1 -s 192.168.100.54 192.168.100.70
有些程序可以检测到IP地址冲突的现象。典型的如DHCP服务器准备提供IP地址给客户端之前,会发送一个arp广播,以便确认该IP地址是否已被其他主机使用(例如其他主机使用静态IP时手动输入了该IP)。如果没有收到回应,则表示该IP地址没有被使用,可以提供给客户端使用,如果收到了回应,则表示该IP地址已经被使用了,DHCP会从IP池中换一个IP提供给客户端。
arp -a(或arp -e)
命令可以显示arp缓存表的内容,arp -d ADDR
可以删除ARP缓存表中某条ARP记录,这两命令对于Windows和Linux系统都可用。
此外,
对于Windows,arp -d *
表示删除arp缓存表中的所有记录,
对于Linux,则使用ip neigh flush all
命令来删除arp缓存中所有记录。
注意,无论是arp请求还是arp应答,都带有完整的源MAC、源IP、目标MAC和目标IP。这看似一句废话,但不熟知arp请求的人很容易因此而陷入困惑。
2. arp_ignore和arp_announce变量的作用分析
在设置VS/TUN和VS/DR时,都需要设置到这两个arp相关的内核变量,所以这里先解释解释。
前文已经说明了arp请求包或应答包中,MAC地址可以和IP地址不对应。
这样一来就出现问题了,
ARP请求包中,使用哪个源IP地址以及哪个源MAC地址?
ARP应答包中,使用哪个源MAC和源IP地址(注意,应答包中源IP地址并不一定是请求包中的目标IP,可能会更换为本机的其他IP地址)?
arp_ignore和arp_announce这两个变量的作用正是设置使用哪个源IP和哪个源MAC
arp_ignore设置的是收到请求包后,在应答包中将本机的哪个IP地址和MAC地址回应给请求者以供缓存;
arp_announce设置的是发出请求包时,选择哪个IP和MAC地址供响应者缓存,这也符合announce的字面意思:向外通告本机的哪个IP地址和MAC地址供其他主机缓存。
它们的作用如下图所示:
再来细看arp_ignore和arp_announce的介绍。
2.1 arp_ignore
arp_ignore - INTEGER
Define different modes for sending replies in response to
received ARP requests that resolve local target IP addresses:
0 - (default): reply for any local target IP address, configured
on any interface
1 - reply only if the target IP address is local address
configured on the incoming interface
2 - reply only if the target IP address is local address
configured on the incoming interface and both with the
sender's IP address are part from same subnet on this interface
3 - do not reply for local addresses configured with scope host,
only resolutions for global and link addresses are replied
4-7 - reserved
8 - do not reply for all local addresses
The max value from conf/{all,interface}/arp_ignore is used
when ARP request is received on the {interface}
大致翻译一下:
该变量接受一个整数值。定义的是当本机接收到别的主机发送的ARP请求时的不同应答模式:回应哪个IP和MAC地址给请求者。
- 0 - (default):将本机所有非lo接口的IP地址都回应出去。
- 1 - 只有当ARP请求中的目标IP地址配置在流入接口上时才响应。
- 2 - 只有当ARP请求中的目标IP地址配置在流入接口上时,且该IP地址(即发送者源地址)是接口地址上的子网地址时才响应(例如192.168.100.10/24和192.168.100.10/16的关系)。
- 3 - do not reply for local addresses configured with scope host, only resolutions for global and link addresses are replied。
- 4-7 - 保留
- 8 - 不回应任意地址。
- 当某接口接收到ARP请求时,将取conf/{all,interface}/arp_ignore中值最大的生效。
注意:这里的是否响应,指的不是是否构建响应,而是路由决策后是否让构建好的响应包出去。换句话说,arp响应包是一定会构建的,但是构建好的响应包根据arp_ignore设置的值不同,不一定能出的去。可见稍后arp_ignore=1的示例分析。
举个例子来解释值为0和1的情况。
(1)arp_ignore=0时,当主机收到arp请求时,会将本机任意可能的IP地址(lo除外)都应答给arp请求者。
例如,主机A有3个网卡,eth0(192.168.100.17/24),eth1(192.168.100.36/24)和eth2(172.16.10.10/16),主机B有网卡eth0(192.168.100.39)。当主机B发起对192.168.100.36(eth1)或17(eth0)的ping时,由于主机A回应icmp响应给主机B时,默认情况下有以下路由:
[root@xuexi ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.100.2 0.0.0.0 UG 100 0 0 eth0
0.0.0.0 192.168.100.2 0.0.0.0 UG 101 0 0 eth1
0.0.0.0 192.168.100.2 0.0.0.0 UG 102 0 0 eth2 这三条是默认路由
172.16.0.0 0.0.0.0 255.255.0.0 U 100 0 0 eth2
192.168.100.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
192.168.100.0 0.0.0.0 255.255.255.0 U 101 0 0 eth1
192.168.100.2 0.0.0.0 255.255.255.255 UH 100 0 0 eth2
这时主机A会将eth0和eth1上的IP地址都应答给主机B,且这两个IP地址对应的MAC地址都是eth0(因为该接口是该网段的第一条路由出口)的MAC地址,因为无论请求的是哪个目标IP,构建的arp响应都能从eth0出去,而eth0的arp_ignore=0,允许任意目标的响应包出去。
以下是主机B上ping 192.168.100.36后的arp缓存表,如果结果不同,请 ip neigh flush all 清空缓存表或者多等待一段时间再测试。
[root@xuexi ~]# ip n s 192.168.100.36 dev eth0 lladdr 00:0c:29:fb:dd:04 REACHABLE 192.168.100.1 dev eth0 lladdr 00:50:56:c0:00:08 REACHABLE 192.168.100.17 dev eth0 lladdr 00:0c:29:fb:dd:04 STALE
如果将主机A上192.168.100.0 eth0的路由条目删除,则主机B ping 192.168.100.36时,主机A将不再把eth0(192.168.100.17)响应给主机B,尽管它可以使用eth1对应的路由出去。但如果主机B ping 192.168.100.17,那么也会将该地址响应出去,使用的源MAC地址也是eth1(因为路由出口为eth1)。因此,主机B只会缓存主机A的192.168.100.36 eth1_MAC。
由此可知,所谓响应任意可能的IP地址并不是响应所有地址,lo接口、非同一网段地址以及无第一路由的接口地址就不会主动响应出去。同样,那些定义在接口上的别名地址也默认不会响应出去,因为它们的数据的流入流出都是通过它所依附的接口。
(2)arp_ignore=1时,当主机收到arp请求时,只有arp请求包中目标IP和流入接口上的IP相同时,才会响应该IP以及该接口的MAC。
例如,设置eth0网卡的该变量值为1。不过,为了完善测试,先将上面示例中删除的路由条目添加回来。
route add -net 192.168.100.0/24 dev eth0
echo 1 >/proc/sys/net/ipv4/conf/eth0/arp_ignore
这时主机B上ping 192.168.100.17(eth0)时,主机A将只会响应eth0_MAC给主机B,且ping 192.168.100.36时将ping不通(这一点需要注意,在后面配置LVS的arp参数时,外界主机往往只能ping通同网段的其中一个地址)。以下是在主机B上ping 192.168.100.36(eth1)时在主机A上抓取的数据包。
[root@xuexi ~]# tcpdump -nn host 192.168.100.39 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 12:21:51.739967 ARP, Request who-has 192.168.100.36 tell 192.168.100.39, length 46 12:21:52.739741 ARP, Request who-has 192.168.100.36 tell 192.168.100.39, length 46 12:21:53.739868 ARP, Request who-has 192.168.100.36 tell 192.168.100.39, length 46 12:21:55.742680 ARP, Request who-has 192.168.100.36 tell 192.168.100.39, length 46 12:21:56.743560 ARP, Request who-has 192.168.100.36 tell 192.168.100.39, length 46 12:21:57.743086 ARP, Request who-has 192.168.100.36 tell 192.168.100.39, length 46 ^C 6 packets captured 6 packets received by filter 0 packets dropped by kernel
从结果中可以看出,主机A收到目标IP为192.168.100.36的ARP请求后,并没有响应给主机B。因为主机B发出的ARP请求包到达主机A时的流入接口为eth1,构建arp响应包后,经过路由决策从eth0出去,而eth0上的arp_ignore=1,这个arp响应包将响应出不去,主机B也就不知道192.168.100.36的MAC地址,从而无法与A主机通信。
这里的关键流入接口是eth1,但arp响应包的流出接口是eth0,eth0的arp_ignore=1意味着只有从eth0流入的arp请求,构建的arp响应包才允许出去。尽管ping的目标是eth0同网段的eth1:192.168.100.36。也就是说,只要eth0的arp_ignore=1,那么该主机上该网段的所有地址(除了配置在eth0上的IP)都无法与外界通信。
此时,如果将eth0的路由条目删除,或者将eth0的路由优先级设置的比eth1的路由优先级更低(例如将eth0路由的metric的值设置的比eth1的metric更大),那么arp响应包的流出接口将是eth1,这时主机B ping主机A的eth0_IP或eth1_IP都能通,且arp缓存表中,这两个地址对应的MAC地址都是eth1_MAC。
再继续,如果没有eth0的路由,或者eth0的路由优先级比eth1低,且还设置了eth1的arp_ignore=1,此时主机B将只能ping通eth1_IP,因为ping eth0_IP的arp请求从eth0而不是eth1流入,但它的响应包经过路由决策后却要从eth1出去,eth1的arp_ignore=1不会允许这样的arp响应包出去。
因此在arp请求过程中,目标主机路由表中路由的先后顺序非常重要,它不仅决定了数据从哪流出,更深层面上还决定了流出时使用哪个MAC地址,而这直接决定是否能成功ARP请求、ARP响应以及arp缓存的 IP<-->MAC 映射结果。如果你在学习arp_ignore和arp_announce时做了很多测试,必然能体会到这一点。
2.2 arp_announce
arp_announce - INTEGER
Define different restriction levels for announcing the local
source IP address from IP packets in ARP requests sent on
interface:
0 - (default) Use any local address, configured on any interface
1 - Try to avoid local addresses that are not in the target's
subnet for this interface. This mode is useful when target
hosts reachable via this interface require the source IP
address in ARP requests to be part of their logical network
configured on the receiving interface. When we generate the
request we will check all our subnets that include the
target IP and will preserve the source address if it is from
such subnet. If there is no such subnet we select source
address according to the rules for level 2.
2 - Always use the best local address for this target.
In this mode we ignore the source address in the IP packet
and try to select local address that we prefer for talks with
the target host. Such local address is selected by looking
for primary IP addresses on all our subnets on the outgoing
interface that include the target IP address. If no suitable
local address is found we select the first local address
we have on the outgoing interface or on all other interfaces,
with the hope we will receive reply for our request and
even sometimes no matter the source IP address we announce.
The max value from conf/{all,interface}/arp_announce is used.
Increasing the restriction level gives more chance for
receiving answer from the resolved target while decreasing
the level announces more valid sender's information.
大致翻译一下:该变量接受一个整数值。它定义的是当发送ARP请求时,在请求数据包中填入的源IP地址和源MAC地址,它们是被响应者缓存的内容。
- 0 - (default)可以使用本机上任意接口的任意地 址。
-
1 - 尽量不使用和目标IP不在同一子网的地址。当目标主机可以通过该接口达到,但要求ARP数据包中的源IP地址是逻辑网络接口网段中的地址时,设置为该级别很有用。当生成ARP请求数据包时,将检测所有包含目标IP的子网(自身网段或子网都可以),如果源IP地址处于该子网内,则使用该地址。如果没有包含该源IP地址的子网,则使用级别2(arp_announce=2)来处理。
-
2 - 总是为目标地址寻找最佳本地地址作为ARP请求的源IP地址。这种模式下,将忽略源IP地址,而是尝试选择出能和目标IP最佳通信质量的IP地址。这个IP是通过寻找各流出接口上的主IP地址(primary IP,不能是secondary IP)得到的,它需要和目标IP地址在同网段或属于其子网内。如果没有选出合适的地址,将选择第一个流出接口上的IP地址,这样不仅可以接收到应答包,还能无视已经手动通告的源地址。
稍微解释下:
arp_announce=0
时,向外发送ARP请求时,很可能会使用流出接口的IP地址和MAC地址,这没有硬性限制。arp_announce=1
时,尽量使用与目标IP地址在同一子网的地址,例如目标IP地址为192.168.100.40/16,而本机有IP地址192.168.100.22/24,这个IP地址是目标IP地址子网内的一个地址,因此会尽量使用该地址作为ARP请求中的源IP地址,但是源MAC地址还是数据流出接口上的MAC。arp_announce=2
时,不管ARP请求包中指定的源IP地址是什么(因为ARP请求包中的源IP和源MAC可以手动指定),总会在本地搜索出和目标IP最匹配的IP地址来作为源地址。它会优先选和目标IP同子网的本地IP,如果没有则选路由表中的第一个流出接口上的IP。
例如,如果Linux主机有3个网卡:eth0(IP0)、eth1(IP1)和eth2(IP2)。如果想通过eth2接口流出源地址为IP0的ARP请求广播包,默认情况下是行不通的。因为默认情况下,使用eth2流出ARP请求的源IP地址必须使用IP2。因此必须设置arp_announce=1或2,其实设置为1时也只是有机会流出,因为它要判断IP0和目标IP地址是否存在子网所属关系。只有设置arp_announce=2才必然能流出,但这时该Linux主机向外通告的IP地址将不是IP0,而是IP2。
3.设置arp_ignore和arp_announce
Linux内核2.0.xx版本中,回环接口、回环别名接口(如lo:0,lo:1)以及回环隧道接口都不会做arp回应,对于LVS集群来说,这很方便。
但从Linux 2.2.xx开始,除了回环地址(127.0.0.0/8)和广播地址外,其他所有地址(包括回环接口上的别名接口)都会做arp回应。因此,在这样的内核版本下配置LVS可能会出现一些问题。
从Linux内核2.2.14开始,提供了一个接口标记"hidden"用于从ARP广播中隐藏指定接口。
这意味着对于现在的CentOS6、CentOS7来说,虽然每个接口(包括lo接口)都可以设置这两个变量,但这两个变量只对能arp回应的接口才生效(如eth0,eth1等对外通信的普通接口)。也就是说,在lo接口上设置arp_ignore、arp_announce等arp参数是没有意义的。
尽管对lo接口设置arp参数没有意义,但为了保证lo和普通网卡、隧道设置方法的统一性,以及未来的内核可能对此做出改变,本文以及网上的文章还是对它进行了同样的设置。
例如,设置lo接口的arp_ignore=1、arp_announce=2。
echo 1 >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo 2 >/proc/sys/net/ipv4/conf/lo/arp_announce
如果在接口上设置了别名IP,例如eth0:0,由于它们仍然使用所依附的接口流入流出数据,因此在接口上设置arp_ignore和arp_announce对别名IP同样生效。
其实,在/proc/sys/net/ipv4/conf下,除了各网卡接口的配置目录,还有default和all两个目录,这两个目录内关于arp参数的值只影响普通网卡,不影响lo接口,也没有意义去影响lo接口。
[root@xuexi ~]# ls -l /proc/sys/net/ipv4/conf/
total 0
dr-xr-xr-x 1 root root 0 Feb 15 2018 all
dr-xr-xr-x 1 root root 0 Feb 15 2018 default
dr-xr-xr-x 1 root root 0 Feb 14 22:56 eth0
dr-xr-xr-x 1 root root 0 Feb 14 22:56 eth1
dr-xr-xr-x 1 root root 0 Feb 14 22:56 lo
其中:
- default目录中的变量值为普通网络接口提供初始化值(不影响lo接口)。这个目录其实没什么用,因为每次重启操作系统,/proc/sys下的设置都会失效,而直接设置该目录下的值又起不到提供初始化值的作用。之所以放在conf目录内是为了提示我们可以设置default属性的值。例如,向sysctl.conf中追加永久设置
net.ipv4.conf.default.arp_ignore=1
,这样每次重启系统后各网卡接口的arp_ignore级别都是1(注意:普通网卡才生效,lo接口不受影响)。 - all目录中的变量作用范围是所有网卡(不包括lo接口)。
对于每个网卡来说,将比较all目录中的变量值和网卡自身的变量值,取较大值。例如:
conf/eth0/arp_ignore值为1,conf/all/arp_ignore值为0,则对于eth0接口来说,arp_ignore=1。
conf/lo/arp_announce值为0,conf/all/arp_announce值为2,则对于lo接口来说,arp_announce=0,因为all目录不影响lo接口。但最终,lo接口上设置的arp参数值是没有意义的。
通常,VS/TUN和VS/DR模式下,Real Server上的VIP设置在lo的别名接口上(如lo:0上),因此应该如下设置:
echo 1 >/proc/sys/net/ipv4/conf/lo/arp_ignore
echo 1 >/proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 >/proc/sys/net/ipv4/conf/lo/arp_announce
echo 2 >/proc/sys/net/ipv4/conf/all/arp_announce
其中生效的为第二条和第四条规则,第一条和第三条对lo接口的设置语句可有可无。
将conf/all/arp_ignore设置为1,可以保证无论哪个对外通信的网卡接口都只会向外响应自己接口上的IP地址(甚至可能有些同网段的接口因为路由顺序排在后面而响应不出去),这样就隐藏了设置在lo别名接口上的VIP地址。
将conf/all/arp_announce设置为2,可以保证本机只向外通告普通网卡上的IP地址,lo别名接口上的VIP不可能被通告出去。