Wifibox или решение проблем с Wireless картами для FreeBSD
Если вы пробовали использовать FreeBSD на современных ноутбуках то гарантированно попадали на проблемы с WiFi-картами: нестабильность работы, зависания, умирание карты после цикла suspend-resume и так далее.
Не так давно появилось решение, про которое я сейчас расскажу.
Проблема
Проблема как обычно в драйверах, еще точнее — в особенностях устройства современных WiFi-чипов:
основной код находится в прошивке (firmware), которая (внимание) загружается динамически, фактически при старте ОС.
Видели наверное в интернете рекомендации по исправлению проблем с WiFi для Windows из серии: «включить-выключить, удалить драйвер и перезагрузить»?
Вот это оно, эти же торчащие уши сложной прошивки.
Вот так например выглядит сбой прошивки на одном из моих ноутбуков:
46.632684] iwlwifi 0000:00:06.0: Failed to load firmware chunk! [ 46.632694] iwlwifi 0000:00:06.0: Could not load the [6] uCode section [ 46.632711] iwlwifi 0000:00:06.0: Failed to start INIT ucode: -110 [ 46.632719] iwlwifi 0000:00:06.0: WRT: Collecting data: ini trigger 13 fired (delay=0ms). [ 46.883163] iwlwifi 0000:00:06.0: Not valid error log pointer 0x00000000 for Init uCode
И вообщем-то наглухо — без перезагрузки карта не оживает, никакие kldunload/kldload не помогут.
А в случае iwm драйвера, который вроде как является во FreeBSD основным для карт Intel, все еще веселее:
нужно перезагрузиться в Windows и там сделать shutdown — полное выключение.
Только после этого прошивка вновь загрузится и карта заработает во FreeBSD. Вообщем цирк да и только.
Но появилось решение, одновременно гениальное и трешовое — смотря с какой стороны посмотреть.
Wifibox
Оригинал статьи на английском тут, я все это повторил, запустил и проверил. Поэтому ниже перевод с моими важными дополнениями.
The TLDR is that wifibox essentially spins up an Alpine Linux VM using FreeBSD's bhyve virtualization technology, and allows you to passthrough your machine's wireless card from the host directly into the guest. To put the cherry on top, there is some "magic" (firewall forwarding rules and natting), that allows the traffic to flow between your host and your guest.
Вообщем во FreeBSD есть свой KVM, который называется поэтичным и легко запоминающимся именем Bhyve. В эту виртуальную машину уровня ядра можно «пробросить» физическое устройство из основной ОС.
В данном случае пробрасывается WiFi-карта в виртуалку, где запущено урезанное ядро Linux, с полноценной поддержкой карты.
Там же запускается wpa_supplicant и поднимается физическое соединение, дальше поднимается сетевой мост из основной ОС в гостевую.
Теперь расскажу как это все поставить и заставить работать.
Установка
Начнем с самого банального — с установки пакета wifibox:
pkg install wifibox
pkg info |grep wifibox wifibox-1.2.2 Wireless card driver via virtualized Linux wifibox-alpine-20230326 Wifibox guest based on Alpine Linux wifibox-core-0.11.0 Wifibox core functionality
Отключение автозагрузки драйверов Wifi в основной ОС
Очевидно что если мы пробрасываем устройство в гостевую ОС то из основной ОС ее использовать в этом время нельзя.
Поэтому нужно отключить автозагрузку драйверов для Wifi, добавив строку devmatch_blocklist= в файл /etc/rc.conf
Поскольку проблемы у меня (как и у автора оригинальной статьи) только с WiFi-картами Intel — именно драйвера для таких карт мы и отключаем, а именно iwm и iwlwifi.
Модули ядра при этом называются if_iwm и if_iwlwifi, указывать необходимо именно название модуля, обратите на это внимание.
В итоге должно получиться что-то такое:
root@aurora:/home/alex # cat /etc/rc.conf |grep devmatch devmatch_blocklist="if_iwm if_iwlwifi"
После этого необходимо перезагрузиться.
Адрес устройства
После перезагрузки нужно проверить что карта в основной ОС нашлась но драйвер к ней не подцепился.
pciconf -lv | less
и ищем в выводе слово "Wireless".
ppt0@pci0:0:20:3: class=0x028000 rev=0x11 hdr=0x00 vendor=0x8086 device=0x9df0 subvendor=0x8086 subdevice=0x0030 vendor = 'Intel Corporation' device = 'Cannon Point-LP CNVi [Wireless-AC]' class = network
У вас должно быть не ppt@ а none@ , как у автора в оригинальной статье:
none@pci0:0:20:3: class=0x028000 rev=0x00 hdr=0x00 vendor=0x8086 device=0x02f0 subvendor=0x8086 subdevice=0x0030 vendor = 'Intel Corporation' device = 'Comet Lake PCH-LP CNVi WiFi' class = network
Просто я пишу эту статью с уже настроенным и работающим подключением и ppt это как раз признак проброса устройства.
Так получилось что он савпал у меня и у автора оригинальной статьи, при этом чипы разные. Поэтому вполне может совпасть и у вас.
Настройка Bhyve
Дальше вам необходимо настроить сам эмулятор.
Открываете файл /usr/local/etc/wifibox/bhyve.conf в вашем любимом vi/vim/emacs и настраиваете.
Самое главное тут это параметр passthru , которому нужно поставить значение равное номеру вашей Wifi-карты, который вы нашли абзацем выше.
Также лучше сразу поставить console=yes , что даст возможность подключаться к запущенной виртуальной машине с помощью команды:
Настройка памяти
По умолчанию значение memory равно 45Мб, что слишком мало для нормальной работы этой чудо-виртуалки.
Автор оригинальной статьи рекомендует 512Мб, что мне показалось чрезмерным, поэтому я вполне нормально работаю со 128Мб.
Вот полностью мой работающий конфиг:
cat /usr/local/etc/wifibox/bhyve.conf # These are the default values for launching the bhyve(8) guest, # please revisit them. # Number of virtual CPUs allocated for the guest, which determines the # count of concurrent execution threads. cpus=1 # Maximum amount of memory allocated for the guest. This is rather a # conservative default, and it is worth considering to lower this # value when possible. memory=128M # Change this to `yes` to activate the nmdm(4)-based console. Usually # this is not needed hence it is disabled by default. console=yes # The value of `passthru` has to match with the slot/bus/function of # the wireless PCI device, which can be obtained from the output of # the pciconf(8) tool. THIS MUST BE SET otherwise the device will not # be visible for the guest. Expected format: "s/b/f", e.g."3/0/0" for # the `pci0:3:0:0` device. passthru=0/20/3
Настройка wpa_supplicant
Оригинал настройки лучше хранить в основной ОС и просто копировать в гостевую, поскольку править внутри виртуалки неудобно из-за busybox окружения.
cp /etc/wpa_supplicant.conf /usr/local/etc/wifibox/wpa_supplicant/
Настройка сети в /etc/rc.conf
Before we boot up the VM, let's also add our network configuration to /etc/rc.conf, so that when your machine starts up, it will start the VM automatically and request the IP from the VM. The actual LAN IP will be inside the VM (since that's what's talking to the access point directly). The host will receive an internal IP (In the range of 10.0.0.2 - 10.0.0.254).
Тут конечно по-хорошему нужно было сделать статичный ip и чистый мост, но мне было уже некогда.
Вообщем мы будем получать IP-адрес виртуального адаптера в основной ОС через DHCP запущенном в гостевой ОС.
По-умолчанию адреса будут в диапазоне 10.0.0.2 - 10.0.0.254б, что пересекается с адресами отдаваемыми моим роутером.
Поэтому точно также как и автору мне пришлось подкручивать этот диапазон в файлах каталога /usr/local/etc/wifibox/appliance
Конкретно это файлы interfaces.conf, udhcpd.conf и uds_passthru.conf.
После этого, необходимо добавить вот такие настройки в файл /etc.rc.conf:
wifibox_enable="YES" ifconfig_wifibox0="SYNCDHCP" background_dhclient_wifibox0="YES" defaultroute_delay="0"
Очевидно что предыдущую настройку для обычного драйвера будет нужно убрать:
#ifconfig_wlan0="WPA DHCP"
Запуск
Наконец весь этот цирк на конной тяге можно попытаться запустить:
service wifibox start
Если вы все настроили правильно — должен заработать ping наружу:
root@aurora:/home/alex # ping www.ru PING www.ru (31.177.76.70): 56 data bytes 64 bytes from 31.177.76.70: icmp_seq=0 ttl=49 time=59.253 ms 64 bytes from 31.177.76.70: icmp_seq=1 ttl=49 time=83.307 ms 64 bytes from 31.177.76.70: icmp_seq=2 ttl=49 time=40.672 ms 64 bytes from 31.177.76.70: icmp_seq=3 ttl=49 time=88.820 ms ^C -— www.ru ping statistics -— 4 packets transmitted, 4 packets received, 0.0% packet loss round-trip min/avg/max/stddev = 40.672/68.013/88.820/19.307 ms
Но скорее всего сразу не заработает, поэтому запускаем:
wifibox console
и видим шелл виртуальной машины:
Если появляется запрос авторизации — входите под учетной записью root без пароля.
Смотрим логи, особенно связанные с драйвером WiFi-карты:
wifibox: ifconfig -a eth0 Link encap:Ethernet HWaddr 00:A0:98:8A:05:71 inet addr:10.1.0.1 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::2a0:98ff:fe8a:571/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:17068 errors:0 dropped:0 overruns:0 frame:0 TX packets:24454 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:4668491 (4.4 MiB) TX bytes:22744732 (21.6 MiB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) sit0 Link encap:IPv6-in-IPv4 NOARP MTU:1480 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) wlan0 Link encap:Ethernet HWaddr 7C:B2:7D:34:35:7D inet addr:192.168.2.150 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::7eb2:7dff:fe34:357d/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:26401 errors:0 dropped:167 overruns:0 frame:0 TX packets:17364 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:22989895 (21.9 MiB) TX bytes:5162382 (4.9 MiB)
Вот так настройка сети выглядит со стороны основной ОС:
root@aurora:/home/alex # ifconfig -a em0: flags=8822<BROADCAST,SIMPLEX,MULTICAST> metric 0 mtu 1500 options=481049b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,LRO,VLAN_HWFILTER,NOMAP> ether 48:2a:e3:5e:66:85 media: Ethernet autoselect status: no carrier nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL> lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384 options=680003<RXCSUM,TXCSUM,LINKSTATE,RXCSUM_IPV6,TXCSUM_IPV6> inet6 ::1 prefixlen 128 inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2 inet 127.0.0.1 netmask 0xff000000 groups: lo nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL> wifibox0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500 ether 58:9c:fc:10:ff:a0 inet 10.1.0.2 netmask 0xffffff00 broadcast 10.1.0.255 id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15 maxage 20 holdcnt 6 proto rstp maxaddr 2000 timeout 1200 root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0 member: tap0 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP> ifmaxaddr 0 port 4 priority 128 path cost 2000000 groups: bridge nd6 options=9<PERFORMNUD,IFDISABLED> tap0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500 options=80000<LINKSTATE> ether 58:9c:fc:10:ff:bb groups: tap media: Ethernet autoselect status: active nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL> Opened by PID 308
Решение проблем с suspend/resume
Вы же не забыли что все это происходит на ноутбуке?
А ноутбуку положено засыпать и просыпаться, возвращаясь к работе.
К сожалению так просто из коробки оно обычно не работает (тем более во FreeBSD), нужно немного подкрутить.
Решение для Bhyve я нашел как обычно копаясь в баг-репортах других пользователей, вот тут.
По итогам было создано два шелл-скрипта, один запускается до засыпания (suspend):
#!/bin/sh wifibox stop guest sleep 3 kldunload vmm
Второй — после пробуждения (resume):
#!/bin/sh kldload vmm wifibox start guest
Эти скрипты запускаются автоматически, поскольку указаны в настройке devd:
cat /usr/local/etc/devd/wifibox.conf notify 11 { match "system" "ACPI"; match "subsystem" "Suspend"; action "logger 'Stopping wifibox before suspend' && /opt/own/bin/pre-suspend && /etc/rc.suspend acpi $notify"; }; notify 11 { match "system" "ACPI"; match "subsystem" "Resume"; action "/etc/rc.resume acpi $notify && logger 'Starting wifibox after resume and getting IP via DHCP' && /opt/own/bin/post-resume"; };
К сожалению этого оказалось недостаточно и пришлось подкручивать еще и сам скрипт засыпания /etc/rc.suspend:
# Notify the kernel to continue the suspend process /usr/sbin/acpiconf -s S3
Суть правки в том чтобы использовать acpiconf -s S3 т.е засыпание с использованием S3 state вместо acpiconf -k 0 , который занимается автоопределением .
Обновление от 12 сентября 2023
К сожалению даже всех этих приседаний нехватило доя нормальной работы и пришлось жечь дальше.
Два-три цикла suspend/resume проходили нормально, дальше ноутбук перезагружался или зависал, поэтому я продолжил изыскания.
Вот так выглядит финальный рабочий скрипт засыпания:
#!/bin/sh pkill -f /dev/nmdm-wifibox.1B echo "root" | cu -s 115200 -l /dev/nmdm-wifibox.1B pkill -f /dev/nmdm-wifibox.1B echo "echo 1 > /sys/bus/pci/devices/0000:00:06.0/remove" | cu -s 115200 -l /dev/nmdm-wifibox.1B pkill -f /dev/nmdm-wifibox.1B #sleep 3 ifconfig wifibox0 down ifconfig tap0 down wifibox stop sleep 3 kldunload vmm kldunload vmm sleep 3
мы вызываем специальное указание ядру линукса на отключение проброшенного через PCI Pass-through устройства, в нашем случае — все той же Wifi-карты.
echo 1 > /sys/bus/pci/devices/0000:00:06.0/remove
Где 0000:00:06 это адрес карты, который можно найти в выдаче lspci:
wifibox:~# lspci -k 00:1f.0 Class 0601: 8086:7000 00:04.2 Class 0100: 1af4:1009 virtio-pci 00:04.0 Class 0100: 1af4:1001 virtio-pci 00:00.0 Class 0600: 1275:1275 00:04.3 Class 0100: 1af4:1009 virtio-pci 00:06.0 Class 0280: 8086:9df0 iwlwifi 00:04.1 Class 0100: 1af4:1009 virtio-pci 00:05.0 Class 0200: 8086:100f e1000
00:06.0 Class 0280: 8086:9df0 iwlwifi
Но это еще не все, потому что теперь надо как-то сделать этот вызов в виртуальном линуксе из хоста FreeBSD перед засыпанием.
Я вначале думал поднять sshd и делать удаленный запуск по ssh, но это бы потребовало кастомизации сборки образа Alpine, который очень сильно порезан.
Дело в том что со стороны виртуального линукса запускается socat, через который происходит переброс консоли в хост — то что вы видите при запуке wifibox console.
Чтобы отправить команду через эту штуку я распотрошил сам скрипт wifibox, в результате получил вот такое:
echo "echo 1 > /sys/bus/pci/devices/0000:00:06.0/remove" | cu -s 115200 -l /dev/nmdm-wifibox.1B
Устройство /dev/nmdm-wifibox.1B имеет фиксированное имя и его создает сам wifibox при запуске.
Следующим важным моментом оказалась необходимость ввести логин юзера в виртуальном линуксе перед запуском команды:
echo "root" | cu -s 115200 -l /dev/nmdm-wifibox.1B
Но и это еще не все, дело в том что у меня не получилось заставить cu разрывать соеднинение сразу после отправки команды и он продолжал висеть в процессах.
Так что пришлось его убивать наглухо:
pkill -f /dev/nmdm-wifibox.1B
Дальше мы останавливаем интерфейсы:
ifconfig wifibox0 down ifconfig tap0 down
и продолжаем всю логику с выключением wifibox.
Дополнительно
Команды iwconfig как в нормальном линуксе тут нет, зато есть iw.
Поэтому для отображения текущего состояния, в том числе названия точки, к которой вы подключились можно использовать:
wifibox: iw wlan0 info Interface wlan0 ifindex 4 wdev 0x1 addr 7c:b2:7d:34:35:7d ssid Redmi type managed wiphy 0 channel 8 (2447 MHz), width: 20 MHz, center1: 2447 MHz txpower 22.00 dBm multicast TXQ: qsz-byt qsz-pkt flows drops marks overlmt hashcol tx-bytestx-packets 0 0 0 0 0 0 0 0 0
Для сканирования и отображения списка найденных AP (Access Point) точек подключения:
iw dev wlan0 scan