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.
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.
sudo apt install dkms linux-headers-$(uname -r) build-essential
The DKMS source package should already be present on Kali. Verify:
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.
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.
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)
"
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.
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
"
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:
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:
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
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
Without this, the system may load rtw88_8812au on the next boot instead of your newly installed module.
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
# 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
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.