5GHz Access Point with Intel WiFi

I have a thin client that I wanted to turn into a WiFi access point. “Consumer APs are basically just really slow computers. Then, shouldn’t a moderately slow computer be a really fast AP?” In theory, it checked out. Just slap OpenWrt onto it or something of that ilk, and Bob’s your uncle.

My thin client has built-in WiFi, which simplifies things somewhat because there aren’t any PCIe expansion slots to spare after shoving a second ethernet card into it. The wireless adapter in question is an Intel Wireless-AC 9560 in M.2 form factor, boasting 2x2 802.11ac support – enabling high-throughput transfers over the 5GHz band. On Linux, this adapter is controlled by the iwlwifi driver.

I proceeded to download the necessary drivers and firmware on my OpenWrt instance, which was simple enough. Then, I created a 5GHz access point through LuCI, pulled out my phone, and scanned for networks. However, no matter how long I waited, nothing appeared. I couldn’t possibly know at that moment how much time I’d waste trying to get this to work.

Looking at the logs, it seems to be failing in the DFS CAC stage.

Thu Jul 21 21:53:49 2022 daemon.notice hostapd: Remove interface 'wlan0'
Thu Jul 21 21:53:49 2022 daemon.notice hostapd: wlanO: interface state DISABLED->DISABLED
Thu Jul 21 21:53:49 2022 daemon.notice hostapd: wlanO: AP-DISABLED
Thu Jul 21 21:53:49 2022 daemon.notice hostapd: wlan0: CTRL-EVENT-TERMINATING
Thu Jul 21 21:53:49 2022 daemon.err hostapd: hostapd_free_hapd_data: Interface wlan0 wasn't started
Thu Jul 21 21:53:49 2022 daemon.notice hostapd: n180211: deinit ifname=wlan0 disabled_11b_rates=0
Thu Jul 21 21:53:49 2022 daemon.notice netifd: Wireless device 'radio0' is now down
Thu Jul 21 21:53:49 2022 daemon.notice hostapd: Configuration file: /var/run/hostapd-phy0.conf (phy wlan0) —> new PHY
Thu Jul 21 21:53:49 2022 daemon.notice hostapd: wlanO: interface state UNINITIALIZED->COUNTRY_UPDATE
Thu Jul 21 21:53:49 2022 daemon.notice hostapd: wlanO: interface state COUNTRY_UPDATE->HT_SCAN
Thu Jul 21 21:53:50 2022 daemon.notice hostapd: wlan0: interface state HT_SCAN->DFS
Thu Jul 21 21:53:50 2022 daemon.notice hostapd: wlanO: DFS-CAC-START freq=5260 chan=52 sec_chan=1, width=0, seg0=54, seg1=0, cac_time=60s
Thu Jul 21 21:53:50 2022 daemon.err hostapd: DFS start_dfs_cac() failed, -1
Thu Jul 21 21:53:50 2022 daemon.err hostapd: Interface initialization failed
Thu Jul 21 21:53:50 2022 daemon.notice hostapd: wlanO: interface state DFS-DISABLED
Thu Jul 21 21:53:50 2022 daemon.notice hostapd: wlanO: AP-DISABLED

DFS is a technology that allows 5GHz WiFi to coexist with certain radar technologies which share the same radio spectra. It works by performing a Channel Availability Check (CAC) before transmitting on any DFS channel. This check involves listening on the channel for radar activity. Whether or not a channel is DFS or not depends on your country’s local ordinance, formally referred to as regulatory domain (or regdom for short). It seems that for whatever reason, DFS CAC doesn’t work with this combination of wireless adapter and driver. I wouldn’t be surprised if it’s not even implemented, because CAC is only used in AP mode. This network adapter belongs in laptops, which never enter AP mode in normal operation.

Yeah, so DFS seems to be broken. And the channel is marked DFS because of the regdom. Where did the regdom come from?

root@OpenWrt:~# iw reg get
global
country 00: DFS-UNSET
        (2402 - 2472 @ 40), (N/A, 20), (N/A)
        (2457 - 2482 @ 20), (N/A, 20), (N/A), AUTO-BW, PASSIVE-SCAN
        (2474 - 2494 @ 20), (N/A, 20), (N/A), NO-OFDM, PASSIVE-SCAN
        (5170 - 5250 @ 80), (N/A, 20), (N/A), AUTO-BW
        (5250 - 5330 @ 80), (N/A, 20), (0 ms), DFS, AUTO-BW, PASSIVE-SCAN
        (5490 - 5730 @ 160), (N/A, 20), (0 ms), DFS, PASSIVE-SCAN
        (5735 - 5835 @ 80), (N/A, 20), (N/A), PASSIVE-SCAN
        (57240 - 63720 @ 2160), (N/A, 0), (N/A)
...

The default regdom is “world”, which is an overly restrictive regdom that should fly no matter what country you’re in. Let’s set it to somewhere that has some 5GHz channels that aren’t DFS, like India.

root@OpenWrt:~# iw reg get
global
country IN: DFS-UNSET
        (2402 - 2472 @ 40), (N/A, 30), (N/A)
        (5150 - 5250 @ 80), (N/A, 30), (N/A)
        (5250 - 5350 @ 80), (N/A, 24), (N/A)
        (5470 - 5725 @ 160), (N/A, 24), (N/A)
        (5725 - 5875 @ 80), (N/A, 30), (N/A)

