20 分钟后 KVM 客户机上的 IPv6 连接丢失

20 分钟后 KVM 客户机上的 IPv6 连接丢失

我在 Hetzner 设置了一台 KVM 虚拟化服务器。Hetzner 为我提供了一个主 IP (95.xxx.xxx.235) 和一个 /29 IPv4 子网 (95.xxx.xxx.184/29) 以及一个 /64 IPv6 网络 (2a01:xxxx:xxxx:xxxx::/64)。

KVM 客户机 (Debian Stretch) 在网络服务重启或重新启动 20 分钟后恰好失去 IPv6 连接。即使连接丢失,我仍可以 ping 默认网关 (fe80::1)。IPv4 连接始终保持正常,没有问题。

目前,接口设置为桥接模式的 macvlan,我也尝试过 VEPA 和私有模式,但没有成功。此外,NIC 类型设置为 e1000,但我也尝试过 virtio,但没有成功。

连接丢失后,我从主机上的物理 NIC 进行了 TCP 转储,结果显示有回显请求离开接口,也有回显答复到达接口,但是从客户 NIC 进行 tcpdump 时,我只能看到离开 NIC 的请求。

主机上的 /etc/network/interfaces:

auto lo
iface lo inet loopback
iface lo inet6 loopback

auto enp2s0
iface enp2s0 inet static
  address 95.xxx.xxx.235
  netmask 255.255.255.192
  gateway 95.xxx.xxx.193
  up route add -net 95.xxx.xxx.192 netmask 255.255.255.192 gw 95.xxx.xxx.193 dev enp2s0

iface enp2s0 inet6 static
  address 2a01:xxx:xxx:xxx::2
  netmask 64
  gateway fe80::1

客户机上的 /etc/network/interfaces:

auto lo
iface lo inet loopback
iface lo inet6 loopback

auto ens3
iface ens3 inet static
    address 95.xxx.xxx.187
    netmask 255.255.255.248
    gateway 95.xxx.xxx.185

iface ens3 inet6 static
    address 2a01:xxx:xxx:xxx::20
    netmask 64
    gateway fe80::1

主机上的 # route -6 -n:

Kernel IPv6 routing table
Destination                    Next Hop                   Flag Met Ref Use If
2a01:xxxx:xxxx:xxxx::/64          ::                         U    256 8  1162 enp2s0
fe80::/64                      ::                         U    256 0     0 macvtap0
fe80::/64                      ::                         U    256 0     0 enp2s0
::/0                           fe80::1                    UG   1024 8  4534 enp2s0
::/0                           ::                         !n   -1  1 11069 lo
::1/128                        ::                         Un   0   9    81 lo
2a01:xxxx:xxxx:xxxx::/128         ::                         Un   0   1     0 lo
2a01:xxxx:xxxx:xxxx::2/128        ::                         Un   0   9    82 lo
fe80::/128                     ::                         Un   0   1     0 lo
fe80::/128                     ::                         Un   0   1     0 lo
fe80::/128                     ::                         Un   0   1     0 lo
fe80::xxxx:xxxx:xxxx:1069/128   ::                         Un   0   1     0 lo
fe80::xxxx:xxxx:xxxx:22e1/128   ::                         Un   0   1     0 lo
fe80::xxxx:xxxx:xxxx:201/128   ::                         Un   0   2    79 lo
ff00::/8                       ::                         U    256 0     0 macvtap0
ff00::/8                       ::                         U    256 0     0 enp2s0
::/0                           ::                         !n   -1  1 11069 lo

来宾上的 # route -6 -n:

Kernel IPv6 routing table
Destination                    Next Hop                   Flag Met Ref Use If
2a01:xxxx:xxxx:1414::/64          ::                         U    256 0     0 ens3
fe80::/64                      ::                         U    256 0     0 ens3
::/0                           fe80::1                    UG   1024 2    77 ens3
::/0                           ::                         !n   -1  1  6846 lo
::1/128                        ::                         Un   0   5   525 lo
2a01:xxxx:xxxx:xxx::20/128       ::                         Un   0   3    70 lo
fe80::xxxx:xxxx:xxx:22e1/128   ::                         Un   0   2     6 lo
ff00::/8                       ::                         U    256 0     0 ens3
::/0                           ::                         !n   -1  1  6846 lo

# ip -6 neigh 主机:

2a01:xxxx:xxxx:xxxx::20 dev enp2s0  FAILED
fe80::1 dev enp2s0 lladdr xx:xx:xx:8d:22:06 router STALE

# ip -6 neigh 访客:

fe80::1 dev enp2s0 lladdr xx:xx:xx:8d:22:06 router REACHABLE

主机上 /etc/sysctl.conf 中可能包含的相关内容:

net.ipv4.ip_forward=1
net.ipv4.conf.enp2s0.send_redirects=0
net.ipv6.conf.all.forwarding=1

Guest 上的 /etc/sysctl.conf 中可能相关的内容:

net.ipv6.conf.default.accept_ra=2
net.ipv6.conf.default.autoconf=1
net.ipv6.conf.all.accept_ra=2
net.ipv6.conf.all.autoconf=1
net.ipv6.conf.ens3.accept_ra=2
net.ipv6.conf.ens3.autoconf=1

Guest libvirt-config 的可能相关部分:

<interface type='direct' trustGuestRxFilters='yes'>
  <mac address='xx:xx:xx:xx:xx:xx'/>
  <source dev='enp2s0' mode='bridge'/>
  <model type='e1000'/>
  <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>

由于我已经为此苦苦挣扎了大约两周,并且阅读了几乎所有涉及类似问题的相关帖子,我发现 Hetzner 显然做了一些可疑的 IPv6 实现。我已经联系了他们,但他们怀疑我自己会遇到路由问题。这可能是真的,因为 20 分钟后,我仍然会在物理 NIC 上收到回显回复,即使它们没有转发给访客。

