├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug-report.yml └── workflows │ ├── man.yml │ └── shellcheck.yml ├── devd └── wifibox.conf.sample ├── rc.d └── wifibox ├── etc ├── core.conf.sample └── bhyve.conf.sample ├── LICENSE ├── Makefile ├── README.md ├── man └── wifibox.8 └── sbin └── wifibox /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Wifibox Support 4 | url: https://github.com/pgj/freebsd-wifibox/discussions 5 | about: Please ask and answer questions, share project related information here. 6 | -------------------------------------------------------------------------------- /.github/workflows/man.yml: -------------------------------------------------------------------------------- 1 | name: Manual page linter 2 | on: 3 | push: 4 | paths: 5 | - 'man/**' 6 | - '.github/workflows/man.yml' 7 | 8 | jobs: 9 | Checks: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: vmactions/freebsd-vm@v1 14 | with: 15 | usesh: true 16 | prepare: | 17 | pkg install -y igor 18 | 19 | run: | 20 | make VERSION=9.9.9 mancheck 21 | -------------------------------------------------------------------------------- /.github/workflows/shellcheck.yml: -------------------------------------------------------------------------------- 1 | name: ShellCheck 2 | on: 3 | push: 4 | paths: 5 | - 'sbin/**' 6 | - '.github/workflows/shellcheck.yml' 7 | 8 | jobs: 9 | Checks: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: vmactions/freebsd-vm@v1 14 | with: 15 | usesh: true 16 | prepare: | 17 | pkg install -y hs-ShellCheck 18 | 19 | run: | 20 | make VERSION=9.9.9 shellcheck 21 | -------------------------------------------------------------------------------- /devd/wifibox.conf.sample: -------------------------------------------------------------------------------- 1 | # This is a shim `devd(8)` configuration file to implement calling the 2 | # `wifibox` service on `suspend` because that is not supported by the 3 | # base system. Review the contents and create a copy of it without 4 | # the `.sample` extension to use it. Restart the `devd` service once 5 | # the file has been created. 6 | # 7 | notify 11 { 8 | match "system" "ACPI"; 9 | match "subsystem" "Suspend"; 10 | action "service wifibox suspend"; 11 | action "/etc/rc.suspend acpi $notify"; 12 | }; 13 | -------------------------------------------------------------------------------- /rc.d/wifibox: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # PROVIDE: wifibox 4 | # BEFORE: netif 5 | # REQUIRE: FILESYSTEMS sysctl kld 6 | # KEYWORD: shutdown nojail suspend resume 7 | 8 | . /etc/rc.subr 9 | 10 | : ${wifibox_enable="NO"} 11 | 12 | name=wifibox 13 | desc="Manage wifibox on boot, shutdown, suspend, and resume" 14 | rcvar=wifibox_enable 15 | extra_commands="suspend resume" 16 | 17 | load_rc_config ${name} 18 | 19 | command="%%PREFIX%%/sbin/${name}" 20 | start_cmd="${command} start" 21 | stop_cmd="${command} stop" 22 | status_cmd="${command} status" 23 | suspend_cmd="%%SUSPEND_CMD%%" 24 | resume_cmd="%%RESUME_CMD%%" 25 | 26 | run_rc_command "$1" 27 | -------------------------------------------------------------------------------- /etc/core.conf.sample: -------------------------------------------------------------------------------- 1 | # These are the default values that control the overall behavior of 2 | # wifibox. 3 | 4 | # Level of logging. Verbosity increases in the following order. 5 | # 6 | # - none: No messages are either displayed or logged. 7 | # - error: Only error messages are displayed and logged. 8 | # - warn: Error and warning messages are displayed and logged. That 9 | # is the default. 10 | # - info: Besides error and warning messages, informative messages 11 | # about the execution are logged. 12 | # - debug: In addition to errors, warnings, and informative messages, 13 | # some extra information about the program's state is logged. 14 | # Useful for troubleshooting, but mostly makes sense when 15 | # read together with the source code. 16 | loglevel=warn -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021-25, PÁLI Gábor János 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /etc/bhyve.conf.sample: -------------------------------------------------------------------------------- 1 | # These are the default values for launching the bhyve(8) guest, 2 | # please revisit them. 3 | 4 | # Number of virtual CPUs allocated for the guest, which determines the 5 | # count of concurrent execution threads. 6 | cpus=1 7 | 8 | # Maximum amount of memory allocated for the guest. This is rather a 9 | # conservative default, and it is worth considering to lower this 10 | # value when possible. 11 | memory=128M 12 | 13 | # Change this to `yes` to activate the nmdm(4)-based console. Usually 14 | # this is not needed hence it is disabled by default. 15 | console=no 16 | 17 | # The value of `passthru` has to match with the slot/bus/function of 18 | # the wireless PCI device, which can be obtained from the output of 19 | # the pciconf(8) tool. THIS MUST BE SET otherwise the device will not 20 | # be visible for the guest. Expected format: "s/b/f", e.g."3/0/0" for 21 | # the `pci0:3:0:0` device. 22 | # 23 | # Note that it is possible to include other PCI devices, for example 24 | # the USB xHCI controller in case interaction with the Bluetooth 25 | # device is needed. Separate the slot/bus/function value with space, 26 | # e.g. "3/0/0 0/20/0", where `pci0:0:20:0` is the xHCI controller. 27 | passthru= 28 | 29 | # Priority of the bhyve(8) process that runs the guest. This has to 30 | # be set in the range of 0 to 99, where 0 is the highest possible 31 | # priority while 99 is the lowest. 32 | priority=50 33 | 34 | # The maximal length of the grace period for waiting the bhyve(8) 35 | # process to exit on signalling ACPI power-off. This is in seconds, 36 | # approximately, within the range of 1 to 60. If the guest does not 37 | # stop after the period is over, a forceful shutdown is attempted, 38 | # which might leave the hardware in inconsistent state. 39 | stop_wait_max=30 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PREFIX?=/usr/local 2 | LOCALBASE?=/usr/local 3 | GUEST_ROOT?=$(LOCALBASE)/share/wifibox 4 | RECOVERY_METHOD?=restart_vmm 5 | 6 | BINDIR=$(DESTDIR)$(PREFIX)/sbin 7 | ETCDIR=$(DESTDIR)$(PREFIX)/etc 8 | RCDIR=$(ETCDIR)/rc.d 9 | SHAREDIR=$(DESTDIR)$(PREFIX)/share 10 | MANDIR=$(SHAREDIR)/man 11 | 12 | MKDIR=/bin/mkdir 13 | LN=/bin/ln 14 | SED=/usr/bin/sed 15 | CP=/bin/cp 16 | CHMOD=/bin/chmod 17 | GZIP=/usr/bin/gzip 18 | GIT=$(LOCALBASE)/bin/git 19 | SHELLCHECK=$(LOCALBASE)/bin/shellcheck 20 | UNAME=/usr/bin/uname 21 | IGOR=${LOCALBASE}/bin/igor 22 | ASPELL=${LOCALBASE}/bin/aspell 23 | MANDOC=/usr/bin/mandoc 24 | ECHO=/bin/echo 25 | TOUCH=/usr/bin/touch 26 | RM=/bin/rm -f 27 | 28 | .if !defined(VERSION) 29 | VERSION!= $(GIT) describe --tags --always 30 | .endif 31 | 32 | .if defined(GUEST_MAN) 33 | _GUEST_MAN= ${GUEST_MAN} 34 | .else 35 | _GUEST_MAN= ../man8/wifibox.8.gz 36 | .endif 37 | 38 | .if !defined(DEVD_FIX) 39 | _FREEBSD_VERSION!= $(UNAME) -U 40 | 41 | .if $(_FREEBSD_VERSION) > 1400089 42 | DEVD_FIX= # 43 | .else 44 | DEVD_FIX= please 45 | .endif 46 | 47 | .endif 48 | 49 | SUB_LIST= PREFIX=$(PREFIX) \ 50 | LOCALBASE=$(LOCALBASE) \ 51 | VERSION=$(VERSION) \ 52 | GUEST_ROOT=$(GUEST_ROOT) 53 | 54 | .if ${RECOVERY_METHOD} == restart_vmm 55 | SUB_LIST+= SUSPEND_CMD=/usr/bin/true \ 56 | RESUME_CMD='$${command} restart vmm' 57 | .elif ${RECOVERY_METHOD} == suspend_guest 58 | SUB_LIST+= SUSPEND_CMD='$${command} stop guest' \ 59 | RESUME_CMD='$${command} start guest' 60 | .elif ${RECOVERY_METHOD} == suspend_vmm 61 | SUB_LIST+= SUSPEND_CMD='$${command} stop vmm' \ 62 | RESUME_CMD='$${command} start vmm' 63 | .else 64 | SUB_LIST+= SUSPEND_CMD=/usr/bin/true \ 65 | RESUME_CMD=/usr/bin/true 66 | .endif 67 | 68 | _SUB_LIST_EXP= ${SUB_LIST:S/$/!g/:S/^/ -e s!%%/:S/=/%%!/} 69 | _SCRIPT_SRC= sbin/wifibox 70 | _MAN_SRC= man/wifibox.8 71 | 72 | install: 73 | $(MKDIR) -p $(BINDIR) 74 | $(SED) ${_SUB_LIST_EXP} ${_SCRIPT_SRC} > $(BINDIR)/wifibox 75 | $(CHMOD) 555 $(BINDIR)/wifibox 76 | 77 | $(MKDIR) -p $(ETCDIR)/wifibox 78 | $(CP) -R etc/* $(ETCDIR)/wifibox/ 79 | 80 | .if defined(DEVD_FIX) 81 | $(MKDIR) -p $(ETCDIR)/devd 82 | $(SED) ${_SUB_LIST_EXP} devd/wifibox.conf.sample \ 83 | > $(ETCDIR)/devd/wifibox.conf.sample 84 | .endif 85 | 86 | $(MKDIR) -p $(RCDIR) 87 | $(SED) ${_SUB_LIST_EXP} rc.d/wifibox > $(RCDIR)/wifibox 88 | $(CHMOD) 555 $(RCDIR)/wifibox 89 | 90 | $(SED) ${_SUB_LIST_EXP} ${_MAN_SRC} \ 91 | | $(GZIP) -c > $(MANDIR)/man8/wifibox.8.gz 92 | $(LN) -s ${_GUEST_MAN} $(MANDIR)/man5/wifibox-guest.5.gz 93 | 94 | .MAIN: clean 95 | 96 | clean: ; 97 | 98 | shellcheck: 99 | @$(SHELLCHECK) -x ${_SCRIPT_SRC} 100 | 101 | mancheck: 102 | @${ECHO} mandoc -T lint 103 | # Create a dummy manual page to suppress the `mandoc` warning 104 | @${TOUCH} wifibox-guest.5 105 | @$(SED) ${_SUB_LIST_EXP} ${_MAN_SRC} | ${MANDOC} -T lint 106 | @${RM} wifibox-guest.5 107 | @${ECHO} igor 108 | @$(SED) ${_SUB_LIST_EXP} ${_MAN_SRC} | ${IGOR} 109 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Tell if something is not working. 3 | labels: ["bug"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: textarea 10 | id: description 11 | attributes: 12 | label: Description 13 | value: | 14 | A clear and concise description of what the problem is. What is expected from the application? 15 | validations: 16 | required: true 17 | - type: textarea 18 | id: configuration-os 19 | attributes: 20 | render: shell 21 | label: Host operating system 22 | value: | 23 | Output of `uname -a`. 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: configuration-wireless-nic 28 | attributes: 29 | render: shell 30 | label: Wireless NIC 31 | value: | 32 | Relevant output of `pciconf -lv`. 33 | validations: 34 | required: true 35 | - type: textarea 36 | id: configuration-wifibox 37 | attributes: 38 | render: shell 39 | label: Wifibox version 40 | value: | 41 | Output of `wifibox version`. Note that only the latest version is supported. 42 | validations: 43 | required: true 44 | - type: textarea 45 | id: configuration-image 46 | attributes: 47 | label: Disk image type and version 48 | value: | 49 | The kind of VM image in use, e.g. Wifibox/Alpine, and its version. 50 | validations: 51 | required: true 52 | - type: textarea 53 | id: configuration-extra 54 | attributes: 55 | render: shell 56 | label: Changes to the default configuration files 57 | value: | 58 | Include relevant sections from all the configuration files that contain local changes and may help to reproduce the problem. 59 | - type: textarea 60 | id: logs 61 | attributes: 62 | render: shell 63 | label: Logs 64 | value: | 65 | Please copy and paste any relevant log output from the following sources: 66 | 67 | - /var/log/wifibox.log (with DEBUG logging verbosity) 68 | - /var/run/wifibox/appliance/log/dmesg 69 | - /var/run/wifibox/appliance/log/messages 70 | 71 | Hint: The DEBUG log verbosity could be configured in the `core.conf` file, which is located in the `$LOCALBASE/etc/wifibox` directory. 72 | validations: 73 | required: true 74 | - type: textarea 75 | id: extra 76 | attributes: 77 | label: Additional context 78 | value: | 79 | Add any other context about the problem here that might help the investigation. 80 | - type: checkboxes 81 | id: documentation 82 | attributes: 83 | label: Have you tried to turn it on and off? 84 | description: | 85 | I declare that I have spent considerable amount of time on reading through the `wifibox(8)` and `wifibox-guest(5)` manual pages before creating this ticket. I will not ask about things that are clearly documented there. Maintainers reserve the right to leave such issues unanswered or close them immediately. Thank you for saving them the time. 86 | options: 87 | - label: Yes, I have read all the manual pages first! 88 | required: true 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project FreeBSD Wifibox 2 | 3 | Wifibox deploys a Linux guest to drive a wireless networking card on 4 | the FreeBSD host system with the help of PCI pass-through. There have 5 | been guides on the Internet to suggest the use of such techniques to 6 | improve the wireless networking experience on FreeBSD, of which 7 | Wifibox tries to implement as a single easy-to-use software package. 8 | 9 | - [`bhyve`], a lightweight virtualization solution for FreeBSD, is 10 | utilized to run the embedded Linux system. This helps to achieve 11 | low resource footprint. 12 | 13 | - Configuration files could be shared with the host system. For 14 | example, the guest may either use `wpa_supplicant(8)` or 15 | `hostapd(8)` and it is possible to import the host's 16 | `wpa_supplicant.conf(5)` and `hostapd.conf(5)` files without any 17 | changes. 18 | 19 | - When configured by the guest, `wpa_supplicant(8)` or `hostapd(8)` 20 | control sockets could be exposed, which enables use of related 21 | utilities directly from the host, such as `wpa_cli(8)` or 22 | `wpa_gui(8)` from the [`net/wpa_supplicant_gui`] FreeBSD package, or 23 | `hostapd_cli(8)`. 24 | 25 | - Everything is shipped in a single FreeBSD package that can be easily 26 | installed and removed. It comes with an `rc(8)` system service that 27 | automatically launches the guest on boot and stops it on shutdown. 28 | 29 | - A workaround is supplied for laptops to support suspend/resume. 30 | 31 | For more information on the background and the high-level overview of 32 | Wifibox, please read the [article] in the November/December 2024 33 | edition of the FreeBSD Journal on Virtualization. 34 | 35 | ## Warning 36 | 37 | *This is a work-in-progress experimental software project without any 38 | guarantees or warranties. It is shared in the hope that is going to 39 | be useful and inspiring for others. By its nature, it is a workaround 40 | and shall be deprecated once the FreeBSD wireless drivers and 41 | networking stack are updated to catch up with Linux.* 42 | 43 | *Wifibox does not necessarily offer a drop-in replacement for the 44 | wireless networking stack of FreeBSD. This is entirely determined by 45 | how the guest exposes network traffic for the host, which might happen 46 | via Network Address Translation (NAT) or bridging, for example. Be 47 | sure to consult the documentation of the guest itself before use.* 48 | 49 | ## Prerequisites 50 | 51 | Before the installation, please check if those items are present on 52 | the target computer otherwise running the software might not be 53 | possible: 54 | 55 | - A CPU that is supported by [`bhyve`] PCI pass-through (I/O MMU) with 56 | ~256 MB physical memory or less depending on the guest, and some 57 | disk space available for the guest virtual disk image. 58 | 59 | - A PCI wireless card that is known to be supported by the recent 60 | Linux versions, but it is not performing well enough under FreeBSD. 61 | Also, the Linux driver has to support [Message Signaled Interrupts] 62 | (MSI) because that is required for the PCI pass-through to work. 63 | USB wireless adapters are not supported. 64 | 65 | - A supported FreeBSD/amd64 system: 13.5-RELEASE or 14.3-RELEASE. 66 | Later versions will also probably work, but your mileage may vary. 67 | 68 | - [`grub2-bhyve`] or the corresponding `sysutils/grub2-bhyve` FreeBSD 69 | package, so the Linux guest could be booted via GRUB 2. 70 | 71 | - [`socat`] or the respective `net/socat` FreeBSD package, through 72 | which control sockets for `wpa_supplicant(8)` and `hostapd(8)` could 73 | be published on the host. The presence of `socat` is required only 74 | if this feature is activated, which depends on the guest 75 | configuration. 76 | 77 | ## Installation 78 | 79 | Use the `net/wifibox` FreeBSD port which is available at the 80 | [`freebsd-wifibox-port`] repository and automatically takes care of all 81 | the following details, installs a guest image, and offers proper 82 | removal of the installed files, hence it is a more convenient way to 83 | manage the whole installation process. 84 | 85 | ### Manual Installation 86 | 87 | Alternatively, a `Makefile` is present in this repository that can be 88 | used to install all the files, as described below. This workflow is 89 | mostly recommended for development and testing. 90 | 91 | ```console 92 | # make install \ 93 | PREFIX= \ 94 | LOCALBASE= \ 95 | GUEST_ROOT= \ 96 | GUEST_MAN= \ 97 | RECOVERY_METHOD= \ 98 | DEVD_FIX= 99 | ``` 100 | 101 | By default, `PREFIX` is set to `/usr/local`. In addition to that, it 102 | is possible to set the `LOCALBASE` variable to tell if the prefix 103 | under which the `grub-bhyve` and `socat` utilities were installed is 104 | different. 105 | 106 | The `GUEST_ROOT` variable should point to the directory that houses 107 | the files related to the guest. Note that these are not part of the 108 | repository and should be installed individually. For example, such 109 | files could be installed from the [`freebsd-wifibox-alpine`] 110 | repository. 111 | 112 | - GRUB is going to be configured according to the contents of 113 | `grub.cfg`, and then the system is booted from the virtual disk 114 | image whose contents should be stored as `disk.img`. 115 | 116 | - When needed, `device.map` could also be placed there to teach GRUB 117 | about the virtual disk image. 118 | 119 | The `RECOVERY_METHOD` variable can be used to tell in which way 120 | Wifibox should be revived on a suspend/resume pair of events. 121 | 122 | - The default value is `restart_vmm`, which means that guest will be 123 | stopped, and the `vmm(4)` kernel module will be reloaded then the 124 | guest will be restarted on resume. 125 | - Another option is `suspend_guest`, which will stop the only guest on 126 | suspend and then start it again on resume. 127 | - Finally, there is `suspend_vmm`, which will stop both the guest and 128 | unload the `vmm(4)` kernel module on suspend and implement the 129 | reverse on resume. 130 | - The recovery mechanism itself could be disabled by setting this 131 | value to be empty. 132 | 133 | The `DEVD_FIX` variable controls the deployment of a fix for handling 134 | the ACPI suspend event. In older FreeBSD versions, suspend will not 135 | automatically trigger a call for the `service wifibox suspend` command 136 | so that has to be explicitly configured. This has been added for 137 | FreeBSD 14.0 hence it is not required any more from that version 138 | onwards. Set it to an empty value to disable this fix, otherwise the 139 | default value is going to be determined based on the OS version where 140 | the build is run. 141 | 142 | ## Documentation 143 | 144 | There is a manual page installed that can be used to learn about the 145 | basic usage and configuration. 146 | 147 | ```console 148 | # man wifibox 149 | ``` 150 | 151 | ## Compatibility 152 | 153 | It has been reported working successfully on the following 154 | configurations: 155 | 156 | | CPU | Wireless NIC | Model | FreeBSD | 157 | | :-- | :----------- | :---- | :------ | 158 | | AMD A6-9225 | Realtek RTL8821CE | Lenovo IdeaPad 330-15AST | 13.1-RELEASE, 14-CURRENT | 159 | | AMD Ryzen 3 5300U | Realtek RTL8852CE | HP HP Laptop 15s-eq2xxx | 14.1-RELEASE | 160 | | AMD Ryzen 5 5600G | Intel Wi-Fi 6 AX-200 | ASUS ROG STRIX B550-I GAMING | 13-STABLE, 14-CURRENT | 161 | | AMD Ryzen 5 5600G | AMD RZ608 Wi-Fi 6E (MediaTek MT7921K) | ASUS ROG STRIX B550-I GAMING | 13-STABLE, 14-CURRENT | 162 | | AMD Ryzen 5 PRO 8540U | AMD RZ616 Wi-Fi 6E 802.11ax (MediaTek MT7922) | HP EliteBook 845 G11 (8R632AV) | GhostBSD 24.10.1 | 163 | | AMD Ryzen 7 5700U | Realtek RTL8852AE | HP 255 G8 | 13.2-RELEASE | 164 | | AMD Ryzen 7 5700X | Intel Wi-Fi 6 AX-200 | GigaByte X570S | 13-STABLE, 14-CURRENT | 165 | | AMD Ryzen 7 5700X | AMD RZ608 Wi-Fi 6E (MediaTek MT7921K) | GigaByte X570S | 13-STABLE, 14-CURRENT | 166 | | AMD Ryzen 7 5825U | Realtek RTL8852BE | HP Laptop 15s-eq3636nz | 13.2-RC3 | 167 | | AMD Ryzen 9 9950X | Intel Wi-Fi 6E AX210 | Minisforum MS-A1-N0CPUR | 14.2-RELEASE | 168 | | Intel Core i5-3210M | Broadcom BCM4331 | Apple MacBook Pro A1278 | 13.2-RELEASE | 169 | | Intel Core i5-5300U | Intel Wireless 7265 | Lenovo ThinkPad T450 | 13.1-RELEASE | 170 | | Intel Core i5-6300U | Intel Dual Band Wireless AC 8260 | Lenovo ThinkPad X270 | 13.5-RELEASE, 14.3-RELEASE, 15.0-ALPHA3 (snapshot `20250921-26988773d1da-280233`) | 171 | | Intel Core i5-10210U | Intel Dual Band Wireless AC 9500 | System 76 Lemur Pro 'LEMP9' | 13.0-RELEASE | 172 | | Intel Core i5-8250U | Realtek RTL8822BE | Lenovo YOGA 730 | 13.2-RELEASE | 173 | | Intel Core i7-4600M | Intel Centrino Advanced-N 6235 | Dell Latitude E6440 | 13.0-RELEASE | 174 | | Intel Core i7-4870HQ | Broadcom BCM43602 | Apple MacBook Pro 11.4 | 13.3-RELEASE | 175 | | Intel Core i7-6600U | Intel(R) Dual Band Wireless AC 8260 | Lenovo ThinkPad T470 | 14.1-RELEASE | 176 | | Intel Core i7-7500U | Intel(R) Dual Band Wireless AC 8265 | Lenovo ThinkPad X1 Carbon Gen5 | 13.2-RELEASE | 177 | | Intel Core i7-7700K | Intel(R) Dual Band Wireless AC 3168 | Desktop HP 820 NL | 13.2-RELEASE | 178 | | Intel Core i7-7820HQ | Intel(R) Wi-Fi 6E AX210/AX1675 | Dell Precision 7720 | 13.3-RELEASE, 14.1-RELEASE | 179 | | Intel Core i7-8565U | Qualcomm Atheros QCA6174 | Dell XPS 9380 | 13-STABLE | 180 | | Intel Core i7-8650U | Intel(R) Dual Band Wireless AC 8265 | Lenovo ThinkPad T480 | 13.1-RELEASE | 181 | | Intel Core i7-8665U | Intel(R) Dual Band Wireless AC 9560 | Lenovo ThinkPad X1 Carbon | GhostBSD 24.01.1 | 182 | | Intel Core i7-10850H | Intel(R) Wi-Fi 6 AX201 | Dell Precision 7550 | 14.2-STABLE | 183 | | Intel Core i7-1185G7 | Intel(R) Wi-Fi 6 AX201 | Lenovo ThinkPad X1 Carbon Gen9 | 14.2-RELEASE | 184 | 185 | Feel free to submit a pull request or write an email to have your 186 | configuration added here! 187 | 188 | [`bhyve`]: https://wiki.freebsd.org/bhyve 189 | [Message Signaled Interrupts]: https://www.kernel.org/doc/Documentation/PCI/MSI-HOWTO.txt 190 | [`freebsd-wifibox-port`]: https://github.com/pgj/freebsd-wifibox-port 191 | [`freebsd-wifibox-alpine`]: https://github.com/pgj/freebsd-wifibox-alpine 192 | [`net/wpa_supplicant_gui`]: https://cgit.freebsd.org/ports/tree/net/wpa_supplicant_gui 193 | [`grub2-bhyve`]: https://github.com/grehan-freebsd/grub2-bhyve 194 | [`socat`]: http://www.dest-unreach.org/socat/ 195 | [article]: https://github.com/pgj/freebsd-wifibox/releases/download/freebsd-journal-2024-06/freebsd-journal-wifibox.pdf 196 | -------------------------------------------------------------------------------- /man/wifibox.8: -------------------------------------------------------------------------------- 1 | .Dd July 26, 2025 2 | .Dt WIFIBOX 8 3 | .Os 4 | .Sh NAME 5 | .Nm wifibox 6 | .Nd embedded (virtualized) wireless router 7 | .Sh SYNOPSIS 8 | .Nm 9 | .Cm start 10 | .Oo 11 | .Cm guest | Cm netif | Cm vmm 12 | .Oc 13 | .Nm 14 | .Cm stop 15 | .Oo 16 | .Cm guest | Cm netif | Cm vmm 17 | .Oc 18 | .Nm 19 | .Cm restart 20 | .Oo 21 | .Cm guest | Cm netif | Cm vmm 22 | .Oc 23 | .Nm 24 | .Cm status 25 | .Nm 26 | .Cm console 27 | .Nm 28 | .Cm version 29 | .Sh DESCRIPTION 30 | .Nm 31 | deploys a Linux guest operating system with the help of 32 | .Xr bhyve 8 33 | and attaches its driver to a wireless network device on the host system 34 | via the PCI pass-through capabilities of the 35 | .Xr vmm 4 36 | kernel module. 37 | This way the original 38 | .Fx 39 | PCI wireless network card driver can be replaced for the performance 40 | and stability of the one provided by the Linux kernel, or put into use 41 | if the device is not supported by 42 | .Fx 43 | at all. 44 | .Pp 45 | Once the guest has been started up successfully, 46 | .Nm 47 | exposes the 48 | .Sy wifibox0 49 | .Xr bridge 4 50 | networking interface, which needs to be configured further with the 51 | help of 52 | .Xr rc.conf 5 , 53 | so that the traffic could start flowing through the wireless card. 54 | .Pp 55 | There is a 56 | .Nm 57 | system service provided that can be used to start the appliance on boot 58 | and stop on shutdown. 59 | .Pp 60 | Note that 61 | .Nm 62 | is only responsible for managing and supervising the Linux guest. 63 | Due to its design, it does not have any knowledge about how the 64 | traffic is presented to the host. 65 | It might be based on Network Address Translation (NAT) or it might 66 | implement bridged networking. 67 | Please check the 68 | .Xr wifibox-guest 5 69 | manual page for more information to learn about the actual approach. 70 | .Sh CONFIGURATION 71 | After the installation, review and revisit the sample configuration 72 | files provided in the 73 | .Pa %%PREFIX%%/etc/wifibox 74 | directory and follow the instructions to create a working 75 | configuration. 76 | The directory layout and the contents of the files depend on the guest 77 | used. 78 | Make sure that the networking configuration does not conflict with 79 | that of the host in any case. 80 | .Pp 81 | By default, PCI pass-through is disabled for CPUs manufactured by AMD 82 | (dubbed AMD-Vi), hence it must be explicitly enabled via the 83 | corresponding 84 | .Xr sysctl 8 85 | variable. 86 | This can be done by adding the following line to either 87 | .Pa /etc/sysctl.conf 88 | or 89 | .Pa /boot/loader.conf 90 | depending on whether 91 | .Xr vmm 4 92 | is going to be loaded by 93 | .Nm 94 | or it is already loaded at boot. 95 | .Bd -literal -offset indent 96 | hw.vmm.amdvi.enable=1 97 | .Ed 98 | .Pp 99 | In order to make 100 | .Nm 101 | work as a system service, the following line has to be added to 102 | .Xr rc.conf 5 . 103 | .Bd -literal -offset indent 104 | wifibox_enable="YES" 105 | .Ed 106 | .Pp 107 | At the same time, make sure that no 108 | .Fx 109 | driver is configured for the same device and remove all the related 110 | settings from there. 111 | The 112 | .Xr devmatch 8 113 | utility might be used to stop any conflicting drivers from loading 114 | automatically. 115 | For example, the 116 | .Xr iwm 4 117 | and 118 | .Xr iwlwifi 4 119 | native drivers could be disabled in 120 | .Xr rc.conf 5 121 | as shown below. 122 | .Bd -literal -offset indent 123 | devmatch_enable="YES" 124 | devmatch_blocklist="if_iwm if_iwlwifi" 125 | .Ed 126 | .Pp 127 | Note these settings will only take effect on the next boot. 128 | Until then the 129 | .Nm devmatch 130 | service must be started and the drivers should be removed manually by 131 | using 132 | .Xr kldunload 8 . 133 | .Bd -literal -offset indent 134 | # service devmatch start 135 | # kldunload if_iwm if_iwlwifi 136 | .Ed 137 | .Pp 138 | The 139 | .Nm 140 | service can be then started up as follows. 141 | .Bd -literal -offset indent 142 | # service wifibox start 143 | .Ed 144 | .Pp 145 | After 146 | .Nm 147 | started successfully, IP addresses for the corresponding networking 148 | interface need to be configured in 149 | .Xr rc.conf 5 . 150 | For example, assignment of a dynamic IPv4 address can be requested by 151 | adding this line below. 152 | .Bd -literal -offset indent 153 | ifconfig_wifibox0="SYNCDHCP" 154 | .Ed 155 | .Pp 156 | In addition to this, to reduce boot times, 157 | .Xr dhclient 8 158 | can be instructed to run in the background and not to wait for a 159 | positive link and issuing an IPv4 address after it has been launched. 160 | .Bd -literal -offset indent 161 | background_dhclient_wifibox0="YES" 162 | defaultroute_delay="0" 163 | .Ed 164 | .Pp 165 | If preferred, static IPv4 address configuration is possible with this 166 | method. 167 | Assume that 168 | .Sy wifibox0 169 | is configured as 10.0.0.1/24 on the guest's side, and the host wants 170 | to use the 10.0.0.2/24 IPv4 address. 171 | .Bd -literal -offset indent 172 | defaultrouter="10.0.0.1" 173 | ifconfig_wifibox0="inet 10.0.0.2/24" 174 | .Ed 175 | .Pp 176 | The 177 | .Sy wifibox0 178 | networking interface can be brought up with the use of the 179 | .Nm netif 180 | service. 181 | .Bd -literal -offset indent 182 | # service netif start wifibox0 183 | .Ed 184 | .Pp 185 | For static IPv4 address configurations, the 186 | .Nm routing 187 | service has to be restarted as well. 188 | .Bd -literal -offset indent 189 | # service routing restart 190 | .Ed 191 | .Pp 192 | In case of IPv6, a unique local address has to be configured for the 193 | interface along with accepting ICMP Router Advertisements and an 194 | automatically generated link-local address. 195 | .Bd -literal -offset indent 196 | ifconfig_wifibox0_ipv6="inet6 fd00::1/64 accept_rtadv auto_linklocal" 197 | .Ed 198 | .Pp 199 | Note that since 200 | .Sy wifibox0 201 | becomes managed by 202 | .Nm netif 203 | this way, 204 | .Nm 205 | has to be restarted every time when the networking interfaces are 206 | recreated by 207 | .Nm netif , 208 | otherwise the link will stop working. 209 | .Bd -literal -offset indent 210 | # service netif stop 211 | # service wifibox restart 212 | # service netif start 213 | .Ed 214 | .Pp 215 | In addition to that, because a 216 | .Xr devd.conf 5 217 | file might be installed as part of the application, 218 | .Xr devd 8 219 | may have to be restarted so the additional configuration file can be 220 | read. 221 | These bits are responsible to managing the recovery in case of 222 | suspend/resume events. 223 | When this feature is not in use, or not required, for example, for 224 | .Fx 14.0 225 | or later, this step may be safely omitted. 226 | .Bd -literal -offset indent 227 | # service devd restart 228 | .Ed 229 | .Sh COMMANDS 230 | The 231 | .Nm 232 | system service and 233 | .Xr devd 8 234 | can manage the following actions by themselves, but the commands for 235 | the 236 | .Nm 237 | script itself are listed below for the reference. 238 | .Pp 239 | For the 240 | .Cm start , 241 | .Cm stop , 242 | and 243 | .Cm restart 244 | commands, it is possible to specify a target, therefore limit the 245 | scope of the operation in different ways. 246 | .Bl -tag -width "console" 247 | .It Cm guest 248 | Guest only, maintain the console device and the networking interface. 249 | .It Cm netif 250 | Networking interface and guest. 251 | That latter is required because the networking interface is bound to 252 | the virtual machine that runs the guest. 253 | .It Cm vmm 254 | The 255 | .Xr vmm 4 256 | kernel module, maintain the console device and the networking 257 | interface. 258 | .El 259 | .Pp 260 | The commands are as follows. 261 | .Bl -tag -width -indent 262 | .It Cm start Oo Cm guest | Cm netif | Cm vmm Oc 263 | Start 264 | .Nm . 265 | By default, the 266 | .Sy wifibox0 267 | interface is created and the guest is attached to the configured PCI 268 | wireless network device. 269 | The network interface of the 270 | .Fx 271 | driver on the same device must not be configured. 272 | Note that the 273 | .Cm guest 274 | target can work only if 275 | .Sy wifibox0 276 | networking interface has already been created. 277 | .It Cm stop Oo Cm guest | Cm netif | Cm vmm Oc 278 | Stop 279 | .Nm . 280 | Without the 281 | .Cm guest 282 | parameter, the 283 | .Sy wifibox0 284 | interface is destroyed and the guest is detached from the configured 285 | PCI wireless network device. 286 | After that, the 287 | .Fx 288 | driver is free to take over the device. 289 | .It Cm restart Oo Cm guest | Cm netif | Cm vmm Oc 290 | Restart 291 | .Nm , 292 | which is the sequential composition of the 293 | .Cm stop 294 | and 295 | .Cm start 296 | commands by default. 297 | The 298 | .Cm guest 299 | parameter is for the guest only. 300 | This is recommended for applying system-level updates to the guest. 301 | The 302 | .Cm netif 303 | parameter is to recreate the networking interface and restart the 304 | guest. 305 | The 306 | .Cm vmm 307 | parameter is to restart the guest while reloading the 308 | .Xr vmm 4 309 | kernel module, maintain the console device and the networking 310 | interface. 311 | This is a workaround for the guest to recover from a state where the 312 | wireless device becomes unresponsive after the ACPI resume event. 313 | .It Cm status 314 | Check and display if 315 | .Nm 316 | is still running. 317 | .It Cm console 318 | Attach to the running guest with 319 | .Xr cu 1 320 | through a virtual serial port, implemented by 321 | .Xr nmdm 4 . 322 | This is recommended for troubleshooting problems with the guest in an 323 | interactive fashion. 324 | This has to be configured specifically in order to work. 325 | The actual way of logging into the system as an administrator depends 326 | on the VM image in use. 327 | Most of the time the 328 | .Sy root 329 | user with a blank password works. 330 | See 331 | .Xr wifibox-guest 5 332 | for more information. 333 | .It Cm version 334 | Display version of 335 | .Nm 336 | and the SHA-256 hash of the guest disk image. 337 | The output is suitable for reporting errors. 338 | Note that custom images are not supported. 339 | .El 340 | .Sh EXIT STATUS 341 | The exit status is 0 on success, and >0 if any of the commands fail. 342 | .Sh DIAGNOSTICS 343 | If 344 | .Nm 345 | does not have behave in the expected way, check 346 | .Pa /var/log/wifibox.log 347 | for errors. 348 | This file holds messages about the progress of each executed command, 349 | and their amount depends on the configured level of logging. 350 | The level of logging could be configured in 351 | .Pa %%PREFIX%%/etc/wifibox/core.conf , 352 | please consult this file for the details. 353 | .Pp 354 | The log files of the guest are exported to the host and they are made 355 | available under the 356 | .Pa /var/run/wifibox/appliance/log 357 | directory. 358 | There it is recommended to check the 359 | .Pa /var/run/wifibox/appliance/log/dmesg 360 | file for messages related to the boot sequence, such as driver 361 | initialization, and the 362 | .Pa /var/run/wifibox/appliance/log/messages 363 | file for the run-time system messages, which are usually emitted 364 | by the daemons. 365 | If all else fails, use the 366 | .Cm console 367 | command to connect to the guest. 368 | In that case, please study the 369 | .Xr wifibox-guest 5 370 | manual page before proceeding. 371 | .Sh SEE ALSO 372 | .Xr cu 1 , 373 | .Xr bridge 4 , 374 | .Xr nmdm 4 , 375 | .Xr vmm 4 , 376 | .Xr devd.conf 5 , 377 | .Xr loader.conf 5 , 378 | .Xr rc.conf 5 , 379 | .Xr sysctl.conf 5 , 380 | .Xr wifibox-guest 5 , 381 | .Xr bhyve 8 , 382 | .Xr devd 8 , 383 | .Xr devmatch 8 , 384 | .Xr kldunload 8 , 385 | .Xr sysctl 8 386 | .Sh AUTHORS 387 | .An Gábor Páli Aq Mt pali.gabor@gmail.com 388 | .Sh CAVEATS 389 | .Nm 390 | supports only a single wireless network device at a time, and it has 391 | to be a PCI one. 392 | USB devices are not supported, and 393 | .Nm 394 | cannot be launched multiple times. 395 | .Pp 396 | The 397 | .Cm restart vmm 398 | command should be used with caution, because it may crash the system 399 | when it has not been in a sleep state. 400 | Hence it is best to use in combination with 401 | .Xr devd 8 . 402 | .Pp 403 | The 404 | .Cm restart vmm 405 | command will not probably work on systems where other 406 | .Xr bhyve 8 407 | guests are running in parallel as 408 | .Xr vmm 4 409 | kernel module could not be unloaded in such cases. 410 | .Pp 411 | The 412 | .Cm restart vmm 413 | command may not work properly on some systems and its repeated use can 414 | cause the PCI device to be lost completely until the next boot. 415 | As a workaround, it is worth to use the combination of 416 | .Cm stop guest 417 | (on suspend) and 418 | .Cm start guest 419 | (on resume) instead. 420 | In some other cases, it is better to unload the 421 | .Xr vmm 4 422 | kernel module to suspend with the 423 | .Cm stop vmm 424 | command, and then load it again on resume by the 425 | .Cm start vmm 426 | command. 427 | .Pp 428 | The PCI pass-through implementation of 429 | .Xr bhyve 8 430 | may not be able to cooperate with the Linux system in the guest due to 431 | lack of emulation of certain quirks and features that are required to 432 | make the driver work. 433 | Sometimes this can cause strange and unexpected error messages. 434 | Always try the latest available version of 435 | .Xr bhyve 8 436 | when this happens. 437 | -------------------------------------------------------------------------------- /sbin/wifibox: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # shellcheck disable=SC3040,SC3043 3 | set -o pipefail 4 | 5 | PREFIX=%%PREFIX%% 6 | LOCALBASE=%%LOCALBASE%% 7 | VERSION=%%VERSION%% 8 | GUEST_ROOT=%%GUEST_ROOT%% 9 | SCRIPT=${PREFIX}/sbin/wifibox 10 | LOGDIR=/var/log 11 | RUNDIR=/var/run/wifibox 12 | CONFDIR=${PREFIX}/etc/wifibox 13 | WIFIBOX_LOG=${LOGDIR}/wifibox.log 14 | UDS_CONFIG=${CONFDIR}/appliance/uds_passthru.conf 15 | 16 | : "${SYSCTL:=/sbin/sysctl}" 17 | : "${SED:=/usr/bin/sed}" 18 | 19 | KERNEL_PATH=$(${SYSCTL} -n kern.module_path | ${SED} -E 's/^([^;]*);.*/\1/') 20 | 21 | : "${BHYVE:=/usr/sbin/bhyve}" 22 | : "${BHYVECTL:=/usr/sbin/bhyvectl}" 23 | : "${VMM_KO:=${KERNEL_PATH}/vmm.ko}" 24 | : "${NMDM_KO:=${KERNEL_PATH}/nmdm.ko}" 25 | : "${DEVCTL:=/usr/sbin/devctl}" 26 | : "${DAEMON:=/usr/sbin/daemon}" 27 | : "${IFCONFIG:=/sbin/ifconfig}" 28 | : "${KLDLOAD:=/sbin/kldload}" 29 | : "${KLDUNLOAD:=/sbin/kldunload}" 30 | : "${KLDSTAT:=/sbin/kldstat}" 31 | : "${SHA256:=/sbin/sha256}" 32 | : "${NICE:=/usr/bin/nice}" 33 | 34 | : "${CAT:=/bin/cat}" 35 | : "${ECHO:=/bin/echo}" 36 | : "${EXPR:=/bin/expr}" 37 | : "${PS:=/bin/ps}" 38 | : "${PRINTF:=/usr/bin/printf}" 39 | : "${GREP:=/usr/bin/grep}" 40 | : "${TAIL:=/usr/bin/tail}" 41 | : "${HEAD:=/usr/bin/head}" 42 | : "${SLEEP:=/bin/sleep}" 43 | : "${PGREP:=/bin/pgrep}" 44 | : "${KILL:=/bin/kill}" 45 | : "${RM:=/bin/rm}" 46 | : "${CU:=/usr/bin/cu}" 47 | : "${TR:=/usr/bin/tr}" 48 | : "${NETSTAT:=/usr/bin/netstat}" 49 | : "${READLINK:=/usr/bin/readlink}" 50 | : "${LS:=/bin/ls}" 51 | : "${SEQ:=/usr/bin/seq}" 52 | 53 | : "${GRUB_BHYVE:=${LOCALBASE}/sbin/grub-bhyve}" 54 | : "${SOCAT:=${LOCALBASE}/bin/socat}" 55 | 56 | DISK_IMAGE="${GUEST_ROOT}/disk.img" 57 | 58 | NMDM_DEVICE=/dev/nmdm-wifibox 59 | NMDM_A="${NMDM_DEVICE}.1A" 60 | NMDM_B="${NMDM_DEVICE}.1B" 61 | 62 | WIFIBOX_IF="wifibox0" 63 | WIFIBOX_VM="wifibox" 64 | 65 | UDS_PASSTHRU_DAEMON_ID="wifibox-uds-passthru" 66 | VM_MANAGER_DAEMON_ID="wifibox-vm-manager" 67 | 68 | log() { 69 | local _type="$1" 70 | local _level 71 | local _message="$2" 72 | local _timestamp 73 | local _config="${CONFDIR}/core.conf" 74 | local _loglevel 75 | 76 | _timestamp="$(date +'%FT%H:%M:%S%z')" 77 | 78 | if [ ! -f "${_config}" ]; then 79 | ${ECHO} "ERROR: ${_config} is missing, please create it from the sample." 80 | exit 3 81 | fi 82 | 83 | loglevel=warn 84 | 85 | # shellcheck source=./etc/core.conf.sample 86 | . "${_config}" 87 | 88 | case ${loglevel} in 89 | [Ee][Rr][Rr][Oo][Rr]) _loglevel=1;; 90 | [Ww][Aa][Rr][Nn]) _loglevel=2;; 91 | [Ii][Nn][Ff][Oo]) _loglevel=3;; 92 | [Dd][Ee][Bb][Uu][Gg]) _loglevel=4;; 93 | *) _loglevel=0;; 94 | esac 95 | 96 | case ${_type} in 97 | error) [ ${_loglevel} -lt 1 ] && return 0;; 98 | warn) [ ${_loglevel} -lt 2 ] && return 0;; 99 | info) [ ${_loglevel} -lt 3 ] && return 0;; 100 | debug) [ ${_loglevel} -lt 4 ] && return 0;; 101 | *) return 0;; 102 | esac 103 | 104 | _level="$(${PRINTF} "%-5s" "${_type}" | ${TR} "[:lower:]" "[:upper:]")" 105 | 106 | if [ -w ${WIFIBOX_LOG} ]; then 107 | ${ECHO} "${_timestamp} ${_level} ${_message}" >> ${WIFIBOX_LOG} 108 | elif [ -z "${WARNED_ABOUT_LOG}" ]; then 109 | ${ECHO} "WARNING: ${WIFIBOX_LOG} is not writeable, messages could not be logged." 110 | WARNED_ABOUT_LOG=yes 111 | fi 112 | 113 | case ${_type} in 114 | error) ${ECHO} "ERROR: ${_message}.";; 115 | warn) ${ECHO} "WARNING: ${_message}.";; 116 | esac 117 | } 118 | 119 | output() { 120 | local _message="$1" 121 | 122 | log info "Output: ${_message}" 123 | ${ECHO} "${_message}." 124 | } 125 | 126 | capture_output() { 127 | local _type="$1" 128 | local _id="$2" 129 | 130 | while read -r message; do 131 | log "${_type}" "[${_id}] ${message}" 132 | done 133 | } 134 | 135 | check_virtfs() { 136 | local _backends 137 | 138 | _backends=$(${BHYVE} -s help) 139 | log debug "Backends reported by bhyve:" 140 | ${ECHO} "${_backends}" | capture_output debug bhyve 141 | 142 | if ! (${ECHO} "${_backends}" | ${GREP} -Fq virtio-9p); then 143 | log error "The Virtio 9p (VirtFS) bhyve interface is not available" 144 | exit 127 145 | fi 146 | } 147 | 148 | sysctl_value() { 149 | ${SYSCTL} -nq "$1" 150 | } 151 | 152 | get_kmod_path() { 153 | local _kmod="$1" 154 | 155 | ${KLDSTAT} -v -n "${_kmod}" 2> /dev/null \ 156 | | ${TAIL} +2 \ 157 | | ${HEAD} -1 \ 158 | | ${SED} -e 's![^(]*(\([^)]*\))!\1!' 159 | } 160 | 161 | assert_kmod_loaded() { 162 | local _kmod="$1" 163 | local _kmod_file 164 | local _kmod_path 165 | 166 | if [ -n "$2" ]; then 167 | _kmod_file="$2" 168 | else 169 | _kmod_file="${_kmod}" 170 | fi 171 | 172 | log debug "Check location: kmod=${_kmod}, kmod_file=${_kmod_file}" 173 | 174 | if (${KLDSTAT} -q -m "${_kmod}" | capture_output debug kldstat); then 175 | _kmod_path="$(get_kmod_path "${_kmod}")" 176 | 177 | if [ -z "${_kmod_path}" ]; then 178 | log info "${_kmod}.ko is built into the kernel, ignoring ${_kmod_file}" 179 | else 180 | log info "${_kmod}.ko is expected at path: ${_kmod_file}" 181 | log info "${_kmod}.ko is found at path: ${_kmod_path}" 182 | 183 | if [ "${_kmod_path}" != "${_kmod_file}" ] \ 184 | && ! (${KLDUNLOAD} "${_kmod}" | capture_output debug kldunload); then 185 | log error "${_kmod}.ko is loaded from a different location, but cannot be replaced" 186 | exit 127 187 | fi 188 | fi 189 | fi 190 | 191 | log debug "Assert loaded: kmod=${_kmod}, kmod_file=${_kmod_file}" 192 | 193 | if ! (${KLDSTAT} -q -m "${_kmod}" | capture_output debug kldstat); then 194 | log debug "Kernel module ${_kmod} is not loaded" 195 | 196 | if ! (${KLDLOAD} "${_kmod_file}" 2>&1 | capture_output debug kldload); then 197 | log error "${_kmod_file} kernel module could not be loaded" 198 | exit 127 199 | fi 200 | 201 | log debug "Kernel module ${_kmod} was loaded successfully" 202 | fi 203 | } 204 | 205 | assert_vmm_loaded() { 206 | assert_kmod_loaded "vmm" "${VMM_KO}" 207 | } 208 | 209 | assert_nmdm_loaded() { 210 | assert_kmod_loaded "nmdm" "${NMDM_KO}" 211 | } 212 | 213 | # shellcheck disable=SC2046 214 | check_iommu() { 215 | local _iommu 216 | local _amdvi 217 | 218 | _iommu=$(sysctl_value "hw.vmm.iommu.enable" || ${ECHO} "0") 219 | _amdvi=$(sysctl_value "hw.vmm.amdvi.enable" || ${ECHO} "0") 220 | log debug "assert hardware support present: iommu=${_iommu}, amdvi=${_amdvi}" 221 | 222 | if [ "${_iommu}" -eq "0" ] && [ "${_amdvi}" -eq "0" ]; then 223 | log error "PCI pass-through is not available for bhyve" 224 | exit 127 225 | fi 226 | } 227 | 228 | assert_vm_can_run() { 229 | assert_vmm_loaded 230 | check_iommu 231 | check_virtfs 232 | } 233 | 234 | get_image_checksum() { 235 | ${SHA256} -q "${DISK_IMAGE}" 236 | } 237 | 238 | load_bhyve_conf_values() { 239 | local _config="${CONFDIR}/bhyve.conf" 240 | 241 | if [ ! -f "${_config}" ]; then 242 | log error "${_config} is missing, please create it from the sample" 243 | exit 3 244 | fi 245 | 246 | # Internal defaults 247 | cpus=1 248 | memory=128M 249 | passthru= 250 | console=no 251 | priority=50 252 | stop_wait_max=10 253 | 254 | log info "Pulling bhyve options from configuration file" 255 | # shellcheck source=./etc/bhyve.conf.sample 256 | . "${CONFDIR}/bhyve.conf" 257 | 258 | log debug "cpus=${cpus}" 259 | log debug "memory=${memory}" 260 | log debug "passthru=[${passthru}]" 261 | log debug "console=${console}" 262 | log debug "priority=${priority}" 263 | log debug "stop_wait_max=${stop_wait_max}" 264 | 265 | _max_vmm_cpus=$(${SYSCTL} -n hw.vmm.maxcpu) 266 | 267 | log debug "bhyve max cpus=${_max_vmm_cpus}" 268 | 269 | assert_value_ranged_integer \ 270 | "bhyve.conf" "cpus" 1 "${_max_vmm_cpus}" \ 271 | "${cpus}" 272 | 273 | assert_value_wellformed \ 274 | "bhyve.conf" "memory" \ 275 | '^[[:space:]]*[0-9]+([Kk]|[Mm]|[Gg]|[Tt])?[[:space:]]*$' \ 276 | "non-negative integer, suffixed with unit: K, M, G, T" \ 277 | "${memory}" 278 | 279 | assert_value_wellformed \ 280 | "bhyve.conf" "passthru" \ 281 | '^([[:space:]]*[0-9]{1,3}/[0-9]{1,3}/[0-9]{1,3}[[:space:]]*)*$' \ 282 | "list of slot/bus/function, triples of non-negative integers" \ 283 | "${passthru}" 284 | 285 | assert_value_yesno \ 286 | "bhyve.conf" "console" \ 287 | "${console}" 288 | 289 | assert_value_ranged_integer \ 290 | "bhyve.conf" "priority" 0 99 \ 291 | "${priority}" 292 | 293 | assert_value_ranged_integer \ 294 | "bhyve.conf" "stop_wait_max" 1 60 \ 295 | "${stop_wait_max}" 296 | } 297 | 298 | has_bridge_interface() { 299 | ${IFCONFIG} | ${GREP} -Fq "${WIFIBOX_IF}: " 300 | } 301 | 302 | get_tap_interface() { 303 | if has_bridge_interface; then 304 | ${IFCONFIG} "${WIFIBOX_IF}" \ 305 | | ${GREP} -F member \ 306 | | ${SED} -E 's/^.*member:.*(tap[^ ]*).*$/\1/' 307 | else 308 | ${ECHO} "" 309 | fi 310 | } 311 | 312 | create_bridge() { 313 | local _tap 314 | 315 | if ! has_bridge_interface; then 316 | log info "Creating bridge interface: ${WIFIBOX_IF}" 317 | ${IFCONFIG} bridge create name ${WIFIBOX_IF} up 2>&1 | capture_output debug ifconfig 318 | else 319 | log warn "Bridge interface already exists: ${WIFIBOX_IF}, skipping creation" 320 | fi 321 | 322 | _tap="$(get_tap_interface)" 323 | 324 | if [ -z "${_tap}" ]; then 325 | _tap="$(${IFCONFIG} tap create up)" 326 | log info "Linking tap interface to ${WIFIBOX_IF}: ${_tap}" 327 | ${IFCONFIG} ${WIFIBOX_IF} addm "${_tap}" 2>&1 | capture_output debug ifconfig 328 | else 329 | log warn "Linked tap interface already exists: ${_tap}, skipping creation" 330 | fi 331 | } 332 | 333 | destroy_bridge() { 334 | local _tap 335 | 336 | _tap="$(get_tap_interface)" 337 | 338 | if [ -n "${_tap}" ]; then 339 | log info "Unlinking tap interface from ${WIFIBOX_IF}: ${_tap}" 340 | ${IFCONFIG} ${WIFIBOX_IF} deletem "${_tap}" 2>&1 | capture_output debug ifconfig 341 | 342 | log info "Destroying linked tap interface: ${_tap}" 343 | ${IFCONFIG} "${_tap}" destroy 2>&1 | capture_output debug ifconfig 344 | else 345 | log warn "No linked tap inteface found for ${WIFIBOX_IF}" 346 | fi 347 | 348 | log info "Destroying bridge interface: ${WIFIBOX_IF}" 349 | ${IFCONFIG} ${WIFIBOX_IF} destroy 2>&1 | capture_output debug ifconfig 350 | 351 | } 352 | 353 | find_guest_ip() { 354 | ${NETSTAT} -r \ 355 | | ${GREP} "^default.*${WIFIBOX_IF}\$" \ 356 | | ${HEAD} -1 \ 357 | | ${SED} -E "s!^default[ ]+([0-9\.]+)[ ]+.*${WIFIBOX_IF}\$!\1!" 358 | } 359 | 360 | get_uds_passthru_connections() { 361 | ${PGREP} -fx "daemon: ${UDS_PASSTHRU_DAEMON_ID}\[[0-9]*\]" 362 | } 363 | 364 | uds_passthru_start() { 365 | local network 366 | local sockets 367 | local _connections 368 | local _ip 369 | local _path 370 | local _port 371 | local _user 372 | local _group 373 | local _mode 374 | 375 | [ ! -r "${UDS_CONFIG}" ] && return 0 376 | 377 | _connections="$(get_uds_passthru_connections)" 378 | 379 | if [ -n "${_connections}" ]; then 380 | log warn "Unix Domain Sockets are already forwarded, skipping" 381 | return 1 382 | fi 383 | 384 | if [ ! -x "${SOCAT}" ]; then 385 | log warn "Socat binary could not be found as ${SOCAT}, dropping UDS pass-through" 386 | return 1 387 | fi 388 | 389 | log info "Bringing up Unix Domain Socket pass-through" 390 | 391 | # shellcheck disable=SC1090 392 | . "${UDS_CONFIG}" 393 | 394 | if [ -z "${network}" ]; then 395 | _ip=$(find_guest_ip) 396 | else 397 | _ip=${network%%:*} 398 | fi 399 | 400 | log info "Found guest IP address: ${_ip}" 401 | 402 | if [ -z "${_ip}" ]; then 403 | log warn "No guest IP address could be discovered, dropping UDS pass-through" 404 | return 1 405 | fi 406 | 407 | log info "Configured sockets: [${sockets}]" 408 | 409 | for s in ${sockets}; do 410 | _path="${s##*path=}"; _path="${_path%%,*}" 411 | _port="${s##*port=}"; _port="${_port%%,*}" 412 | _user="${s##*user=}"; _user="${_user%%,*}" 413 | _group="${s##*group=}"; _group="${_group%%,*}" 414 | _mode="${s##*mode=}"; _mode="${_mode%%,*}" 415 | 416 | if [ -z "${_port}" ]; then 417 | log warn "No port defined for ${_path}, dropping UDS pass-through" 418 | continue 419 | fi 420 | 421 | log info "Hooking up ${_ip}:${_port} as ${_path} (${_user}:${_group}@${_mode})" 422 | ${DAEMON} -r -t "${UDS_PASSTHRU_DAEMON_ID}" \ 423 | "${SOCAT}" \ 424 | UNIX-RECVFROM:"${_path}",reuseaddr,fork,unlink-early,user="${_user}",group="${_group}",mode="${_mode}" \ 425 | TCP4:"${_ip}":"${_port}" 2>&1 \ 426 | | capture_output debug socat & 427 | done 428 | } 429 | 430 | uds_passthru_stop() { 431 | local _connections 432 | 433 | _connections="$(get_uds_passthru_connections)" 434 | [ -z "${_connections}" ] && return 0 435 | 436 | log info "Tearing down Unix Domain Socket pass-through" 437 | log info "Daemonized socat processes found: [${_connections}]" 438 | 439 | # shellcheck disable=SC2086 440 | ${KILL} -TERM ${_connections} 441 | } 442 | 443 | get_ppt_devices() { 444 | load_bhyve_conf_values 445 | 446 | if [ -z "${passthru}" ]; then 447 | ${ECHO} "" 448 | else 449 | ${ECHO} "${passthru}" \ 450 | | ${SED} -E 's!([0-9]*)/([0-9]*)/([0-9]*)!pci\1:\2:\3!g' 451 | fi 452 | } 453 | 454 | get_vm_manager_pid() { 455 | ${PGREP} -fx "daemon: ${VM_MANAGER_DAEMON_ID}\[[0-9]*\]" 456 | } 457 | 458 | get_vm_pid() { 459 | ${PGREP} -fx "bhyve: ${WIFIBOX_VM}" 460 | } 461 | 462 | destroy_vm() { 463 | log info "Destroying guest ${WIFIBOX_VM}" 464 | 465 | ${BHYVECTL} --destroy --vm=${WIFIBOX_VM} 2>&1 | capture_output info bhyvectl 466 | ${SLEEP} 0.5 2>&1 | capture_output debug sleep 467 | 468 | _ppts="$(get_ppt_devices)" 469 | 470 | log info "Destroying bhyve PPT devices: [${_ppts}]" 471 | 472 | if [ -n "${_ppts}" ]; then 473 | for ppt in ${_ppts}; do 474 | if ! (${DEVCTL} clear driver -f "${ppt}" 2>&1 | capture_output debug devctl); then 475 | log warn "PPT device ${ppt} could not be destroyed" 476 | else 477 | log info "${ppt}: destroyed" 478 | fi 479 | done 480 | else 481 | log warn "No bhyve PPT device could be found" 482 | fi 483 | } 484 | 485 | vm_start() { 486 | local _pid 487 | local _start_wait_max=5 488 | 489 | _pid="$(get_vm_pid)" 490 | 491 | if [ -n "${_pid}" ]; then 492 | log warn "Guest is already run as PID ${_pid}, left intact" 493 | return 1 494 | fi 495 | 496 | ${DAEMON} -r -t "${VM_MANAGER_DAEMON_ID}" \ 497 | "${0}" _manage_vm 498 | 499 | for i in $(${SEQ} 0 ${_start_wait_max}); do 500 | _pid=$(get_vm_pid) 501 | 502 | log info "Waiting for bhyve to start up: [${i}/${_start_wait_max}]: [${_pid}]" 503 | 504 | if [ -n "${_pid}" ]; then 505 | log info "Guest ${WIFIBOX_VM} has started as PID ${_pid}" 506 | break 507 | fi 508 | 509 | ${SLEEP} 1 2>&1 | capture_output debug sleep 510 | done 511 | 512 | if [ -z "${_pid}" ]; then 513 | log warn "Guest is not up after ${_start_wait_max} seconds" 514 | fi 515 | } 516 | 517 | assert_daemonized() { 518 | local _parent 519 | 520 | _parent=$(${PS} -o comm $PPID | ${TAIL} +2) 521 | log debug "assert daemonized: parent=${_parent}" 522 | 523 | if [ "${_parent}" != "daemon" ]; then 524 | log error "This portion of the program could only be run daemonized" 525 | exit 127 526 | fi 527 | } 528 | 529 | quit_daemonization() { 530 | log info "VM manager: quit daemonization" 531 | ${KILL} -TERM $PPID 532 | } 533 | 534 | assert_value_wellformed() { 535 | local _location="$1" 536 | local _name="$2" 537 | local _syntax="$3" 538 | local _expected="$4" 539 | local _value="$5" 540 | 541 | if ! (${ECHO} "${_value}" | ${GREP} -Eq "${_syntax}"); then 542 | log error "${_location}: malformed ${_name} value: \"${_value}\", expected: ${_expected}" 543 | quit_daemonization 544 | exit 3 545 | fi 546 | } 547 | 548 | assert_value_ranged_integer() { 549 | local _location="$1" 550 | local _name="$2" 551 | local _lower="$3" 552 | local _upper="$4" 553 | local _value="$5" 554 | local _syntax='^[[:space:]]*[0-9]+[[:space:]]*$' 555 | 556 | assert_value_wellformed \ 557 | "${_location}" "${_name}" \ 558 | "${_syntax}" "non-negative integer" \ 559 | "${_value}" 560 | 561 | if [ "${_value}" -lt "${_lower}" ] || [ "${_value}" -gt "${_upper}" ]; then 562 | log error "${_location}: ${_name} should be in between ${_lower} and ${_upper}: ${_value}" 563 | quit_daemonization 564 | exit 3 565 | fi 566 | } 567 | 568 | assert_value_yesno() { 569 | local _location="$1" 570 | local _name="$2" 571 | local _value="$3" 572 | local _syntax='^[[:space:]]*(yes|no)[[:space:]]*$' 573 | 574 | assert_value_wellformed \ 575 | "${_location}" "${_name}" \ 576 | "${_syntax}" "yes or no" \ 577 | "${_value}" 578 | } 579 | 580 | # shellcheck disable=SC2086 581 | vm_manager() { 582 | local _max_vmm_cpus 583 | local _nmdm_grub_bhyve 584 | local _nmdm_bhyve 585 | local _passthru_bhyve 586 | local _tap_bhyve 587 | local _ppt 588 | local _tap 589 | local _app_conf 590 | local _app_conf_ptr="${CONFDIR}/app_config" 591 | local _app_conf_mode 592 | local _app_conf_bhyve 593 | local _grub_bhyve_args 594 | local _grub_bhyve_exit_code 595 | local _grub_device_map="${GUEST_ROOT}/device.map" 596 | local _grub_cfg="${GUEST_ROOT}/grub.cfg" 597 | local _nice_priority 598 | local _bhyve_args 599 | local _bhyve_devs 600 | local _slot 601 | local _bhyve_exit_code 602 | local _restart 603 | 604 | assert_daemonized 605 | 606 | log info "VM manager launched" 607 | log info "Gathering necessary configuration files for launching the guest" 608 | 609 | load_bhyve_conf_values 610 | _nice_priority=$(${EXPR} \( ${priority} \* 40 / 99 \) - 20) 611 | 612 | if [ "${console}" = "yes" ]; then 613 | assert_nmdm_loaded 614 | _nmdm_grub_bhyve="-c ${NMDM_A}" 615 | _nmdm_bhyve="-l com1,${NMDM_A}" 616 | log info "Guest console is configured to use" 617 | else 618 | log info "Guest console is not configured to use" 619 | fi 620 | 621 | if [ -n "${passthru}" ]; then 622 | _passthru_bhyve="" 623 | _slot=0 624 | 625 | for sbf in ${passthru}; do 626 | _passthru_bhyve="${_passthru_bhyve} -s 6:${_slot},passthru,${sbf}" 627 | _slot=$(${EXPR} ${_slot} + 1) 628 | done 629 | 630 | log info "Passthru devices configured: [${passthru}]" 631 | else 632 | log warn "No passthru device is configured" 633 | fi 634 | 635 | _ppts="$(get_ppt_devices)" 636 | if [ -n "${_ppts}" ]; then 637 | for ppt in ${_ppts}; do 638 | ${DEVCTL} set driver -f "${ppt}" ppt 2>&1 | capture_output debug devctl 639 | log info "PPT driver is configured for ${ppt} device" 640 | done 641 | else 642 | log warn "No PPT driver is attached due to lack of device" 643 | fi 644 | 645 | _tap="$(get_tap_interface)" 646 | if [ -n "${_tap}" ]; then 647 | _tap_bhyve="-s 5:0,e1000,${_tap}" 648 | ${IFCONFIG} "${_tap}" up 2>&1 | capture_output debug ifconfig 649 | log info "tap interface is configured: ${_tap}" 650 | else 651 | log error "No tap interface is available, cannot proceed" 652 | quit_daemonization 653 | exit 5 654 | fi 655 | 656 | log info "Launching guest ${WIFIBOX_VM} from ${GUEST_ROOT} with grub-bhyve" 657 | 658 | if [ ! -f "${_grub_cfg}" ]; then 659 | log error "${_grub_cfg} could not be found, guest cannot be started" 660 | quit_daemonization 661 | exit 4 662 | fi 663 | 664 | if [ ! -f "${DISK_IMAGE}" ]; then 665 | log error "${DISK_IMAGE} could not be found, guest cannot be started" 666 | quit_daemonization 667 | exit 4 668 | fi 669 | 670 | _bhyve_devs="virtio-blk,${DISK_IMAGE}" 671 | _bhyve_devs="${_bhyve_devs} virtio-9p,config=${CONFDIR}/appliance,ro" 672 | _bhyve_devs="${_bhyve_devs} virtio-9p,var=${RUNDIR}/appliance" 673 | _app_conf=$(${READLINK} -f "%Y" ${_app_conf_ptr}) 674 | 675 | if [ -n "${_app_conf}" ]; then 676 | log info "Application config is found at ${_app_conf}" 677 | 678 | if [ -r "${UDS_CONFIG}" ]; then 679 | _app_conf_mode="" 680 | log info "Application config will be mounted writeable" 681 | else 682 | _app_conf_mode=",ro" 683 | log info "Application config will be mounted read-only" 684 | fi 685 | 686 | _bhyve_devs="${_bhyve_devs} virtio-9p,app_config=${_app_conf}${_app_conf_mode}" 687 | else 688 | log info "No application config found, nothing to mount" 689 | fi 690 | 691 | log debug "Devices: ${_bhyve_devs}" 692 | 693 | _grub_bhyve_args="" 694 | _grub_bhyve_args="${_grub_bhyve_args} -S -M ${memory}" 695 | _grub_bhyve_args="${_grub_bhyve_args} -r host ${_nmdm_grub_bhyve}" 696 | [ -f "${_grub_device_map}" ] \ 697 | && _grub_bhyve_args="${_grub_bhyve_args} -m ${_grub_device_map}" 698 | _grub_bhyve_args="${_grub_bhyve_args} -d ${GUEST_ROOT} ${WIFIBOX_VM}" 699 | 700 | log debug "Arguments: ${_grub_bhyve_args}" 701 | ${GRUB_BHYVE} ${_grub_bhyve_args} 2>&1 | capture_output debug grub-bhyve 702 | _grub_bhyve_exit_code="$?" 703 | 704 | if [ "${_grub_bhyve_exit_code}" -ne "0" ]; then 705 | destroy_vm 706 | 707 | log debug "exit_code=${_grub_bhyve_exit_code}" 708 | log info "grub-bhyve failed to start, signaling restart after 5 seconds" 709 | 710 | ${SLEEP} 5 2>&1 | capture_output debug sleep 711 | exit 1 712 | fi 713 | 714 | log info "Launching guest ${WIFIBOX_VM} from ${GUEST_ROOT} with bhyve" 715 | 716 | _bhyve_args="" 717 | _bhyve_args="${_bhyve_args} -c ${cpus}" 718 | _bhyve_args="${_bhyve_args} -m ${memory} -AHP -u -S" 719 | _bhyve_args="${_bhyve_args} ${_nmdm_bhyve}" 720 | _bhyve_args="${_bhyve_args} -s 0,hostbridge" 721 | _bhyve_args="${_bhyve_args} -s 31,lpc" 722 | _slot=0 723 | 724 | for dev in ${_bhyve_devs}; do 725 | _bhyve_args="${_bhyve_args} -s 4:${_slot},${dev}" 726 | _slot=$(${EXPR} ${_slot} + 1) 727 | done 728 | 729 | _bhyve_args="${_bhyve_args} ${_tap_bhyve}" 730 | _bhyve_args="${_bhyve_args} ${_passthru_bhyve}" 731 | _bhyve_args="${_bhyve_args} ${WIFIBOX_VM}" 732 | 733 | log debug "Nice priority: ${_nice_priority}" 734 | log debug "Arguments: ${_bhyve_args}" 735 | 736 | ${NICE} -n ${_nice_priority} \ 737 | ${BHYVE} ${_bhyve_args} 2>&1 | capture_output debug bhyve 738 | 739 | _bhyve_exit_code="$?" 740 | 741 | destroy_vm 742 | 743 | case "${_bhyve_exit_code}" in 744 | 0) log info "VM manager: guest was rebooted, signaling restart" 745 | _restart=yes;; 746 | 1) log info "VM manager: guest was powered off, signaling exit";; 747 | 2) log info "VM manager: guest was halted, signaling exit";; 748 | *) log info "VM manager: guest crashed, signaling restart after 5 seconds" 749 | log debug "exit_code=${_bhyve_exit_code}" 750 | ${SLEEP} 5 2>&1 | capture_output debug sleep 751 | _restart=yes;; 752 | esac 753 | 754 | [ -n "${_restart}" ] && exit 1 755 | 756 | quit_daemonization 757 | } 758 | 759 | vm_stop() { 760 | local _ppt 761 | local _pid 762 | local _manager_pid 763 | 764 | _pid="$(get_vm_pid)" 765 | 766 | log info "Stopping guest ${WIFIBOX_VM} run as PID [${_pid}]" 767 | 768 | if [ -z "${_pid}" ]; then 769 | log warn "Guest is not running, hence not stopped" 770 | return 1 771 | fi 772 | 773 | load_bhyve_conf_values 774 | 775 | if ! (${KILL} -TERM "${_pid}" 2>&1 | capture_output debug kill); then 776 | log warn "Guest could not be stopped gracefully" 777 | else 778 | for i in $(${SEQ} 1 ${stop_wait_max}); do 779 | _pid=$(get_vm_pid) 780 | 781 | log info "Check if the guest is still running [${i}/${stop_wait_max}]: [${_pid}]" 782 | 783 | if [ -z "${_pid}" ]; then 784 | log info "Guest has stopped. All good!" 785 | break 786 | fi 787 | 788 | ${SLEEP} 1 2>&1 | capture_output debug sleep 789 | done 790 | fi 791 | 792 | if [ -n "${_pid}" ]; then 793 | log info "Grace period is over, forcing shutdown of guest ${WIFIBOX_VM}" 794 | ${BHYVECTL} --force-poweroff --vm=${WIFIBOX_VM} 2>&1 | capture_output debug bhyvectl 795 | fi 796 | 797 | while true; do 798 | _manager_pid=$(get_vm_manager_pid) 799 | log info "Waiting for the manager to clean up: [${_manager_pid}]" 800 | 801 | if [ -z "${_manager_pid}" ]; then 802 | log info "The manager has finished. Perfect!" 803 | break 804 | fi 805 | 806 | ${SLEEP} 1 2>&1 | capture_output debug sleep 807 | done 808 | } 809 | 810 | show_progress() { 811 | ${PRINTF} "." 812 | } 813 | 814 | has_flag() { 815 | local _flags="$1" 816 | local _flag="$2" 817 | 818 | ${ECHO} "${_flags}" | ${GREP} -Fq "${_flag}" 819 | } 820 | 821 | wifibox_start() { 822 | local _target="$1" 823 | local _start 824 | 825 | log info "Begin: wifibox start" 826 | 827 | if [ -n "$2" ]; then 828 | log error "Too many parameters" 829 | exit 1 830 | fi 831 | 832 | case ${_target} in 833 | guest) _start="G";; 834 | ""|netif) _start="GN";; 835 | vmm) _start="GV";; 836 | *) log error "Unknown target: ${_target} (supported: guest, netif, vmm)" 837 | exit 1;; 838 | esac 839 | 840 | log debug "start=${_start}" 841 | ${PRINTF} "Starting wifibox..." 842 | 843 | if has_flag "${_start}" "V"; then 844 | load_vmm 845 | show_progress 846 | fi 847 | 848 | if has_flag "${_start}" "N"; then 849 | create_bridge 850 | show_progress 851 | fi 852 | 853 | if has_flag "${_start}" "G"; then 854 | assert_vm_can_run 855 | 856 | vm_start 857 | show_progress 858 | 859 | uds_passthru_start 860 | show_progress 861 | fi 862 | 863 | ${ECHO} "OK" 864 | log info "End: wifibox start" 865 | } 866 | 867 | wifibox_stop() { 868 | local _target="$1" 869 | local _stop 870 | 871 | log info "Begin: wifibox stop" 872 | 873 | if [ -n "$2" ]; then 874 | log error "Too many parameters" 875 | exit 1 876 | fi 877 | 878 | case ${_target} in 879 | guest) _stop="G";; 880 | ""|netif) _stop="GN";; 881 | vmm) _stop="GV";; 882 | *) log error "Unknown target: ${_target} (supported: guest, netif, vmm)" 883 | exit 1;; 884 | esac 885 | 886 | log debug "stop=${_stop}" 887 | ${PRINTF} "Stopping wifibox..." 888 | 889 | if has_flag "${_stop}" "G"; then 890 | uds_passthru_stop 891 | show_progress 892 | 893 | vm_stop 894 | show_progress 895 | fi 896 | 897 | if has_flag "${_stop}" "N"; then 898 | destroy_bridge 899 | show_progress 900 | fi 901 | 902 | if has_flag "${_stop}" "V"; then 903 | unload_vmm 904 | show_progress 905 | fi 906 | 907 | ${ECHO} "OK" 908 | log info "End: wifibox stop" 909 | } 910 | 911 | # This is a workaround to recover from the unfortunate state of the 912 | # wireless device after resume. 913 | load_vmm() { 914 | log info "Reloading vmm.ko" 915 | ${KLDLOAD} "${VMM_KO}" 2>&1 | capture_output debug kldload 916 | } 917 | 918 | unload_vmm() { 919 | log info "Unloading vmm.ko" 920 | 921 | if ! (${KLDUNLOAD} vmm 2>&1 | capture_output debug kldunload); then 922 | log error "Some other bhyve guests might be running, vmm.ko could not be unloaded" 923 | exit 127 924 | fi 925 | } 926 | 927 | reload_vmm() { 928 | unload_vmm 929 | load_vmm 930 | } 931 | 932 | wifibox_restart() { 933 | local _target="$1" 934 | local _restart 935 | local _pid 936 | 937 | log info "Begin: wifibox restart" 938 | 939 | if [ -n "$2" ]; then 940 | log error "Too many parameters" 941 | exit 1 942 | fi 943 | 944 | _pid="$(get_vm_pid)" 945 | 946 | if [ -z "${_pid}" ]; then 947 | log warn "No running instance found that could be restarted" 948 | return 1 949 | fi 950 | 951 | case ${_target} in 952 | guest) _restart="G";; 953 | ""|netif) _restart="GN";; 954 | vmm) _restart="GV";; 955 | *) log error "Unknown target: ${_target} (supported: guest, netif, vmm)" 956 | exit 1;; 957 | esac 958 | 959 | log debug "restart=${_restart}" 960 | ${PRINTF} "Restarting wifibox..." 961 | 962 | if has_flag "${_restart}" "G"; then 963 | uds_passthru_stop 964 | show_progress 965 | vm_stop 966 | show_progress 967 | fi 968 | 969 | if has_flag "${_restart}" "N"; then 970 | destroy_bridge 971 | show_progress 972 | fi 973 | 974 | if has_flag "${_restart}" "V"; then 975 | reload_vmm 976 | show_progress 977 | fi 978 | 979 | if has_flag "${_restart}" "N"; then 980 | create_bridge 981 | show_progress 982 | fi 983 | 984 | if has_flag "${_restart}" "G"; then 985 | vm_start 986 | show_progress 987 | uds_passthru_start 988 | show_progress 989 | fi 990 | 991 | ${ECHO} "OK" 992 | log info "End: wifibox restart" 993 | } 994 | 995 | wifibox_status() { 996 | local _pid 997 | 998 | log info "Begin: wifibox status" 999 | 1000 | _pid="$(get_vm_pid)" 1001 | 1002 | if [ -n "${_pid}" ]; then 1003 | output "wifibox is run as PID ${_pid}" 1004 | else 1005 | output "wifibox is not run" 1006 | return 1 1007 | fi 1008 | 1009 | log info "End: wifibox status" 1010 | } 1011 | 1012 | wifibox_console() { 1013 | local _pid 1014 | 1015 | log info "Begin: wifibox console" 1016 | _pid="$(get_vm_pid)" 1017 | log debug "Guest is run as ${_pid}" 1018 | 1019 | if [ -z "${_pid}" ]; then 1020 | log error "There is no guest to attach to" 1021 | exit 127 1022 | fi 1023 | 1024 | if ! ${LS} "${NMDM_DEVICE}"* > /dev/null 2>&1; then 1025 | log error "No null-modem device is configured" 1026 | exit 127 1027 | fi 1028 | 1029 | ${ECHO} 'Connecting, type "~." to leave the session...' 1030 | log info "Attaching to the guest" 1031 | ${CU} -s 115200 -l ${NMDM_B} 1032 | log info "Detached from the guest" 1033 | ${ECHO} "Finished." 1034 | log info "End: wifibox console" 1035 | } 1036 | 1037 | wifibox_version() { 1038 | local _checksum 1039 | 1040 | log info "Begin: wifibox version" 1041 | _checksum="$(get_image_checksum)" 1042 | 1043 | log debug "version=${VERSION}, checksum=${_checksum}" 1044 | ${ECHO} "wifibox version ${VERSION}" 1045 | ${ECHO} "Disk image checksum: ${_checksum}" 1046 | log info "End: wifibox version" 1047 | } 1048 | 1049 | wifibox_usage() { 1050 | ${CAT} <