Libvirt - 主机和虚拟机之间的 UDP 不起作用

Libvirt - 主机和虚拟机之间的 UDP 不起作用

主机:192.168.1.144/24
虚拟机(路由)网络:192.168.122.0/24

我有连接到 libvirt 路由网络的虚拟机。

<network>
  <name>routed-122</name>
  <uuid>86ca64a6-7fea-4cf8-9625-fa45fe944c2c</uuid>
  <forward mode='route'/>
  <bridge name='virbr1' zone='public' stp='on' delay='0'/>
  <mac address='52:54:00:0e:84:40'/>
  <domain name='routed_nat'/>
  <ip address='192.168.122.1' netmask='255.255.255.0'>
    <dhcp>
      <range start='192.168.122.128' end='192.168.122.254'/>
      <host mac='52:54:00:32:a9:9d' name='kvm-srv01' ip='192.168.122.131'/>
      <host mac='52:54:00:82:b7:f7' name='kvm-srv02' ip='192.168.122.132'/>
      <host mac='52:54:00:ee:38:54' name='kvm-srv03' ip='192.168.122.133'/>
    </dhcp>
  </ip>
</network>

UDP 工作原理成功地之间:
虚拟机 <--> 桥接 virbr1(192.168.122.1)
虚拟机 <--> 主机外部网络 (192.168.1.0/24)

UDP 确实不行之间:
虚拟机 <--> 主机 192.168.1.144/24

同时,TCP 工作没有问题!

我通过这种方式检查UDP的运行情况(通过iperf3 -u,即UDP模式):

  1. 在主机上:iperf3 -s(服务器模式)
  2. 在 VM 上:iperf3 -u -c HOST-IP
    反之亦然 - 结果相同,没有通过 UDP 进行通信。

通过 Wireshark 进行的转储显示以下内容。 在此处输入图片描述 尝试通过默认 NAT 网络 (192.168.100.0/24) 连接虚拟机 - 同样的这个问题。
在试验 docker swarm 时遇到了这个问题。

答案1

这是多宿主主机的路由问题,具体影响 UDP 而不是 TCP。

从截图中可以看出,VM 的iperf3客户端发送UDP数据包:

192.168.122.131:59879 -> 192.168.1.144:5021

并期待主人的回复iperf3服务器如下:

192.168.1.144:5021 -> 192.168.122.131:59879

但主持人回复说:

192.168.122.1:5021 -> 192.168.122.131:59879

由于源与预期不匹配(客户端 UDP 套接字连接(2)-ed 为 192.168.1.144:5021)虚拟机的内核向主机的iperf3服务器。

这是多宿主无感知的已知问题UDP使用 BSD 套接字 API 的应用程序:由于 UDP 套接字通常绑定到 0.0.0.0 (INADDR_ANY),因此它不知道在多个可能的地址中的哪个地址上接收到 UDP 数据包(此信息不可用默认情况下)。当使用套接字上的源地址 0.0.0.0 (INADDR_ANY) 回复时,它依赖路由堆栈从路由中填充实际源地址。当数据包是在添加 IP 地址(例如:eth0)的其他接口(例如:vmbr1)上接收时,此操作会失败。

TCP 不受影响,因为一旦连接接受(2)-ed,并且在已建立状态下创建一个新的套接字,这个新的套接字会自动绑定到正确的本地地址:初始请求的目的地,即使监听套接字绑定到 INADDR_ANY。

有两种方法可以使用 UDP 解决这个问题:

  1. 永远不会绑定到 INADDR_ANY,而只会绑定到特定地址,可能多次。

    回复总是来自绑定地址,因此总是具有正确的源 IP 地址。

  2. 或者使用套接字选项提供的高级功能IP_PKTINFO(*BSD 提供了与 等效的功能 IP_RECVDSTADDR)。

    ... 这使得应用程序能够知道每个接收到的 UDP 数据包是在哪个本地地址(以及在哪个接口)上接收的,并使用它发回正确的答复,同时使用(通常)绑定到 INADDR_ANY 的单个 UDP 套接字。这要求应用程序具有额外的特定代码才能正确处理。

这里,解决方案 1. 足够好,并且足够近iperf3:使用选项--bind指定要绑定 UDP 套接字的地址(TCP 和当客户端请求 UDP 模式时)。然后所有回复都将来自正确的源地址:

iperf3 -s --bind 192.168.1.144

如果虚拟机也是多宿主的,则可以对客户端使用相同类型的选项,例如:

iperf3 -c 192.168.1.144 --bind 192.168.122.131 -u

注意:实际上我的版本iperf3默认绑定到 ::(在 IPv6 双栈模式下)而不是(使用 IPv4 并绑定到) 0.0.0.0,但这是相同的行为。

相关内容