If for some reason you need to tag traffic outgoing from a vNICs of VMs bridged to a wireless interface in macOS, you can do so as follows.
I will describe this process with VLAN configuration on an OpenWrt router with DSA support.
Open the interfaces tab on the Network menu in LuCI. Switch to the Devices tab and click configure br-lan, then switch to Bridge VLAN filtering and configure as in the screenshot below.
Do not apply these settings immediately. After enabling VLAN filtering in the bridge, we need to change the lan interface device from br-lan to br-lan.1 on the Interfaces tab. After making these changes to the lan interface, the configuration can be applied.
After that you need to add a VLAN tag for the wireless interface, this cannot be done via LuCI and you need to SSH into the router. Install the ip-bridge
package and run the following command afterwards:
bridge vlan add dev phy0-ap0 vid 5
You can verify that the interface is tagged with the following command:
bridge vlan show vid 5
----------------------------------------
port vlan-id
lan1 5 PVID Egress Untagged
phy1-ap0 5
Create a VLAN interface and assign a VID to it, specifying the wireless interface as the parent interface:
sudo ifconfig vlan5 create
sudo ifconfig vlan5 vlan 5 vlandev en0
To create VMs I use virt-install
with passing arguments to configure network interfaces via qemu:commandline
because vmnet-bridged, vmnet-shared, vmnet-host libvirt still doesn't support. You can use lima-vm or UTM, VMware Fusion. But in the case of the latter two, they will not allow you to explicitly specify a VLAN interface in bridge mode when creating a VM. You can remove the Wi-Fi interface in bridge mode after creating the VM and add the one you want using the following commands:
sudo ifconfig bridge100 deletem en0
sudo ifconfig bridge100 addm vlan5
Command to create a VM with virt-install
:
sudo virt-install \
--name test-vm \
--memory 4096 \
--vcpus 2 \
--disk=size=20,backing_store="/var/lib/libvirt/images/CentOS-Stream-GenericCloud-9-latest.aarch64.qcow2" \
--disk path=/var/lib/libvirt/boot/cidata.iso,device=cdrom \
--import \
--os-variant centos-stream9 \
--graphic vnc \
--noautoconsole \
--virt-type hvf \
--qemu-commandline='-netdev vmnet-bridged,id=net0,ifname=vlan5 -device virtio-net-device,netdev=net0' \
--network user
--disk path=/var/lib/libvirt/boot/cidata.iso,device=cdrom
This disk image contains meta-data and user-data files for cloud-init. It can be made with the following commands:
mkdir cidata
cat > cidata/user-data << EOF
#cloud-config
password: qwerty123
chpasswd: { expire: False }
ssh_pwauth: True
EOF
cat > cidata/meta-data << EOF
instance-id: test-vm
local-hostname: test-vm
EOF
hdiutil makehybrid -o /var/lib/libvirt/boot/cidata.iso cidata -iso -joliet
I specify to create the vNIC in bridge mode with VLAN interface via qemu:commandline
.
After creating a VM, a network bridge will appear in macOS interfaces, but you will find that packet transmission to your VM doesn't work properly. The reason is that the created VM will send packets through the router with its own MAC address, and the router won't know where to send the reply packets when the MAC address of your VM is in the frame, because the MAC address of your Wi-Fi interface in macOS will be different, and even if you add your VM's MAC address to the FDB table by specifying the router's wireless interface in OpenWrt, it won't help, because when sending a packet through the wireless interface the router doesn't look in the FDB and searches for the station's MAC address (STA) to send the packet to.
For example, your VM sends a broadcast ARP request and the host you are looking for will reply to it, the packet is sent to your VM's MAC address and will not be delivered because the VM's MAC address is not listed in the associated stations (Fig. 1). But at the same time your VM will reply to ARP requests from other hosts on the network and those host will receive a reply (Fig. 2).
Fig. 1 |
Fig. 2 |
To solve this issue you need MAC NAT, and it's easy to do. I found in the XNU kernel code and ifconfig
utility that this can be done, but it is not described in the documentation or in the ifconfig
man
pages and if it wasn't for this issue I wouldn't have noticed that if a vNIC is bridged with a wireless interface, this flag is set automatically, so I haven't encountered this issue before until I needed the VLANs.
As a confirmation, this listing shows that the flag is set automatically when the wireless interface is added to the network bridge:
if (wifi_infra) {
(void)bridge_mac_nat_enable(sc, bif);
}
But in the case of a bridge connection to a VLAN interface there is no condition in the code to set this flag and as I wrote above ifconfig
allows you to set this flag:
sudo ifconfig bridge100 macnat vlan5
Verify the flag is set and that MAC learning has happened:
ifconfig -v bridge100
-------------------------------------------------------------------------------------
bridge100: flags=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST> mtu 1496 index 24
eflags=41810000<CLONE,CL2K,ECN_ENABLE,FASTLN_ON>
options=3<RXCSUM,TXCSUM>
hwassist=100067<CSUM_IP,CSUM_TCP,CSUM_UDP,CSUM_TCPIPV6,CSUM_UDPIPV6,MULTIPAGES>
ether f2:2f:4b:c0:7c:64
Configuration:
id 0:0:0:0:0:0 priority 0 hellotime 0 fwddelay 0
maxage 0 holdcnt 0 proto stp maxaddr 100 timeout 1200
root id 0:0:0:0:0:0 priority 0 ifcost 0 port 0
ipfilter disabled flags 0x0
member: vlan5 flags=8003<LEARNING,DISCOVER,MACNAT> <<--
ifmaxaddr 0 port 27 priority 0 path cost 0
hostfilter 0 hw: 0:0:0:0:0:0 ip: 0.0.0.0
member: vmenet0 flags=3<LEARNING,DISCOVER>
ifmaxaddr 0 port 23 priority 0 path cost 0
hostfilter 0 hw: 0:0:0:0:0:0 ip: 0.0.0.0
Address cache:
10:e7:c6:xx:xx:xx Vlan1 vlan5 1199 flags=0<>
fa:16:3e:c9:25:bf Vlan1 vmenet0 1199 flags=0<>
52:54:0:12:34:56 Vlan1 vmenet0 1191 flags=0<>
MAC NAT list: <<--
vmenet0 192.168.24.1 52:54:0:12:34:56 1191 <<--
media: autoselect
status: active
generation id: 137
state availability: 0 (true)
desc: com.apple.NetworkSharing
qosmarking enabled: yes mode: none
low power mode: disabled
multi layer packet logging (mpklog): disabled
routermode4: disabled
routermode6: disabled
Now the packet capture will show the MAC address of your wireless interface and the packet will be sent to the MacBook's wireless interface, then to the network bridge and here the destination MAC header will be replaced in the frame:
But keep in mind that MAC learning won't be happen if you bridged the wireless interface with vNIC and assign a VLAN tag inside the VM.