那么,IPv6 用户有什么想法吗?

更新:

因此 Hetzner 向我确认,2a01:xxxx:xxxx:xxxx::/64 网络被路由到物理接口的链路本地地址。重新启动网络时,NDP 条目会保留 20 分钟,但之后会被删除,因为虚拟机没有使用正确的链路本地地址进行应答,因为它具有不同的 MAC 地址。

看起来我似乎无法在这里使用 macvtap 接口,但我必须为此创建一个桥接器。但是,我想知道为什么当 IPv4 仍然有效时,主机无法使用 IPv6 看到客户机(反之亦然)。我认为这将允许我直接从主链路本地地址路由流量。

答案1

我在使用 Hetzner 服务器时遇到了同样的问题,但使用的是 VirtualBox 而不是 KVM。

问题:

Hetzner 将所有具有 /64 子网内任何目标 IP 的 IPv6 数据包路由到物理主机的 MAC 地址。这意味着,如果您从互联网上的某个地方向具有与主机相同前缀的 IPv6 地址的 VM 发送 ping,Hetzer 的网关不会进行邻居请求来查找 VM 的 MAC 地址,而只是将 ICMP 数据包转发到主机的 MAC。这就是为什么您可以在物理主机上看到回显回复,但在 VM 上看不到的原因:它针对的是主机的 MAC,而不是 VM 的 MAC。

但是,Hetzner 的 IPv6 实现似乎存在错误(或者可能是故意的,我不知道):如果虚拟机发送邻居请求以查找网关的 MAC 地址(fe80::1),并且它使用其全局 IPv6 IP 作为请求的源地址,Hetzner 的网关似乎会以某种方式更新其内部 IPv6 到 MAC 地址表。在接下来的 20 分钟内,Hetzner 的网关将把任何以虚拟机 IPv6 地址为目标的数据包发送到虚拟机的 MAC 地址。如果在 20 分钟内没有从虚拟机的 MAC 和虚拟机的全局 IP 向网关发送进一步的请求,它会回退到将 IPv6 数据包发送到主机的 MAC。

现在您的虚拟机 - 在网络启动后,可能是因为此时未分配本地链路地址 - ONCE 使用其全局 IPv6 地址作为源发送请求,因此“意外”更新了 Hetzner 的 MAC 地址表。在运行时,虚拟机仍会不断发送请求以查找网关的 MAC 地址以使其 MAC 地址表保持最新,但它使用其本地链路 IPv6 地址来执行此操作(从 IPv6 的角度来看这是完全可以的),但这不会更新 Hetzner 网关的 MAC 地址表。这就是为什么 IPv6 在虚拟机启动后似乎完全正常工作但只能持续 20 分钟的原因。

解决方案:

有一个肮脏的解决方案和一个干净的解决方案:

  • 肮脏的解决方案:您的虚拟机必须不时使用其全局 IPv6 地址(比如说每 5 分钟一次)发送网关 MAC 地址的请求。这很棘手:您的虚拟机将发送请求,但使用其链路本地 IPv6。所以我在这里使用了一个便宜的技巧:我从接口中删除链路本地 IP,发送请求(然后强制使用全局 IP)并重新连接链路本地 IP:

    ip -6 addr del fe80::a00:27ff:fecf:e270/64 dev enp0s3
    ndisc6 fe80::1 enp0s3
    ip -6 addr add fe80::a00:27ff:fecf:e270/64 dev enp0s3
    
  • 干净的解决方案:不要使用桥接。我现在使用仅主机网络。这意味着虚拟机连接到单独的 NIC(vboxnet0)。我添加了一个 IPv6 路由,将所有流量从主机转发到虚拟机的 IPv6 地址:

    ip -6 route add <my IPv6 pefix>::20 dev vboxnet0
    

在虚拟机上,我使用主机的本地链路 IPv6 地址作为其默认网关。为了允许主机在其全局 IPv6 IP 上连接虚拟机,我将同一 /64 子网中的另一个 IP 分配给 vboxnet0。对我来说,这非常完美。

答案2

我的 Hetzner 服务器遇到了完全相同的问题,但我找到了一个比您的“肮脏的解决方案”更好的解决方案,因为ndisc6它有一个参数-s可以指定另一个源 IP 而不是链接本地地址:

ndisc6 -s \<my IPv6 prefix\>::20 fe80::1 enp1s0

因此我编写了一个 cron 作业,每 5 分钟发送一次邻居请求,现在我很好了 :)

答案3

继续建设f0ssie 的回答,我编写了一个 bash 脚本,它可以执行相同的操作,但不依赖于任何硬编码的地址或接口,以便更轻松地跨多台计算机和网络进行部署。请注意,除了ndisc6,你还需要iproute2杰奇

#!/bin/bash

set -euo pipefail

default_route=$(ip -6 --json route | jq --exit-status '[.[] | select(.dst == "default")] | first')
default_gateway=$(echo "${default_route}" | jq --raw-output --exit-status '.gateway')
net_device=$(echo "${default_route}" | jq --raw-output --exit-status '.dev')
our_global_addr=$(ip -6 --json addr show dev "${net_device}" scope global | jq --raw-output --exit-status 'first | .addr_info | first | .local')

ndisc6 --source "${our_global_addr}" "${default_gateway}" "${net_device}"

答案4

我找到sysctl -w net.ipv6.conf.all.forwarding=1很有帮助。不知道为什么。灵感来自https://ipv6-first-guide.hillbrecht.de/#_preparing_the_network_settings_of_the_host

相关内容