Field Notes · Home Lab · Last verified: Kernel 6.19

RTL8812AU Driver Fix
for Linux Kernel 6.x

The in-kernel rtw88_8812au driver doesn't support monitor mode. This guide patches the out-of-tree 88XXau driver to build on kernel 6.x and installs it via DKMS.

Adapter Alfa AWUS036ACH (RTL8812AU)
OS Kali Linux (kernel 6.x)
Patches 3 required
Kali Linux RTL8812AU Kernel 6.x Monitor Mode DKMS

Background

Kali ships with the RTL8812AU chipset supported by two drivers:

Driver Normal WiFi Monitor mode / injection After this guide
rtw88_8812au ✓ Works ✗ Limited — airodump-ng fails Blacklisted
88XXau (out-of-tree) ✓ Works ✓ Full support Active (DKMS)

The out-of-tree 88XXau module requires three patches to compile on kernel 6.x — each fixes a different API that was removed or changed in a different kernel version.

Prerequisites

bash
sudo apt install dkms linux-headers-$(uname -r) build-essential

The DKMS source package should already be present on Kali. Verify:

bash
ls /usr/src/realtek-rtl88xxau-*/

If the directory is missing: sudo apt install realtek-rtl88xxau-dkms

All patches target files inside /usr/src/realtek-rtl88xxau-<version>/. Replace <BASE> with that path throughout this guide.

The Patches

Patch 1 — Makefile: EXTRA_CFLAGS → ccflags-y

Why: EXTRA_CFLAGS was removed from the kernel build system in kernel 5.18. On 6.x it is silently ignored — none of the -I include paths or -D defines reach the compiler. The fix adds one line inside the ifneq ($(KERNELRELEASE),) block that forwards everything into ccflags-y, which the kernel build system does respect.

bash — run as root
sudo python3 -c "
path = '<BASE>/Makefile'
lines = open(path).readlines()
for i, l in enumerate(lines):
    if 'ifneq (\$(KERNELRELEASE),)' in l:
        lines.insert(i+1, 'ccflags-y += \$(EXTRA_CFLAGS)\n')
        print('Inserted at line', i+2)
        break
open(path, 'w').writelines(lines)
"

Patch 2 — osdep_service_linux.h: Timer API compat shims

Why: Two timer functions were removed in kernel 6.x: from_timer() was removed in 6.16 (replaced by timer_container_of()), and del_timer_sync() was removed in 6.15 (replaced by timer_delete_sync()). The fix adds versioned #define shims so the same source compiles on old and new kernels.

bash — run as root
sudo python3 -c "
path = '<BASE>/include/osdep_service_linux.h'
lines = open(path).readlines()
for i, l in enumerate(lines):
    if l.strip() == '#include <linux/version.h>':
        shim = [
            '/* Kernel 6.x compat shims */\n',
            '#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 15, 0)\n',
            '#define del_timer_sync timer_delete_sync\n',
            '#endif\n',
            '#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 16, 0)\n',
            '#define from_timer(var, callback_timer, timer_fieldname) \\\\\n',
            '    timer_container_of(var, callback_timer, timer_fieldname)\n',
            '#endif\n',
        ]
        lines[i+1:i+1] = shim
        open(path, 'w').writelines(lines)
        print('Shims inserted after line', i+1)
        break
"

Patch 3 — ioctl_cfg80211.c: cfg80211 API changes

Why: Kernel 6.x added MLO (Multi-Link Operation, Wi-Fi 7) support, which required changing several cfg80211_ops callback signatures. Three functions need updating. Check the current expected signatures in your running kernel's headers if needed:

bash
grep -n "get_tx_power\|set_tx_power\|get_channel" \
  /usr/src/linux-headers-$(uname -r)-common/include/net/cfg80211.h

Apply the patch with this script:

bash — run as root
sudo python3 << 'PYEOF'
path = '<BASE>/os_dep/linux/ioctl_cfg80211.c'
content = open(path).read()

