第 D-1 章 Linux arp 应用

作者: Brinnatt 分类: ARM64 Linux 补充知识 发布时间: 2022-01-21 21:13

D1、ARP 协议简介

ARP(Address Resolution Protocol)协议称为地址解析协议,用于将主机 IP 地址解析为主机的 MAC 地址,即 IP<-->MAC 之间一一映射。RARP 协议相反,是将 MAC 地址解析为 IP 地址。

ARP 解析时分两种情况:

  1. 解析目标和自己在同一网段,A 解析同网段的 B,A 根据自己的 IP 和子网掩码判断 B 和自己同网段,这时 A 就直接在这个网段上发一个 ARP 广播包寻求 B 的 MAC 地址,所有人都收到广播信息,但是 B 会将 MAC 地址回应给 A,A 缓存 B 的 MAC 地址。
  2. 解析目标和自己不在同一网段,A 根据自己的 IP 和子网掩码判断出 B 和自己不在同一个网段,这时 A 就向自己的网段发送一个 ARP 广播包用来解析网关的 MAC 地址,也就是路由器的接口 MAC 地址,然后路由器回应,A 缓存回应的 MAC 结果。

当发送 ARP 请求广播后,目标设备会进行应答,其中请求数据包和应答数据包的格式非常接近。以下是请求包和应答包数据格式的一部分,完整格式请自行查阅。

arp format

  • 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 req-rep

  • 当响应者接收到请求者的 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 广播包,其中源 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
    • 如果 "-c N" 的 N 大于 1,则只有第一个请求包是广播,其他是单播。

有些程序可以检测到 IP 地址冲突的现象。典型的如 DHCP 服务器准备提供 IP 地址给客户端之前,会发送一个 arp 广播,以便确认该 IP 地址是否已被其他主机使用(例如其他主机使用静态 IP 时手动输入了该 IP)。

如果没有收到回应,则表示该 IP 地址没有被使用,可以提供给客户端使用,如果收到了回应,则表示该 IP 地址已经被使用了,DHCP 会从 IP 池中换一个 IP 提供给客户端。

arp -a 命令可以显示 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 请求的人很容易因此而陷入困惑。

D2、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 ann-ign

D2.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 地址给请求者。

