情况概览
我有一台 Fedora 主机,上面有多个 NIC 和 VM(QEMU/KVM/LibVirt)。每个客户机通过以 NAT 模式运行的虚拟网桥访问互联网,并将流量转发到单个物理设备。
由于一些我无法理解的原因,当涉及到这个 USB/以太网适配器(Android 手机)时,我似乎无法像使用其他 NIC 那样让它工作。客户机从主机收到 DNS 答案,然后尝试访问互联网,但只收到ICMP 主机不可达和TTL 过期类型的响应。
关于我的基础架构如何运作的描述
以下是它通常如何工作的概述。有问题的客户机/桥接器/NIC 三重奏稍后会出现(但应该以大致相同的方式工作)。
主人
x86_64 Fedora 40 工作站。
➜ ~ uname -r
6.10.6-200.fc40.x86_64
虚拟机管理程序
QEMU/KVM、LibVirt,根据需要使用 virt-manager 或 virsh。
➜ ~ qemu-kvm --version
QEMU emulator version 8.2.6 (qemu-8.2.6-3.fc40)
Copyright (c) 2003-2023 Fabrice Bellard and the QEMU Project developers
➜ ~ virsh --version=long
Virsh command line tool of libvirt 10.1.0
See web site at https://libvirt.org/
Compiled with support for:
Hypervisors: QEMU/KVM LXC LibXL OpenVZ VMware VirtualBox ESX Hyper-V Test
Networking: Remote Network Bridging Interface udev Nwfilter
Storage: Dir Disk Filesystem SCSI Multipath iSCSI iSCSI-direct LVM RBD Gluster ZFS
Miscellaneous: Daemon Nodedev SELinux Secrets Debug DTrace Readline
➜ ~ virt-manager --version
4.1.0
客人
干净的 Win10 安装,来自 MS 的 ISO,安装了 winFSP 和 Virtio-win-guest-tools。
C:\Users\User>ver
Microsoft Windows [version 10.0.19045.4780]
PS C:\Windows\system32> winget list
Nom ID Version Disponible Source
-----------------------------------------------------------------------------------------------------------------------
WinFsp 2023 WinFsp.WinFsp 2.0.23075 winget
Virtio-win-guest-tools ARP\Machine\X86\{c5ad265e-98e6-40bd-801… 0.1.262
常规设置概述
总之
我通常的做法如下。主机通过物理以太网 NIC 连接到路由器,我们将其称为 my_network 上的 eth0。在 virt-manager 中创建一个虚拟网络,我们将其称为 my_virt_net。它有一个在 NAT 模式下运行并转发到物理设备 eth0 的虚拟网桥,我们将其称为 virbr0。客户机获得与该网络源相对应的虚拟网络接口,设置为 virtIO 设备,我们将其称为 vNIC0。
客户机的 vNIC 通过 DHCP 分配 IP(我认为 libvirt 为每个 vNIC 运行一个 DNSMASQ 实例?)。在此示例中,我们在 10.0.0.0/24 上运行,网关为 10.0.0.1。
创建 IP 规则和路由以确保来宾的流量通过正确的设备。Libvirt 负责在 iptables 中创建 MASQUERADE 规则。
完成所有操作后,访客即可访问互联网。流量按以下方式流动:
Guest <-> vNIC0 <-> virbr0 <-> vnet0 <-> eth0 <-> my_network <-> outside_world
详细
➜ ~ nmcli con show
NAME UUID TYPE DEVICE
my_network 3d72b049-b1a7-49e8-ab55-18edf0d79de9 ethernet enp0s31f6
➜ ~ ip addr
1: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 40:8d:5c:4d:e5:c0 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.1/24 brd 192.168.1.255 scope global dynamic noprefixroute enp0s31f6
valid_lft 40431sec preferred_lft 40431sec
inet6 2a01:e0a:18b:b670:3429:7bc3:6997:2e17/64 scope global dynamic noprefixroute
valid_lft 86265sec preferred_lft 86265sec
inet6 fe80::ca7c:24c:ce13:2a22/64 scope link noprefixroute
valid_lft forever preferred_lft forever
2: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 52:54:00:ed:b3:89 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.1/24 brd 10.0.0.255 scope global virbr0
valid_lft forever preferred_lft forever
3: vnet0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master virbr0 state UNKNOWN group default qlen 1000
link/ether fe:54:00:32:2f:3e brd ff:ff:ff:ff:ff:ff
inet6 fe80::fc54:ff:fe32:2f3e/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
➜ ~ brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.525400edb389 yes vnet0
➜ ~ virsh net-list
Name State Autostart Persistent
-----------------------------------------------------
my_virt_net active yes yes
➜ ~ ip rule
0: from all lookup local
100: from all iif virbr0 lookup 100
32766: from all lookup main
32767: from all lookup default
➜ ~ ip route show table 100
default dev enp0s31f6 scope link
➜ ~ ip route
default via 192.168.1.254 dev enp0s31f6 proto dhcp src 192.168.1.1 metric 100
10.0.0.0/24 dev virbr0 proto kernel scope link src 10.0.0.1
192.168.1.0/24 dev enp0s31f6 proto kernel scope link src 192.168.1.1 metric 100
➜ ~ iptables -t nat -L -v -n
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 19151 packets, 2171K bytes)
pkts bytes target prot opt in out source destination
21657 2313K LIBVIRT_PRT 0 -- * * 0.0.0.0/0 0.0.0.0/0
Chain LIBVIRT_PRT (1 references)
0 0 RETURN 0 -- * enp0s31f6 10.0.0.0/24 224.0.0.0/24
0 0 RETURN 0 -- * enp0s31f6 10.0.0.0/24 255.255.255.255
19 988 MASQUERADE 6 -- * enp0s31f6 10.0.0.0/24 !10.0.0.0/24 masq ports: 1024-65535
4 5112 MASQUERADE 17 -- * enp0s31f6 10.0.0.0/24 !10.0.0.0/24 masq ports: 1024-65535
0 0 MASQUERADE 0 -- * enp0s31f6 10.0.0.0/24 !10.0.0.0/24
我认为这是一个相当标准的设置。它很好地满足了我的需求。我展示的输出经过了简化,因为我实际上有多个客户机和 NIC,但你明白我的意思。它始终是相同的设置,并且有效。
需要注意的一点是,我使用脚本/etc/NetworkManager/dispatcher.d/
在接口启动或关闭时添加或删除 IP 规则和路由。
那么问题是什么?
事情是这样的。我想使用一个连接到主机的 USB/以太网设备(Android 手机),并像其他 NIC 一样设置 IP。我希望该手机被一个且只有一个客人用作通向外界的网关,并且主机最好不能使用它。我通常的设置是将所有东西都隔离开来。
手机已连接到主机,并配置为自动打开 USB 网络共享。它被检测为网络接口,并按预期提供 DHCP 租约:
➜ ~ ip addr
8: enp0s20f0u6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN group default qlen 1000
link/ether 46:7a:42:be:dc:85 brd ff:ff:ff:ff:ff:ff
inet 192.168.171.52/24 brd 192.168.171.255 scope global dynamic noprefixroute enp0s20f0u6
valid_lft 2302sec preferred_lft 2302sec
inet6 2a02:8440:6103:24e4:d1fa:3bae:2f95:5b9d/64 scope global deprecated dynamic noprefixroute
valid_lft 1503sec preferred_lft 0sec
inet6 2a02:8440:6315:5cc:8eb1:665a:abb2:6847/64 scope global dynamic noprefixroute
valid_lft 7108sec preferred_lft 7108sec
inet6 fe80::faaa:5745:cf53:55cf/64 scope link noprefixroute
valid_lft forever preferred_lft forever
13: virbr3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 52:54:00:eb:85:6a brd ff:ff:ff:ff:ff:ff
inet 10.0.3.1/24 brd 10.0.3.255 scope global virbr3
valid_lft forever preferred_lft forever
确保我们与外界连通:
➜ ~ traceroute -i enp0s20f0u6 www.google.com
traceroute to www.google.com (142.250.178.132), 30 hops max, 60 byte packets
1 _gateway (192.168.171.21) 0.831 ms 0.711 ms 0.803 ms
...
...
...
[REDACTED]
...
...
...
15 par21s22-in-f4.1e100.net (142.250.178.132) 49.062 ms 66.086 ms 65.975 ms
所以我照常操作。我在 virt-manager 中创建一个虚拟网络(NAT 模式,具体转发到物理设备enp0s20f0u6),并将其作为 virtIO 设备附加到客户机。我在接口启动或关闭时运行的脚本中设置了 ip 规则和路由:
➜ ~ sudo nano /etc/NetworkManager/dispatcher.d/103-motoE14-handler.sh
#!/bin/bash
interface=$1
event=$2
if [ "$interface" = "enp0s20f0u6" ]; then
case "$event" in
up)
ip rule add prio 103 iif virbr3 table 103
ip route add default dev "$interface" table 103
;;
down)
ip rule del iif virbr3 table 103
ip route del table 103 default dev "$interface"
;;
esac
fi
➜ ~ sudo chmod +x /etc/NetworkManager/dispatcher.d/103-motoE14-handler.sh
虽然这与其他客户机和 NIC 的设置完全相同,但客户机与外界没有任何连接。
健全性检查
需要说明的是,在发布之前,我做了以下事情,甚至更多:
- 尝试另一部电话:不好。
- 尝试另一位客人:不好。
- 删除配置,从头开始重建:不好。
- 在有问题的客户机上尝试另一个 NIC:它可以工作。
- 在主机上尝试有问题的网卡:它可以工作。
- 尝试关闭所有其他 NIC 并仅保留这一个,包括将其设置为所有流量的默认网关:无变化。
- n 元组检查 ip 规则、ip 路由、ip 表:对我来说看起来不错。
- 检查防火墙。一切正常。
- 尝试运行 Fedora 40 的新客户机:同样的问题。
- 使用 USB 直通:它可以工作,但我想以相同的方式管理所有接口。
- 使用 e1000e 设备模型而不是 virtIO:不好。
网络流量捕获
我正在尝试读取从主机、物理接口、桥接器和虚拟网络捕获的 TCP 转储,但这超出了我对网络的理解......
请注意以下几点:
- “菲德尔”是主持人的名字。
- USB/以太网 NIC,又名 Android 手机,是:
- enp0s20f0u6(接口名称)。
- 46:7a:42:be:dc:85(MAC 地址)。
- 192.168.171.21(网关IP)。
- 192.168.171.52 是 NIC 通过 DHCP 租用给主机的 IPv4。
- 虚拟桥是:
- virbr3。
- 52:54:00:eb:85:6a。
- 10.0.3.1.
- 附加到客户机的虚拟网卡是:
- 52:54:00:43:16:2f
- 10.0.3.219
tcpdump
来自主机:enp0s20f0u6
➜ ~ sudo tcpdump -e -E -f -i enp0s20f0u6
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enp0s20f0u6, link-type EN10MB (Ethernet), snapshot length 262144 bytes
18:48:37.824751 46:7a:42:be:dc:85 (oui Unknown) > a6:fe:e6:ee:73:2d (oui Unknown), ethertype IPv6 (0x86dd), length 106: 2a02-8440-6315-05cc-8eb1-665a-abb2-6847.rev.sfr.net.57013 > 2a02-8440-6315-05cc-0000-0000-0000-0033.rev.sfr.net.domain: 3959+ A? checkappexec.microsoft.com. (44)
18:48:37.838107 46:7a:42:be:dc:85 (oui Unknown) > Broadcast, ethertype ARP (0x0806), length 42: Request who-has 20.191.45.158 tell fidel, length 28
18:48:37.867576 46:7a:42:be:dc:85 (oui Unknown) > a6:fe:e6:ee:73:2d (oui Unknown), ethertype IPv6 (0x86dd), length 152: 2a02-8440-6315-05cc-8eb1-665a-abb2-6847.rev.sfr.net.57276 > 2a02-8440-6315-05cc-0000-0000-0000-0033.rev.sfr.net.domain: 10318+ PTR? 3.3.0.0.0.0.0.0.0.0.0.0.0.0.0.0.c.c.5.0.5.1.3.6.0.4.4.8.2.0.a.2.ip6.arpa. (90)
18:48:37.988238 a6:fe:e6:ee:73:2d (oui Unknown) > 46:7a:42:be:dc:85 (oui Unknown), ethertype IPv6 (0x86dd), length 232: 2a02-8440-6315-05cc-0000-0000-0000-0033.rev.sfr.net.domain > 2a02-8440-6315-05cc-8eb1-665a-abb2-6847.rev.sfr.net.57013: 3959 3/0/0 CNAME prod-atm-wds-apprep.trafficmanager.net., CNAME prod-agic-we-3.westeurope.cloudapp.azure.com., A 20.56.187.20 (170)
tcpdump
来自主机:virbr3
➜ ~ sudo tcpdump -e -E -f -i virbr3
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on virbr3, link-type EN10MB (Ethernet), snapshot length 262144 bytes
19:14:19.404024 52:54:00:43:16:2f (oui Unknown) > 52:54:00:eb:85:6a (oui Unknown), ethertype IPv4 (0x0800), length 74: 10.0.3.219.62464 > fidel.domain: 54546+ A? www.google.com. (32)
19:14:19.414360 52:54:00:eb:85:6a (oui Unknown) > 52:54:00:43:16:2f (oui Unknown), ethertype IPv4 (0x0800), length 90: fidel.domain > 10.0.3.219.62464: 54546 1/0/0 A 216.58.213.68 (48)
19:14:19.427738 52:54:00:43:16:2f (oui Unknown) > 52:54:00:eb:85:6a (oui Unknown), ethertype IPv4 (0x0800), length 106: 10.0.3.219 > par21s18-in-f4.1e100.net: ICMP echo request, id 1, seq 13, length 72
19:14:19.427786 52:54:00:eb:85:6a (oui Unknown) > 52:54:00:43:16:2f (oui Unknown), ethertype IPv4 (0x0800), length 134: fidel > 10.0.3.219: ICMP time exceeded in-transit, length 100
19:14:19.429008 52:54:00:43:16:2f (oui Unknown) > 52:54:00:eb:85:6a (oui Unknown), ethertype IPv4 (0x0800), length 106: 10.0.3.219 > par21s18-in-f4.1e100.net: ICMP echo request, id 1, seq 14, length 72
我看到了什么?
当客户机请求 DNS 解析时,主机似乎提供了该解析(事实上,Windows 客户机中的 traceroute 输出显示已找到www.google.com的 IP 地址,但无法跟踪该路由)。
有人对正在发生的事情有什么建议吗?
编辑:我不得不缩短输出,tcpdump
因为 StackExchange 将我的帖子检测为垃圾邮件...如果需要,我将在后续帖子中提供更多内容。
ip route add default dev "$interface"
在大多数情况下,这是一条不完整的默认路由。当您未指定网关 IP 地址时,它会将整个 0.0.0.0/0 声明为“在链路上”或“本地子网”——操作系统不会对网关进行 ARP 查询,而是被告知直接对每个目标 IP 进行 ARP 查询,就像它们都在您的子网上一样。因此,Request who-has 20.191.45.158
您的 tcpdump 中存在 ARP 请求。这仅在您的网关提供代理 ARP并代表远程地址响应 ARP 时才有效 - 据我所知,由于历史原因,只有 Cisco 开箱即用地启用此功能(该功能甚至早于子网划分的概念)。大多数其他路由器,即使技术上能够执行代理 ARP,也不会默认提供此功能;我敢肯定 Android 也不提供此功能。
缺少 ARP 响应是导致 ICMP 错误“主机不可达”的原因(不要与“网络不可达”混淆)。
由于您无法在标准 Android 1上启用 Proxy-ARP ,因此您必须使用指定的网关地址定义默认路由。(由于您可能使用 DHCP 从 Android 获取 IP 地址,因此最好让 DHCP 客户端直接将路由添加到正确的表中。)
即使可以使用代理 ARP(例如,对于您的主要有线连接),网关路由仍然比依赖代理 ARP 的路由更可取,因为在后一种情况下,您的主机的 ARP 缓存(
ip neigh
或arp -an
)必须为需要联系的每个目标 IP 地址保留一个条目,而网关路由只需要一个 ARP 缓存条目(用于网关)。1除非 Android 设备已 root,否则在这种情况下常规 Linux sysctl 应该可以工作:
net.ipv4.conf.xxx.proxy_arp
将使该接口代表通过不同接口路由的任何 IP 地址响应 ARP。