unix
June 8, 2023

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

Оригинал статьи на английском тут, я все это повторил, запустил и проверил. Поэтому ниже перевод с моими важными дополнениями.

Что такое 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 и поднимается физическое соединение, дальше поднимается сетевой мост из основной ОС в гостевую.

И все начинает работать.

Это FreeBSD детка!

Теперь расскажу как это все поставить и заставить работать.

Установка

Начнем с самого банального — с установки пакета 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 это как раз признак проброса устройства.

Искомый адрес:

0:20:3

Так получилось что он савпал у меня и у автора оригинальной статьи, при этом чипы разные. Поэтому вполне может совпасть и у вас.

Настройка Bhyve

Дальше вам необходимо настроить сам эмулятор.

Открываете файл /usr/local/etc/wifibox/bhyve.conf в вашем любимом vi/vim/emacs и настраиваете.

Самое главное тут это параметр passthru , которому нужно поставить значение равное номеру вашей Wifi-карты, который вы нашли абзацем выше.

Также лучше сразу поставить console=yes , что даст возможность подключаться к запущенной виртуальной машине с помощью команды:

wifibox console

Настройка памяти

По умолчанию значение 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