This guide is for configuring Wi-Fi hotspot disposable qube using udev rules.
You can refer to this guide as a base:
Introduction
This guide is for users who would like to create a Wi-Fi hotspot from their Qubes OS qube. This can be useful if you want to provide a Wi-Fi network tunnelling the traffic through a VPN or sys-whonix.
Basically, the hotspot runs in a qube, and the traffic will pass through the qube’s netvm.
Requirements
You need a Wi-Fi device, either USB or integrated. If you connect to the Internet / Network with Wi-Fi, you need a second Wi-Fi device.
Setup
The guide uses non-minimal templates. …
But compared to it, this guide will allow you to dynamically attach/detach USB Wi-Fi adapter to the Wi-Fi hotspot qube.
Follow the base guide linked above, but instead of editing the /rw/config/rc.local script, run the following commands in the terminal of Wi-Fi hotspot disposable template qube.
Run these commands to set SSID and password, but change “your_ssid_name_here” and “the_PSK_password” to your own:
WIFI_SSID="your_ssid_name_here"
WIFI_PASSWORD="the_PSK_password"
Then run these commands in the same terminal to create the configuration files:
sudo mkdir -p /rw/config/qubes-bind-dirs.d/
cat << 'EOF' | sudo tee /rw/config/qubes-bind-dirs.d/50_hotspot.conf > /dev/null
binds+=( '/etc/udev/rules.d/99-wireless-detect.rules' )
EOF
sudo mkdir -p /rw/bind-dirs/etc/udev/rules.d/
cat << 'EOF' | sudo tee /rw/bind-dirs/etc/udev/rules.d/99-wireless-detect.rules > /dev/null
SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ACTION=="move", RUN+="/rw/config/wifi-hotspot-add.sh '%E{INTERFACE}'"
SUBSYSTEM=="net", ENV{DEVTYPE}=="wlan", ACTION=="remove", RUN+="/rw/config/wifi-hotspot-remove.sh '%E{INTERFACE}'"
EOF
cat << 'EOF' | sudo tee /rw/config/wifi-hotspot-add.sh > /dev/null
#!/bin/sh
if [ "$(qubesdb-read /qubes-vm-persistence)" = "none" ]
then
WIFI_INTERFACE=$1
sed -i "s/^interface-name=.*/interface-name=$WIFI_INTERFACE/g" /rw/config/NM-system-connections/Hotspot.nmconnection
nmcli conn reload
if nft create chain ip qubes hotspot-input-$WIFI_INTERFACE >/dev/null 2>&1; then
nft add rule ip qubes custom-input jump hotspot-input-$WIFI_INTERFACE
fi
nft add rule ip qubes hotspot-input-$WIFI_INTERFACE iifname $WIFI_INTERFACE meta l4proto udp udp dport 67 accept
nft add chain ip qubes hotspot-dnat-dns-$WIFI_INTERFACE '{ type nat hook prerouting priority dstnat - 1; policy accept; }' >/dev/null 2>&1
nft add rule ip qubes hotspot-dnat-dns-$WIFI_INTERFACE iifname $WIFI_INTERFACE ip daddr 10.42.0.1 udp dport 53 dnat to 10.139.1.1
nft add rule ip qubes hotspot-dnat-dns-$WIFI_INTERFACE iifname $WIFI_INTERFACE ip daddr 10.42.0.1 tcp dport 53 dnat to 10.139.1.1
fi
EOF
sudo chmod +x /rw/config/wifi-hotspot-add.sh
cat << 'EOF' | sudo tee /rw/config/wifi-hotspot-remove.sh > /dev/null
#!/bin/sh
if [ "$(qubesdb-read /qubes-vm-persistence)" = "none" ]
then
WIFI_INTERFACE=$1
nft flush chain ip qubes hotspot-input-$WIFI_INTERFACE >/dev/null 2>&1
nft flush chain ip qubes hotspot-dnat-dns-$WIFI_INTERFACE >/dev/null 2>&1
fi
EOF
sudo chmod +x /rw/config/wifi-hotspot-remove.sh
sudo mkdir -p /rw/config/NM-system-connections
cat << EOF | sudo tee /rw/config/NM-system-connections/Hotspot.nmconnection > /dev/null
[connection]
id=Hotspot
type=wifi
autoconnect=true
interface-name=
[wifi]
mode=ap
ssid=$WIFI_SSID
[wifi-security]
group=ccmp;
key-mgmt=wpa-psk
pairwise=ccmp;
proto=rsn;
psk=$WIFI_PASSWORD
[ipv4]
method=shared
[ipv6]
addr-gen-mode=default
method=ignore
[proxy]
EOF
sudo chmod 600 /rw/config/NM-system-connections/Hotspot.nmconnection
sudo mkdir -p /rw/config/rc.local.d/
cat << 'EOF' | sudo tee /rw/config/rc.local.d/udev.rc > /dev/null
#!/bin/sh
udevadm trigger --subsystem-match=net --action=move --property-match=DEVTYPE=wlan
EOF
sudo chmod +x /rw/config/rc.local.d/udev.rc
1 Like
solene
September 17, 2025, 4:21pm
2
I started from scratch, in the current state this does not support starting with a PCI Wi-Fi interface.
This did not help:
mkdir -p /rw/config/rc.local-early.d/
cat << 'EOF' | tee /rw/config/rc.local-early.d/udev.rc > /dev/null
#!/bin/sh
ln -s /rw/bind-dirs/etc/udev/rules.d/99-wireless-detect.rules /etc/udev/rules.d/99-wireless-detect.rules
udevadm control --reload-rules
udevadm trigger
EOF
I’m not sure what’s wrong here.
Yes, rc.local-early doesn’t work:
The PCI network interface is added before rc.local-early scripts are started.
So I’ve end up running /rw/config/wifi-hotspot-add.sh in rc.local:
Also, I’ve ended up doing it like this:
sudo mkdir -p /rw/config/rc.local.d/
cat << 'EOF' | sudo tee /rw/config/rc.local.d/udev.rc > /dev/null
#!/bin/sh
WIFI_INTERFACE=$(cat /proc/net/wireless | sed -n 3p | cut -d: -f1)
if [ -n "$WIFI_INTERFACE" ]; then
/rw/config/wifi-hotspot-add.sh $WIFI_INTERFACE
fi
EOF
sudo chmod +x /rw/config/rc.local.d/udev.rc
solene
September 17, 2025, 4:32pm
4
I’ll try this, thanks
Maybe in WIFI_INTERFACES you should add head -n 1 at the end in case there are multiple wifi devices (an usb in auto attach and a pci)
It’s getting only first interface with sed -n 3p. First 2 lines are headers and 3rd line is the first interface.
1 Like
solene
September 17, 2025, 4:36pm
6
My PCI interface does not show up in /proc/net/wireless, so the script does not start the hotspot.
[root@sys-net-wifi-hotspot-dvm ~]# cat /proc/net/wireless
Inter-| sta-| Quality | Discarded packets | Missed | WE
face | tus | link level noise | nwid crypt frag retry misc | beacon | 22
solene
September 17, 2025, 4:37pm
7
iw dev output shows there is an interface
# iw dev
phy#0
Unnamed/non-netdev interface
wdev 0x2
addr XXX
type P2P-device
Interface wls7f0
ifindex 3
wdev 0x1
addr 0XXX
type managed
multicast TXQ:
qsz-byt qsz-pkt flows drops marks overlmt hashcol tx-bytes tx-packets
0 0 0 0 0 0 0 0 0
I’ll look int o it.
I wanted to avoid using iw so I wouldn’t need to install it in a minimal template, but if I won’t find another way to get the list of wireless interfaces, then I’ll change it to use iw.
1 Like
solene
September 17, 2025, 4:46pm
9
I’m surprised iw is not pulled in by Network manager.
Maybe if you want to go minimal, there is hostapd I think that could be used instead of Network Manager.
I found a better way to do this - you can just trigger the udev manually for all wireless devices from rc.local like this:
sudo mkdir -p /rw/config/rc.local.d/
cat << 'EOF' | sudo tee /rw/config/rc.local.d/udev.rc > /dev/null
#!/bin/sh
udevadm trigger --subsystem-match=net --action=move --property-match=DEVTYPE=wlan
EOF
sudo chmod +x /rw/config/rc.local.d/udev.rc
I’ll consider writing a minimal setup guide, since Network Manager is not really needed in this case.
1 Like
MellowPoison:
I’ll look int o it.
I wanted to avoid using iw so I wouldn’t need to install it in a minimal template, but if I won’t find another way to get the list of wireless interfaces, then I’ll change it to use iw.
Just to note, /proc/net/wireless seems to be deprecated:
2334171 – Missing /proc/net/wireless in 6.13.0-0-*
And if someone wants to check the device type without iw, then instead of checking /proc/net/wireless, it’s possible to get the list of all interfaces from /proc/net/dev and then for each of them check the DEVTYPE= value from /sys/class/net/$INTERFACE_NAME/uevent.
solene
September 17, 2025, 8:49pm
12
This reports wireless interfaces
awk -F ':' 'NR > 2 { print $1 }' /proc/net/dev | while read interface ; do if grep DEVTYPE=wlan /sys/class/net/$interface/uevent >/dev/null ; then echo $interface ; fi ; done
Or on multiple lines
awk -F ':' 'NR > 2 { print $1 }' /proc/net/dev | while read interface
do
if grep "^DEVTYPE=wlan$" "/sys/class/net/${interface}/uevent" >/dev/null
then
echo "$interface"
fi
done
1 Like