注意:这里的是否响应,指的不是是否构建响应,而是路由决策后是否让构建好的响应包出去。换句话说,arp 响应包是一定会构建的,但是构建好的响应包根据 arp_ignore 设置的值不同,不一定能出的去。可见稍后 arp_ignore=1 的示例分析。

  1. arp_ignore=0 时,当主机收到 arp 请求时,会将本机任意可能的 IP 地址都应答给 arp 请求者。

    例如,主机 test1 有 3 个网卡,ens32(192.168.0.196),ens34(192.168.95.136),ens35(192.168.95.137);主机 test2 有网卡 ens34(192.168.95.134)。当 test2 去 ping test1 上的 192.168.95.137/136 时,test1 回应 icmp 响应给主机 test2,默认情况下有以下路由:

    # test1
    [root@test1 ~]# route -n
    Kernel IP routing table
    Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
    0.0.0.0         192.168.95.2    0.0.0.0         UG    0      0        0 ens35
    169.254.0.0     0.0.0.0         255.255.0.0     U     1002   0        0 ens32
    169.254.0.0     0.0.0.0         255.255.0.0     U     1003   0        0 ens34
    169.254.0.0     0.0.0.0         255.255.0.0     U     1004   0        0 ens35
    192.168.0.0     0.0.0.0         255.255.255.0   U     0      0        0 ens32
    192.168.95.0    0.0.0.0         255.255.255.0   U     0      0        0 ens34
    192.168.95.0    0.0.0.0         255.255.255.0   U     0      0        0 ens35
    [root@test1 ~]#
    [root@test1 ~]# ip ad
    1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
       link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
          valid_lft forever preferred_lft forever
       inet6 ::1/128 scope host 
          valid_lft forever preferred_lft forever
    2: ens32:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
       link/ether 00:0c:29:fa:f1:15 brd ff:ff:ff:ff:ff:ff
       inet 192.168.0.196/24 brd 192.168.0.255 scope global dynamic ens32
          valid_lft 1615sec preferred_lft 1615sec
       inet6 fe80::20c:29ff:fefa:f115/64 scope link 
          valid_lft forever preferred_lft forever
    3: ens34:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
       link/ether 00:0c:29:fa:f1:1f brd ff:ff:ff:ff:ff:ff
       inet 192.168.95.136/24 brd 192.168.95.255 scope global dynamic ens34
          valid_lft 1617sec preferred_lft 1617sec
       inet6 fe80::20c:29ff:fefa:f11f/64 scope link 
          valid_lft forever preferred_lft forever
    4: ens35:  mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
       link/ether 00:0c:29:fa:f1:29 brd ff:ff:ff:ff:ff:ff
       inet 192.168.95.137/24 brd 192.168.95.255 scope global dynamic ens35
          valid_lft 1619sec preferred_lft 1619sec
       inet6 fe80::20c:29ff:fefa:f129/64 scope link 
          valid_lft forever preferred_lft forever
    [root@test1 ~]#
    
    # test2
    [root@test2 ~]# ip ad
    1: lo:  mtu 65536 qdisc noqueue state UNKNOWN qlen 1
       link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
       inet 127.0.0.1/8 scope host lo
          valid_lft forever preferred_lft forever
       inet6 ::1/128 scope host 
          valid_lft forever preferred_lft forever
    2: ens32:  mtu 1500 qdisc noop state DOWN qlen 1000
       link/ether 00:0c:29:4c:1d:3b brd ff:ff:ff:ff:ff:ff
    3: ens34:  mtu 1500 qdisc pfifo_fast state UP qlen 1000
       link/ether 00:0c:29:4c:1d:45 brd ff:ff:ff:ff:ff:ff
       inet 192.168.95.134/24 brd 192.168.95.255 scope global dynamic ens34
          valid_lft 1588sec preferred_lft 1588sec
       inet6 fe80::20c:29ff:fe4c:1d45/64 scope link 
          valid_lft forever preferred_lft forever
    4: ens35:  mtu 1500 qdisc noop state DOWN qlen 1000
       link/ether 00:0c:29:4c:1d:4f brd ff:ff:ff:ff:ff:ff
    [root@test2 ~]#
    • test1 和 test2 同一网段都在同一局域网,相当于直连。

    • 这时主机 test1 会将 ens34 和 ens35上的 IP 地址都应答给主机 test2,且这两个 IP 地址对应的 MAC 地址都是 ens34(因为该接口是该网段的第一条路由出口)的 MAC 地址,因为无论请求的是哪个目标 IP,构建的 arp 响应都能从 ens34 出去,而 ens34 的 arp_ignore=0,允许任意目标的响应包出去。以下是主机 test2 上 ping 192.168.95.137 后的 arp 缓存表,如果结果不同,请 ip neigh flush all 清空缓存表或者多等待一段时间再测试。

      [root@test2 ~]# ip neigh sh
      192.168.95.137 dev ens34 lladdr 00:0c:29:fa:f1:1f STALE
      192.168.95.136 dev ens34 lladdr 00:0c:29:fa:f1:1f STALE
      192.168.95.1 dev ens34 lladdr 00:50:56:c0:00:08 REACHABLE
      [root@test2 ~]#
    • 所谓响应任意可能的 IP 地址并不是响应所有地址,lo 接口、非同一网段地址以及无第一路由的接口地址就不会主动响应出去。同样,那些定义在接口上的别名地址也默认不会响应出去,因为它们的数据的流入流出都是通过它所依附的接口。

  2. arp_ignore=1 时,当主机收到 arp 请求时,只有 arp 请求包中目标 IP 和流入接口上的 IP 相同时,才会响应该 IP 以及该接口的 MAC。

    例如,设置 ens34 网卡的该变量值为:

    [root@test1 ~]# echo 1 >/proc/sys/net/ipv4/conf/ens34/arp_ignore

    再从 test2 上去 ping test1 的 192.168.95.136/137,看现象:

    [root@test2 ~]# ping 192.168.95.136
    PING 192.168.95.136 (192.168.95.136) 56(84) bytes of data.
    64 bytes from 192.168.95.136: icmp_seq=1 ttl=64 time=1.16 ms
    64 bytes from 192.168.95.136: icmp_seq=2 ttl=64 time=1.00 ms
    64 bytes from 192.168.95.136: icmp_seq=3 ttl=64 time=0.979 ms
    ^C
    --- 192.168.95.136 ping statistics ---
    3 packets transmitted, 3 received, 0% packet loss, time 2004ms
    rtt min/avg/max/mdev = 0.979/1.049/1.166/0.083 ms
    [root@test2 ~]# 
    [root@test2 ~]# 
    [root@test2 ~]# ping 192.168.95.137
    PING 192.168.95.137 (192.168.95.137) 56(84) bytes of data.
    From 192.168.95.134 icmp_seq=1 Destination Host Unreachable
    From 192.168.95.134 icmp_seq=2 Destination Host Unreachable
    From 192.168.95.134 icmp_seq=3 Destination Host Unreachable
    From 192.168.95.134 icmp_seq=4 Destination Host Unreachable
    ^C
    --- 192.168.95.137 ping statistics ---
    4 packets transmitted, 0 received, +4 errors, 100% packet loss, time 3000ms
    pipe 4
    [root@test2 ~]#
    • 结果 test1 ens34 上的 136 可以通,ens35 上的 137 不通。回头看一下 test1 的路由表,192.168.95.0/24 网段的第一条路由如下:

      192.168.95.0    0.0.0.0         255.255.255.0   U     0      0        0 ens34

      也就是说 test2 去 ping test1 上的 192.168.95.0/24 网段,都是由这条路由回应,正好是 ens34 接口,又正好 ens34 接口 arp_ignore=1,所以 ens35 上的 IP 地址 137 无法通过 ens34 接口响应。那如果我把这条路由删了呢,会看到什么?

      [root@test1 ~]# ip route del 192.168.95.0/24 dev ens34
      [root@test2 ~]# ping 192.168.95.137
      PING 192.168.95.137 (192.168.95.137) 56(84) bytes of data.
      64 bytes from 192.168.95.137: icmp_seq=1 ttl=64 time=1.28 ms
      64 bytes from 192.168.95.137: icmp_seq=2 ttl=64 time=0.971 ms
      64 bytes from 192.168.95.137: icmp_seq=3 ttl=64 time=0.350 ms
      ^C
      --- 192.168.95.137 ping statistics ---
      3 packets transmitted, 3 received, 0% packet loss, time 2003ms
      rtt min/avg/max/mdev = 0.350/0.869/1.287/0.389 ms
      [root@test2 ~]#
      • 如您所想,137 也通了,不过很明显,走的是 ens35 的这条路由:
      192.168.95.0    0.0.0.0         255.255.255.0   U     0      0        0 ens35

    因此在 arp 请求过程中,目标主机路由表中路由的先后顺序非常重要,它不仅决定了数据从哪流出,更深层面上还决定了流出时使用哪个 MAC 地址,而这直接决定是否能成功 ARP 请求、ARP 响应以及 arp 缓存的 IP<-->MAC 映射结果。

    经过这些实验,我想您已经有很感性的认识,不过需要举一反三,实验无法详尽其说。

