Visão geral rápida da situação
Tenho um host Fedora no qual há várias NICs e VMs (QEMU/KVM/LibVirt). Cada convidado acessa a internet por meio de uma ponte virtual que opera no modo NAT e encaminha o tráfego para um único dispositivo físico.
Por razões que estão além de mim, quando se trata deste adaptador USB/Ethernet (um telefone Android), não consigo fazê-lo funcionar como faço com outras NICs. O convidado recebe respostas DNS do host, então tenta acessar a internet e não obtém nada além de respostas do tipo ICMP host unreachable e TTL expired .
Uma descrição de como minha infraestrutura funciona
Aqui está um resumo de como isso geralmente funciona. O problemático trio guest/bridge/NIC virá depois (mas supostamente funciona da mesma forma).
O host
Uma estação de trabalho Fedora 40 x86_64.
➜ ~ uname -r
6.10.6-200.fc40.x86_64
O hipervisor
QEMU/KVM, LibVirt, usando virt-manager ou virsh dependendo das necessidades.
➜ ~ 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
O convidado
Instalação limpa do Win10, ISO do MS, winFSP e Virtio-win-guest-tools instalados.
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
Visão geral da configuração usual
Resumindo
Aqui está como eu costumo fazer as coisas. O host é conectado a um roteador por meio de uma NIC Ethernet física, vamos chamá-la de eth0 em my_network. Uma rede virtual é criada no virt-manager, vamos chamá-la de my_virt_net. Ela tem uma ponte virtual que opera no modo NAT e encaminha para o dispositivo físico eth0, vamos chamá-lo de virbr0. O convidado obtém uma interface de rede virtual correspondente àquela fonte de rede, configurada como um dispositivo virtIO, vamos chamá-la de vNIC0.
O vNIC do convidado recebe um IP via DHCP (libvirt executa uma instância DNSMASQ para cada um, eu acho?). Neste exemplo, estamos operando em 10.0.0.0/24, o gateway sendo 10.0.0.1.
Regras e rotas de IP são criadas para garantir que o tráfego do convidado passe pelo dispositivo certo. Libvirt cuida da criação de regras MASQUERADE no iptables.
Uma vez feito tudo isso, o convidado tem acesso à internet. O tráfego flui da seguinte maneira:
Guest <-> vNIC0 <-> virbr0 <-> vnet0 <-> eth0 <-> my_network <-> outside_world
Em detalhe
➜ ~ 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
Esta é uma configuração bem padrão, eu acho. Ela funciona bem para as minhas necessidades. As saídas que mostrei são simplificadas, já que tenho, de fato, vários convidados e NICs, mas você entendeu a ideia. É sempre a mesma configuração, e funciona.
Uma coisa a ser observada é que eu uso scripts /etc/NetworkManager/dispatcher.d/
para adicionar ou remover regras de IP e rotas quando as interfaces ficam ativas ou inativas.
Então qual é o problema?
Aqui está o que está acontecendo. Quero usar um dispositivo USB/Ethernet (um telefone Android) conectado ao host e configurar o IP como qualquer outra NIC. Quero que esse telefone seja usado como um gateway para o mundo externo, por um e apenas um convidado, e o host, de preferência, não deve poder usá-lo. Minha configuração usual é ter tudo bem isolado.
O telefone é conectado ao host e configurado para ligar automaticamente o tethering USB. Ele é detectado como uma interface de rede e fornece uma concessão DHCP, como esperado:
➜ ~ 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
Garantindo que temos conectividade com o mundo exterior:
➜ ~ 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
Então eu faço o de sempre. Eu crio uma rede virtual no virt-manager (modo NAT, encaminhamento para o dispositivo físico enp0s20f0u6 especificamente) e a conecto como um dispositivo virtIO ao convidado. Eu configuro regras de ip e rotas em um script que roda quando a interface é ativada ou desativada:
➜ ~ 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
Embora essa seja exatamente a mesma configuração para outros convidados e NICs, o convidado não tem conectividade com o mundo externo.
Verificações de sanidade
Só para esclarecer, fiz o seguinte, e muito mais, antes de postar:
- Tente outro telefone: não é bom.
- Tente outro convidado: no bueno.
- Acabar com a configuração, reconstruir do zero: nada bom.
- Tente outra NIC no convidado problemático: funciona.
- Experimente a NIC problemática no host: ela funciona.
- Tente com todas as outras NICs inativas e apenas esta restante, incluindo defini-la como o gateway padrão para todo o tráfego: nenhuma alteração.
- n-tuple verifica as regras de IP, rotas de IP, tabelas de IP: parece bom para mim.
- Verifique o firewall. Tudo parece bem para mim.
- Tente um novo convidado executando o Fedora 40: mesmo problema.
- Usar passagem USB: funciona, mas gostaria de gerenciar todas as minhas interfaces da mesma maneira.
- Use um modelo de dispositivo e1000e em vez de virtIO: não é bom.
Captura de tráfego de rede
Estou tentando ler os dumps TCP que capturo do host, na interface física, na ponte e na vnet, mas isso está além da minha compreensão de rede...
Observe o seguinte:
- "Fidel" é o nome do anfitrião.
- A placa de rede USB/Ethernet, também conhecida como telefone Android, é:
- enp0s20f0u6 (nome da interface).
- 46:7a:42:be:dc:85 (endereço MAC).
- 192.168.171.21 (IP de gateway).
- 192.168.171.52 é o IPv4 alugado pela NIC ao host via DHCP.
- A ponte virtual é:
- virbr3.
- 52:54:00:eb:85:6a.
- 10.0.3.1.
- A NIC virtual anexada ao convidado é:
- 52:54:00:43:16:2f
- 10.0.3.219
tcpdump
do anfitrião: 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
do anfitrião: 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
O que eu vejo?
Quando o convidado solicita uma resolução de DNS, o host parece fornecê-la (e, de fato, a saída do traceroute no convidado do Windows mostra que o endereço IP de www.google.com foi encontrado, mas a rota não pode ser... rastreada).
Alguém tem sugestões sobre o que está acontecendo?
Editar: Tive que encurtar os resultados tcpdump
porque o StackExchange estava detectando minha postagem como spam... Fornecerei mais em postagens subsequentes, se necessário.