# Fix set_txpower: add radio_idx after wdev block
old = ('#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0))\n'
       '\tstruct wireless_dev *wdev,\n'
       '#endif\n'
       '#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) || defined(COMPAT_KERNEL_RELEASE)\n'
       '\tenum nl80211_tx_power_setting type, int mbm)')
new = ('#if (LINUX_VERSION_CODE >= KERNEL_VERSION(3, 8, 0))\n'
       '\tstruct wireless_dev *wdev,\n'
       '#endif\n'
       '#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0))\n'
       '\tint radio_idx,\n'
       '#endif\n'
       '#if (LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)) || defined(COMPAT_KERNEL_RELEASE)\n'
       '\tenum nl80211_tx_power_setting type, int mbm)')
content = content.replace(old, new, 1)

# Fix get_txpower: replace old link_id block with radio_idx + unsigned link_id
old2 = ('#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 14, 0))\n'
        '\tint link_id,\n'
        '#endif\n'
        '\tint *dbm)')
new2 = ('#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0))\n'
        '\tint radio_idx,\n'
        '\tunsigned int link_id,\n'
        '#endif\n'
        '\tint *dbm)')
content = content.replace(old2, new2, 1)

# Fix get_channel: int link_id -> unsigned int link_id
content = content.replace(
    'struct wireless_dev *wdev, int link_id, struct cfg80211_chan_def *chandef){',
    'struct wireless_dev *wdev, unsigned int link_id, struct cfg80211_chan_def *chandef){'
)

# Fix set_wiphy_params: add int link_id parameter for kernel 6.x
content = content.replace(
    'static int cfg80211_rtw_set_wiphy_params(struct wiphy *wiphy, u32 changed)',
    '#if (LINUX_VERSION_CODE >= KERNEL_VERSION(6, 0, 0))\n'
    'static int cfg80211_rtw_set_wiphy_params(struct wiphy *wiphy, int link_id, u32 changed)\n'
    '#else\n'
    'static int cfg80211_rtw_set_wiphy_params(struct wiphy *wiphy, u32 changed)\n'
    '#endif'
)

open(path, 'w').write(content)
print('cfg80211 patches applied')
PYEOF

Build & Install

bash
cd /usr/src/realtek-rtl88xxau-<version>

# Build
sudo make -j4 KVER=$(uname -r) KSRC=/lib/modules/$(uname -r)/build

# Verify the .ko was produced
ls -lh 88XXau.ko

# Install manually for immediate use
sudo make install KVER=$(uname -r)

# Install via DKMS — survives kernel updates
sudo dkms install realtek-rtl88xxau/<version> --force

Blacklist the in-kernel driver

Without this, the system may load rtw88_8812au on the next boot instead of your newly installed module.

bash
echo "blacklist rtw88_8812au" | sudo tee /etc/modprobe.d/rtw88-blacklist.conf
echo "88XXau" | sudo tee /etc/modules-load.d/88xxau.conf
sudo update-initramfs -u

Verify

bash
# Load the module
sudo modprobe 88XXau

# Check it's running — should show Nickname:"<WIFI@REALTEK>"
iwconfig wlan0

# Test monitor mode
sudo ip link set wlan0 down
sudo iw dev wlan0 set type monitor
sudo ip link set wlan0 up
iwconfig wlan0   # Mode should say Monitor

# Test airodump-ng
sudo timeout 15 airodump-ng wlan0 --output-format csv -w /tmp/test --write-interval 3

Notes

These patches target kernel 6.19 (Kali 2026). The version guards in patches 2 and 3 mean they won't break on older kernels. If you're on a significantly newer kernel and the build still fails, run make V=1 for verbose output and check the -I flags in the compiler command — include path issues usually show up there.

DKMS will automatically rebuild 88XXau.ko when a new kernel is installed. If set_wiphy_params or other cfg80211 signatures change again in a future kernel, check /usr/src/linux-headers-$(uname -r)*/include/net/cfg80211.h for the current expected signatures.

Applies to: Alfa AWUS036ACH and any other adapter using the RTL8812AU chipset on Kali Linux with kernel 6.x. The root cause (driver written for older kernel APIs) will affect any distro running these kernel versions.
Pixel the AI mascot