Wi-Fi hotspot from Qubes OS using udev

This guide is for configuring Wi-Fi hotspot disposable qube using udev rules.
You can refer to this guide as a base:

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

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:

I’ll try this, thanks :slight_smile:

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

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

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

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

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.

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