phy#0 (self-managed)
country 00: DFS-UNSET
        (2402 - 2437 @ 40), (6, 22), (N/A), AUTO-BW, NO-HT40MINUS, NO-80MHZ, NO-160MHZ
        (2422 - 2462 @ 40), (6, 22), (N/A), AUTO-BW, NO-80MHZ, NO-160MHZ
        (2447 - 2482 @ 40), (6, 22), (N/A), AUTO-BW, NO-HT40PLUS, NO-80MHZ, NO-160MHZ
        (5170 - 5190 @ 160), (6, 22), (N/A), NO-OUTDOOR, AUTO-BW, IR-CONCURRENT, NO-HT40MINUS, PASSIVE-SCAN
        (5190 - 5210 @ 160), (6, 22), (N/A), NO-OUTDOOR, AUTO-BW, IR-CONCURRENT, NO-HT40PLUS, PASSIVE-SCAN
        (5210 - 5230 @ 160), (6, 22), (N/A), NO-OUTDOOR, AUTO-BW, IR-CONCURRENT, NO-HT40MINUS, PASSIVE-SCAN
        (5230 - 5250 @ 160), (6, 22), (N/A), NO-OUTDOOR, AUTO-BW, IR-CONCURRENT, NO-HT40PLUS, PASSIVE-SCAN
        (5250 - 5270 @ 160), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, PASSIVE-SCAN
        (5270 - 5290 @ 160), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, PASSIVE-SCAN
        (5290 - 5310 @ 160), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, PASSIVE-SCAN
        (5310 - 5330 @ 160), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, PASSIVE-SCAN
        (5490 - 5510 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, PASSIVE-SCAN
        (5510 - 5530 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, PASSIVE-SCAN
        (5530 - 5550 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, PASSIVE-SCAN
        (5550 - 5570 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, PASSIVE-SCAN
        (5570 - 5590 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, PASSIVE-SCAN
        (5590 - 5610 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, PASSIVE-SCAN
        (5610 - 5630 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, PASSIVE-SCAN
        (5630 - 5650 @ 240), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, PASSIVE-SCAN
        (5650 - 5670 @ 80), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, NO-160MHZ, PASSIVE-SCAN
        (5670 - 5690 @ 80), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, NO-160MHZ, PASSIVE-SCAN
        (5690 - 5710 @ 80), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40MINUS, NO-160MHZ, PASSIVE-SCAN
        (5710 - 5730 @ 80), (6, 22), (0 ms), DFS, AUTO-BW, NO-HT40PLUS, NO-160MHZ, PASSIVE-SCAN
        (5735 - 5755 @ 80), (6, 22), (N/A), AUTO-BW, IR-CONCURRENT, NO-HT40MINUS, NO-160MHZ, PASSIVE-SCAN
        (5755 - 5775 @ 80), (6, 22), (N/A), AUTO-BW, IR-CONCURRENT, NO-HT40PLUS, NO-160MHZ, PASSIVE-SCAN
        (5775 - 5795 @ 80), (6, 22), (N/A), AUTO-BW, IR-CONCURRENT, NO-HT40MINUS, NO-160MHZ, PASSIVE-SCAN
        (5795 - 5815 @ 80), (6, 22), (N/A), AUTO-BW, IR-CONCURRENT, NO-HT40PLUS, NO-160MHZ, PASSIVE-SCAN
        (5815 - 5835 @ 20), (6, 22), (N/A), AUTO-BW, IR-CONCURRENT, NO-HT40MINUS, NO-HT40PLUS, NO-80MHZ, NO-160MHZ, PASSIVE-SCAN

That didn’t change anything. The adapter is ignoring our regdom. To be more precise, it’s applying the global regdom as a mask, which can only further restrict the adapter’s self-managed regdom. Not that you could further restrict it much, since it’s already super restrictive. Every single 5GHz channel is marked DFS. Even worse, they are also marked PASSIVE-SCAN, also known as “no IR” (initiating radiation). This is a no-go for AP mode, since PASSIVE-SCAN disallows transmission unless the adapter is associated with an AP.

Now, where does the adapter’s self-managed regdom come from? Turns out it comes from a feature called Location Aware Regulatory (LAR). LAR is a feature of these Intel WiFi adapters which allows it to infer its regulatory domain from its environment. If LAR works reasonably well, then it makes sense to infer regdom rather than let it be user configurable in order to ensure compliance with local laws. There used to be a kernel module parameter for iwlwifi called lar_disable which allowed disabling LAR. However, it has been removed since Linux 5.4 as it wasn’t meant to be user-facing. Today, there is no way to disable LAR.

To summarize, we can’t create an AP on 5GHz because all 5GHz channels are DFS channels (and also because they are all PASSIVE-SCAN). DFS is broken with this adapter. They are DFS channels because of the regdom. We can’t change the regdom because it is inferred with LAR. We can’t disable LAR, because the config parameter was removed. We can’t have nice things.

At the end of this wild goose chase, I found this Reddit post. If it wasn’t for this post, I would be halfway down this list already.