D2.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 地址,它们是被响应者缓存的内容
  • 对照着上面的官方英文描述去理解,汉语有时未必有英文那么简单明了。
    • 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):

  1. 如果想通过 eth2 接口流出源地址为 IP0 的 ARP 请求广播包,默认情况下是行不通的。

  2. 因为默认情况下,使用 eth2 流出 ARP 请求的源 IP 地址必须使用 IP2。

  3. 因此必须设置 arp_announce=1 或 2,其实设置为 1 时也只是有机会流出,因为它要判断 IP0 和目标 IP 地址是否存在子网所属关系。只有设置 arp_announce=2 才必然能流出,但这时该 Linux 主机向外通告的 IP 地址将不是 IP0,而是 IP2。

D2.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 广播中隐藏指定接口

不同的系统对环回设备的 IP 地址配置策略不同,对于现在的 CentOS 来说,虽然每个接口(包括 lo 接口)都可以设置这两个变量,但这两个变量只对能 arp 回应的接口才生效(如 eth0,eth1 等对外通信的普通接口)。经过测试得知,lo 接口属于 non-arp 类型,在 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@test1 ~]# ls -l /proc/sys/net/ipv4/conf/
total 0
dr-xr-xr-x 1 root root 0 Dec 26 19:11 all
dr-xr-xr-x 1 root root 0 Dec 26 19:11 default
dr-xr-xr-x 1 root root 0 Dec 26 20:00 ens32
dr-xr-xr-x 1 root root 0 Dec 26 20:00 ens34
dr-xr-xr-x 1 root root 0 Dec 26 20:00 ens35
dr-xr-xr-x 1 root root 0 Dec 26 20:00 lo
[root@test1 ~]#
  • 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 不可能被通告出去。
标签云