From 26f952247d08939845d7dea3b3b14b8717c0ab27 Mon Sep 17 00:00:00 2001 From: YaleiZang Date: Tue, 23 Jun 2026 11:02:22 +0800 Subject: [PATCH 01/49] airoha: correct node addresses for crypto and thermal-sensor Update the @ address suffix of crypto and thermal-sensor nodes in the device tree to match the reg properties for consistency. Signed-off-by: YaleiZang Link: https://github.com/openwrt/openwrt/pull/23920 (cherry picked from commit f7864649ce601d0c1fb8f78366a0f0aaca7f4af2) Link: https://github.com/openwrt/openwrt/pull/24153 Signed-off-by: Jonas Jelonek --- target/linux/airoha/dts/an7581.dtsi | 4 ++-- target/linux/airoha/dts/an7583.dtsi | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/target/linux/airoha/dts/an7581.dtsi b/target/linux/airoha/dts/an7581.dtsi index dc6b014f14..a33409f289 100644 --- a/target/linux/airoha/dts/an7581.dtsi +++ b/target/linux/airoha/dts/an7581.dtsi @@ -575,14 +575,14 @@ #phy-cells = <1>; }; - crypto@1e004000 { + crypto@1fb70000 { compatible = "inside-secure,safexcel-eip93ies"; reg = <0x0 0x1fb70000 0x0 0x1000>; interrupts = ; }; - thermal: thermal-sensor@1efbd800 { + thermal: thermal-sensor@1efbd000 { compatible = "airoha,en7581-thermal"; reg = <0x0 0x1efbd000 0x0 0xd5c>; interrupts = ; diff --git a/target/linux/airoha/dts/an7583.dtsi b/target/linux/airoha/dts/an7583.dtsi index 33d5be1513..7a39e9b221 100644 --- a/target/linux/airoha/dts/an7583.dtsi +++ b/target/linux/airoha/dts/an7583.dtsi @@ -539,7 +539,7 @@ status = "disabled"; }; - crypto@1e004000 { + crypto@1fb70000 { compatible = "inside-secure,safexcel-eip93ies"; reg = <0x0 0x1fb70000 0x0 0x1000>; From c574db4737395322868dcd21743c454a94c2a0d9 Mon Sep 17 00:00:00 2001 From: YaleiZang Date: Tue, 23 Jun 2026 11:34:02 +0800 Subject: [PATCH 02/49] airoha: align ethernet-phy node name with reg property Change ethernet-phy node name for alignment with reg. This enhances the clarity and maintainability of the device-tree. Signed-off-by: YaleiZang Link: https://github.com/openwrt/openwrt/pull/23920 (cherry picked from commit 4745f757a7d6b571d2279052b40a0dc5ba1ab2a0) Link: https://github.com/openwrt/openwrt/pull/24153 Signed-off-by: Jonas Jelonek --- target/linux/airoha/dts/an7583.dtsi | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/target/linux/airoha/dts/an7583.dtsi b/target/linux/airoha/dts/an7583.dtsi index 7a39e9b221..5656ffb20d 100644 --- a/target/linux/airoha/dts/an7583.dtsi +++ b/target/linux/airoha/dts/an7583.dtsi @@ -747,7 +747,7 @@ #address-cells = <1>; #size-cells = <0>; - gsw_phy1: ethernet-phy@1 { + gsw_phy1: ethernet-phy@9 { compatible = "ethernet-phy-ieee802.3-c22"; reg = <9>; phy-mode = "internal"; @@ -770,7 +770,7 @@ }; }; - gsw_phy2: ethernet-phy@2 { + gsw_phy2: ethernet-phy@a { compatible = "ethernet-phy-ieee802.3-c22"; reg = <10>; phy-mode = "internal"; @@ -793,7 +793,7 @@ }; }; - gsw_phy3: ethernet-phy@3 { + gsw_phy3: ethernet-phy@b { compatible = "ethernet-phy-ieee802.3-c22"; reg = <11>; phy-mode = "internal"; @@ -816,7 +816,7 @@ }; }; - gsw_phy4: ethernet-phy@4 { + gsw_phy4: ethernet-phy@c { compatible = "ethernet-phy-ieee802.3-c22"; reg = <12>; phy-mode = "internal"; From 01de5484fc7f6191ac1f5c922332906fd6f2dc81 Mon Sep 17 00:00:00 2001 From: YaleiZang Date: Tue, 30 Jun 2026 16:13:01 +0800 Subject: [PATCH 03/49] airoha: remove KERNEL_LOADADDR from airoha_an7583-evb device definition Delete the KERNEL_LOADADDR assignment from the airoha_an7583-evb device definition. The kernel load address is now centrally managed in image Makefile with, just using default load address: loadaddr-$(CONFIG_TARGET_airoha_an7583) := 0x80200000 This ensures the load address configuration is maintained in a single location. Removing the duplicate assignment prevents confusion and potential misconfiguration. Signed-off-by: YaleiZang Link: https://github.com/openwrt/openwrt/pull/24000 Signed-off-by: Robert Marko (cherry picked from commit 10631d244cdacd004ccfe74c691765f9beacd413) Link: https://github.com/openwrt/openwrt/pull/24153 Signed-off-by: Jonas Jelonek --- target/linux/airoha/image/an7583.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/target/linux/airoha/image/an7583.mk b/target/linux/airoha/image/an7583.mk index bbc78c65ae..f51b79e27b 100644 --- a/target/linux/airoha/image/an7583.mk +++ b/target/linux/airoha/image/an7583.mk @@ -11,7 +11,6 @@ define Device/airoha_an7583-evb DEVICE_PACKAGES := kmod-phy-aeonsemi-as21xxx kmod-leds-pwm kmod-pwm-airoha kmod-input-gpio-keys-polled DEVICE_DTS := an7583-evb DEVICE_DTS_CONFIG := config@1 - KERNEL_LOADADDR := 0x80088000 IMAGE/sysupgrade.bin := append-kernel | pad-to 128k | append-rootfs | pad-rootfs | append-metadata endef TARGET_DEVICES += airoha_an7583-evb From 42194f91881f6735b4848a741b88b66793857d48 Mon Sep 17 00:00:00 2001 From: Fil Dunsky Date: Fri, 9 Jan 2026 20:16:30 +0300 Subject: [PATCH 04/49] mediatek: add support for Hiveton H5000M Hardware specification: SoC: MediaTek MT7987A Flash: 8GB eMMC RAM: 1GB DDR4 Ethernet: 2x 2.5GbE (RTL8221B PHY + Internal PHY) WiFi: MediaTek MT7992 / 2+3 antenna variant Interface: M.2 USB for 5G module LED: Power (hw-controlled), 2.5G WAN (user-definable), 5G Module (hw-controlled), LED-3 (user-definable), LED-4 (user-definable) Button: Reset, WPS Power: USB Type-C PD Other: PWM Fan control MAC address assignment is unconventional: the board has no Factory partition with pre-programmed MACs. Instead, all addresses are derived from the eMMC CID at runtime: LAN : macaddr_generate_from_mmc_cid mmcblk0 WAN : LAN + 1 (label_mac) phy0: LAN + 2 phy1: LAN + 3 This matches the vendor firmware behaviour and ensures stable, unique addresses across reboots without a dedicated EEPROM region for MACs. Flash instructions: Factory `mmcblk0p2` partition is empty, stock ImmortalWrt-798x-mt799x-6.6-mtwifi from PadavanOnly firmware uses `MT7991_MT7976_EEPROM_BE5040_iPAiLNA.bin`. Flashing this eeprom before flashing OpenWrt will make OpenWrt read eeprom: ``` dd if=/lib/firmware/MT7991_MT7976_EEPROM_BE5040_iPAiLNA.bin of=/dev/mmcblk0p2 bs=1 count=7680 sync ``` But it will have wifi issues for now. It's better to use OpenWrt fallback eeprom for now. Power off the device. Press and hold the Reset button. Power on the device while keeping the Reset button pressed. Wait for the device to enter U-Boot recovery mode. Connect to the device via Ethernet (default IP: 192.168.1.1). Set your PC's IP to 192.168.1.x (e.g., 192.168.1.100). Open a web browser and navigate to http://192.168.1.1 Upload and flash the OpenWrt firmware Wait for the flashing process to complete and the device to reboot. Signed-off-by: Fil Dunsky Link: https://github.com/openwrt/openwrt/pull/21398 (cherry picked from commit 6487cc9a1f4caa07486772be851aaed3d155345d) Link: https://github.com/openwrt/openwrt/pull/24156 Signed-off-by: Jonas Jelonek --- .../mediatek/dts/mt7987a-hiveton-h5000m.dts | 245 ++++++++++++++++++ .../filogic/base-files/etc/board.d/01_leds | 4 + .../filogic/base-files/etc/board.d/02_network | 6 + .../etc/hotplug.d/ieee80211/11_fix_wifi_mac | 5 + .../base-files/lib/upgrade/platform.sh | 3 + target/linux/mediatek/image/filogic.mk | 14 + 6 files changed, 277 insertions(+) create mode 100644 target/linux/mediatek/dts/mt7987a-hiveton-h5000m.dts diff --git a/target/linux/mediatek/dts/mt7987a-hiveton-h5000m.dts b/target/linux/mediatek/dts/mt7987a-hiveton-h5000m.dts new file mode 100644 index 0000000000..7c4ff8a1ab --- /dev/null +++ b/target/linux/mediatek/dts/mt7987a-hiveton-h5000m.dts @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: (GPL-2.0 OR MIT) + +/dts-v1/; +#include "mt7987a.dtsi" +#include + +/ { + model = "Hiveton H5000M"; + compatible = "hiveton,h5000m", "mediatek,mt7987a", "mediatek,mt7987"; + + aliases { + led-boot = &led_status_blue; + led-failsafe = &led_status_amber; + led-upgrade = &led_status_amber; + serial0 = &uart0; + }; + + chosen { + bootargs = "earlycon=uart8250,mmio32,0x11000000 \ + root=PARTLABEL=rootfs rootwait pci=pcie_bus_perf"; + stdout-path = "serial0:115200n8"; + }; + + gpio-keys { + compatible = "gpio-keys"; + + button-reset { + label = "reset"; + linux,code = ; + gpios = <&pio 1 GPIO_ACTIVE_LOW>; + debounce-interval = <10>; + }; + + button-wps { + label = "wps"; + linux,code = ; + gpios = <&pio 0 GPIO_ACTIVE_LOW>; + debounce-interval = <10>; + }; + }; + + /* + * LED Indicator Layout + * ------------------- + * Total of 5 LED indicators, viewed from front with antenna facing up (left to right): + * + * LED 1: 5G Module indicator + * LED 2: Built-in 2.5G Ethernet port indicator (configurable in system) + * LED 3: User-definable (configurable in system) + * LED 4: User-definable (configurable in system) + * LED 5: Power indicator + * + * Note: + * - LEDs 1 and 5 are hardware-controlled and cannot be modified via software + * - In vendor firmware, LEDs 3 and 4 are used as WiFi link activity indicators + * for dual-band (2.4GHz/5GHz) respectively + */ + leds { + compatible = "gpio-leds"; + + led_status_amber: led-3 { + function = LED_FUNCTION_WLAN_2GHZ; + color = ; + gpios = <&pio 3 GPIO_ACTIVE_LOW>; + }; + + led_status_blue: led-4 { + function = LED_FUNCTION_WLAN_5GHZ; + color = ; + gpios = <&pio 4 GPIO_ACTIVE_LOW>; + }; + }; + + reg_3p3v: regulator-3p3v { + compatible = "regulator-fixed"; + regulator-name = "fixed-3.3V"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + regulator-always-on; + }; + + reg_usb_5v: regulator-usb-5v { + compatible = "regulator-fixed"; + regulator-name = "usb-5v"; + regulator-max-microvolt = <5000000>; + regulator-min-microvolt = <5000000>; + regulator-boot-on; + regulator-always-on; + }; +}; + +ð { + status = "okay"; +}; + +&fan { + pwms = <&pwm 1 50000 0>; + status = "okay"; +}; + +&gmac0 { + phy-mode = "2500base-x"; + phy-handle = <&phy0>; + status = "okay"; +}; + +&gmac1 { + phy-mode = "internal"; + phy-handle = <&phy1>; + status = "okay"; +}; + +&mdio { + /* RTL8221B-VB-CG 2.5Gbps PHY (away from power) eth0 */ + phy0: phy@1 { + compatible = "ethernet-phy-ieee802.3-c45"; + reg = <1>; + reset-gpios = <&pio 42 GPIO_ACTIVE_LOW>; + reset-assert-us = <100000>; + reset-deassert-us = <100000>; + interrupt-parent = <&pio>; + interrupts = <41 IRQ_TYPE_LEVEL_LOW>; + realtek,aldps-enable; + }; + + /* built-in 2.5G Ethernet PHY (near power) eth1 */ + phy1: phy@15 { + compatible = "ethernet-phy-ieee802.3-c45"; + reg = <15>; + pinctrl-names = "i2p5gbe-led"; + pinctrl-0 = <&i2p5gbe_led0_pins>; + + leds { + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + function = LED_FUNCTION_WAN; + color = ; + }; + }; + }; +}; + +&mmc0 { + pinctrl-names = "default", "state_uhs"; + pinctrl-0 = <&mmc_pins_default>; + pinctrl-1 = <&mmc_pins_uhs>; + bus-width = <8>; + max-frequency = <48000000>; + cap-mmc-highspeed; + vmmc-supply = <®_3p3v>; + non-removable; + status = "okay"; + + card@0 { + compatible = "mmc-card"; + reg = <0>; + + block { + compatible = "block-device"; + + partitions { + block-partition-factory { + partname = "factory"; + + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + eeprom_factory_0: eeprom@0 { + reg = <0x0 0x1e00>; + }; + }; + }; + }; + }; + }; +}; + +&pcie0 { + pinctrl-names = "default"; + pinctrl-0 = <&pcie0_pins>; + reset-gpios = <&pio 36 GPIO_ACTIVE_HIGH>; + status = "okay"; + + pcie@0,0 { + reg = <0x0000 0 0 0 0>; + #address-cells = <3>; + #size-cells = <2>; + device_type = "pci"; + + mt7992@0,0 { + compatible = "mediatek,mt76"; + reg = <0x0000 0 0 0 0>; + + nvmem-cells = <&eeprom_factory_0>; + nvmem-cell-names = "eeprom"; + + #address-cells = <1>; + #size-cells = <0>; + + ieee80211-freq-limit = <2400000 2500000>, + <5170000 5835000>; + }; + }; +}; + +&pcie1 { + status = "disabled"; +}; + +&pio { + pwm_fan_pins: pwm-fan-pins { + mux { + function = "pwm"; + groups = "pwm1_0"; + }; + }; +}; + +&pwm { + status = "okay"; + pinctrl-names = "default"; + pinctrl-0 = <&pwm_fan_pins>; +}; + +&ssusb { + status = "okay"; + vusb33-supply = <®_3p3v>; + vbus-supply = <®_usb_5v>; +}; + +&tphyu3port0 { + status = "okay"; +}; + +&uart0 { + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; + status = "okay"; +}; diff --git a/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds b/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds index 473684825f..dc1f9df6d4 100644 --- a/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds +++ b/target/linux/mediatek/filogic/base-files/etc/board.d/01_leds @@ -111,6 +111,10 @@ glinet,gl-xe3000) ucidef_set_led_netdev "wlan2g" "WLAN2G" "green:wifi2g" "phy0-ap0" ucidef_set_led_netdev "wlan5g" "WLAN5G" "green:wifi5g" "phy1-ap0" ;; +hiveton,h5000m) + ucidef_set_led_netdev "wlan2g" "wlan-2ghz" "amber:wlan-2ghz" "phy0.0-ap0" "link tx rx" + ucidef_set_led_netdev "wlan5g" "wlan-5ghz" "blue:wlan-5ghz" "phy0.1-ap0" "link tx rx" + ;; huasifei,wh3000) ucidef_set_led_netdev "wan" "WAN" "red:wan" "eth1" "link tx rx" ;; diff --git a/target/linux/mediatek/filogic/base-files/etc/board.d/02_network b/target/linux/mediatek/filogic/base-files/etc/board.d/02_network index 95b297efb4..443918f06d 100644 --- a/target/linux/mediatek/filogic/base-files/etc/board.d/02_network +++ b/target/linux/mediatek/filogic/base-files/etc/board.d/02_network @@ -133,6 +133,7 @@ mediatek_setup_interfaces() airpi,ap3000m|\ bananapi,bpi-r3-mini|\ edgecore,eap111|\ + hiveton,h5000m|\ huasifei,wh3000|\ huasifei,wh3000-pro-emmc|\ huasifei,wh3000-pro-nand) @@ -266,6 +267,11 @@ mediatek_setup_macs() lan_mac=$(macaddr_add "$wan_mac" 1) label_mac=$wan_mac ;; + hiveton,h5000m) + lan_mac=$(macaddr_generate_from_mmc_cid mmcblk0) + wan_mac=$(macaddr_add "$lan_mac" 1) + label_mac=$wan_mac + ;; jiorouter,ax6000-jidu6101) label_mac=$(mtd_get_mac_ascii u-boot-env mac) wan_mac=$label_mac diff --git a/target/linux/mediatek/filogic/base-files/etc/hotplug.d/ieee80211/11_fix_wifi_mac b/target/linux/mediatek/filogic/base-files/etc/hotplug.d/ieee80211/11_fix_wifi_mac index 5c4ddd2c29..c184316ee7 100644 --- a/target/linux/mediatek/filogic/base-files/etc/hotplug.d/ieee80211/11_fix_wifi_mac +++ b/target/linux/mediatek/filogic/base-files/etc/hotplug.d/ieee80211/11_fix_wifi_mac @@ -133,6 +133,11 @@ case "$board" in [ "$PHYNBR" = "0" ] && macaddr_add $addr 2 > /sys${DEVPATH}/macaddress [ "$PHYNBR" = "1" ] && macaddr_add $addr 3 > /sys${DEVPATH}/macaddress ;; + hiveton,h5000m) + base_mac=$(macaddr_generate_from_mmc_cid mmcblk0) + [ "$PHYNBR" = "0" ] && macaddr_add $base_mac 2 > /sys${DEVPATH}/macaddress + [ "$PHYNBR" = "1" ] && macaddr_add $base_mac 3 > /sys${DEVPATH}/macaddress + ;; iptime,ax3000q) addr=$(mtd_get_mac_binary "Factory" 0x4) [ "$PHYNBR" = "1" ] && macaddr_setbit_la $addr > /sys${DEVPATH}/macaddress diff --git a/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh b/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh index 47b1d9627d..c094dc5a74 100644 --- a/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh +++ b/target/linux/mediatek/filogic/base-files/lib/upgrade/platform.sh @@ -164,6 +164,7 @@ platform_do_upgrade() { glinet,gl-mt6000|\ glinet,gl-x3000|\ glinet,gl-xe3000|\ + hiveton,h5000m|\ huasifei,wh3000|\ huasifei,wh3000-pro-emmc|\ smartrg,sdg-8612|\ @@ -358,6 +359,7 @@ platform_check_image() { ;; creatlentem,clt-r30b1|\ creatlentem,clt-r30b1-112m|\ + hiveton,h5000m|\ nradio,c8-668gl) # tar magic `ustar` magic="$(dd if="$1" bs=1 skip=257 count=5 2>/dev/null)" @@ -403,6 +405,7 @@ platform_copy_config() { glinet,gl-mt6000|\ glinet,gl-x3000|\ glinet,gl-xe3000|\ + hiveton,h5000m|\ huasifei,wh3000|\ huasifei,wh3000-pro-emmc|\ jdcloud,re-cp-03|\ diff --git a/target/linux/mediatek/image/filogic.mk b/target/linux/mediatek/image/filogic.mk index a1bce5ff1e..2d991cbc22 100644 --- a/target/linux/mediatek/image/filogic.mk +++ b/target/linux/mediatek/image/filogic.mk @@ -1785,6 +1785,20 @@ define Device/h3c_magic-nx30-pro endef TARGET_DEVICES += h3c_magic-nx30-pro +define Device/hiveton_h5000m + DEVICE_VENDOR := Hiveton + DEVICE_MODEL := H5000M + DEVICE_ALT0_VENDOR := Airpi + DEVICE_ALT0_MODEL := H5000M + DEVICE_DTS := mt7987a-hiveton-h5000m + DEVICE_DTS_DIR := ../dts + DEVICE_PACKAGES := kmod-hwmon-pwmfan kmod-usb3 mt7987-2p5g-phy-firmware \ + kmod-mt7996e kmod-mt7992-23-firmware f2fsck mkf2fs + KERNEL_LOADADDR := 0x40000000 + IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata +endef +TARGET_DEVICES += hiveton_h5000m + define Device/huasifei_wh3000 DEVICE_VENDOR := Huasifei DEVICE_MODEL := WH3000 From bce309a33b1dd05dcc50b1bb2e0bf42f9bdff59c Mon Sep 17 00:00:00 2001 From: Paul Spooren Date: Thu, 15 Jan 2026 09:19:16 +0700 Subject: [PATCH 05/49] ci: rework and move build-on-comment action Run this only when a PR is created, so either author or anyone who can edit the PR message (all maintainers) can trigger such build. Instead of maintaining it in the main repository, move it to the external repository to bloat this repository less with further CI commits. Signed-off-by: Paul Spooren (cherry picked from commit 8cff0d8ca0d42aec4cb47313cd0c3dd99e06bbec) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/build-on-comment.yml | 164 ------------------------- .github/workflows/build-pr-profile.yml | 12 ++ 2 files changed, 12 insertions(+), 164 deletions(-) delete mode 100644 .github/workflows/build-on-comment.yml create mode 100644 .github/workflows/build-pr-profile.yml diff --git a/.github/workflows/build-on-comment.yml b/.github/workflows/build-on-comment.yml deleted file mode 100644 index cbce8afe17..0000000000 --- a/.github/workflows/build-on-comment.yml +++ /dev/null @@ -1,164 +0,0 @@ -name: Build on Comment - -on: - issue_comment: - types: [created, edited] - -permissions: - pull-requests: write - -concurrency: - group: build-on-comment-${{ github.event.issue.number || github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - check-and-build: - if: github.event.issue.pull_request != null - runs-on: ubuntu-latest - - steps: - - name: Check if user is in reviewers team - id: check-reviewer - run: | - USERNAME="${{ github.event.comment.user.login }}" - - STATUS_CODE=$(curl -s -H "Authorization: token ${{ secrets.LOOKUP_MEMBERS }}" \ - -o response.json -w "%{http_code}" \ - https://api.github.com/orgs/openwrt/teams/reviewers/memberships/$USERNAME) - - if grep -q '"state": "active"' response.json && [ "$STATUS_CODE" -eq 200 ]; then - echo "authorized=true" >> $GITHUB_OUTPUT - else - echo "authorized=false" >> $GITHUB_OUTPUT - fi - - - name: Parse build command - if: steps.check-reviewer.outputs.authorized == 'true' - id: parse-command - run: | - COMMENT="${{ github.event.comment.body }}" - if echo "$COMMENT" | grep -q "build [a-zA-Z0-9_-]\+/[a-zA-Z0-9_-]\+/[a-zA-Z0-9_-]\+"; then - BUILD_PATH=$(echo "$COMMENT" | grep -o "build [a-zA-Z0-9_-]\+/[a-zA-Z0-9_-]\+/[a-zA-Z0-9_-]\+" | sed 's/build //') - TARGET=$(echo "$BUILD_PATH" | cut -d'/' -f1) - SUBTARGET=$(echo "$BUILD_PATH" | cut -d'/' -f2) - PROFILE=$(echo "$BUILD_PATH" | cut -d'/' -f3) - echo "build_requested=true" >> $GITHUB_OUTPUT - echo "target=$TARGET" >> $GITHUB_OUTPUT - echo "subtarget=$SUBTARGET" >> $GITHUB_OUTPUT - echo "profile=$PROFILE" >> $GITHUB_OUTPUT - echo "build_path=$BUILD_PATH" >> $GITHUB_OUTPUT - else - echo "build_requested=false" >> $GITHUB_OUTPUT - fi - - - name: Find existing build comment - if: steps.parse-command.outputs.build_requested == 'true' - id: find-comment - uses: peter-evans/find-comment@v2 - with: - issue-number: ${{ github.event.pull_request.number || github.event.issue.number }} - comment-author: "github-actions[bot]" - - - name: Create early build comment - if: steps.parse-command.outputs.build_requested == 'true' - id: start-comment - uses: peter-evans/create-or-update-comment@v3 - with: - issue-number: ${{ github.event.pull_request.number || github.event.issue.number }} - comment-id: ${{ steps.find-comment.outputs.comment-id }} - body: | - ๐Ÿšง **Build in progress for** `${{ steps.parse-command.outputs.build_path }}`... - - You can follow progress [here](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - *Triggered by: @${{ github.event.comment.user.login }}* - edit-mode: replace - - - name: Checkout repository - if: steps.parse-command.outputs.build_requested == 'true' - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - ref: refs/pull/${{ github.event.issue.number }}/merge - - - name: Setup build environment - if: steps.parse-command.outputs.build_requested == 'true' - continue-on-error: true - run: | - sudo apt-get update - sudo apt-get install -y build-essential libncurses5-dev gawk git subversion libssl-dev gettext zlib1g-dev swig unzip time rsync - - - name: Build target - if: steps.parse-command.outputs.build_requested == 'true' - id: build - run: | - make defconfig - echo "CONFIG_DEVEL=y" > .config - echo "CONFIG_BPF_TOOLCHAIN_HOST=y" >> .config - echo "CONFIG_TARGET_${{ steps.parse-command.outputs.target }}=y" >> .config - echo "CONFIG_TARGET_${{ steps.parse-command.outputs.target }}_${{ steps.parse-command.outputs.subtarget }}=y" >> .config - echo "CONFIG_TARGET_${{ steps.parse-command.outputs.target }}_${{ steps.parse-command.outputs.subtarget }}_DEVICE_${{ steps.parse-command.outputs.profile }}=y" >> .config - - make defconfig - make -j$(nproc) BUILD_LOG=1 - - echo "build_success=true" >> $GITHUB_OUTPUT - - - name: Upload log - uses: actions/upload-artifact@v4 - if: steps.check-reviewer.outputs.authorized == 'true' && (success() || failure()) - with: - name: build-log-${{ steps.parse-command.outputs.target }}-${{ steps.parse-command.outputs.subtarget }}-${{ steps.parse-command.outputs.profile }} - path: logs/ - - - name: Create artifact archive - if: steps.build.outputs.build_success == 'true' - run: | - cd bin/ - tar -czf ../build-artifacts.tar.gz * - cd .. - - - name: Upload build artifacts - if: steps.build.outputs.build_success == 'true' - uses: actions/upload-artifact@v4 - with: - name: build-${{ steps.parse-command.outputs.target }}-${{ steps.parse-command.outputs.subtarget }}-${{ steps.parse-command.outputs.profile }} - path: build-artifacts.tar.gz - - - name: Update comment with build results - if: steps.build.outputs.build_success == 'true' - uses: peter-evans/create-or-update-comment@v3 - with: - comment-id: ${{ steps.start-comment.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number || github.event.issue.number }} - body: | - ## Build Results for `${{ steps.parse-command.outputs.build_path }}` - - โœ… **Build completed successfully!** - - **Target:** `${{ steps.parse-command.outputs.target }}` - **Subtarget:** `${{ steps.parse-command.outputs.subtarget }}` - **Profile:** `${{ steps.parse-command.outputs.profile }}` - - ๐Ÿ“ฆ **Artifacts:** [Download build-${{ steps.parse-command.outputs.target }}-${{ steps.parse-command.outputs.subtarget }}-${{ steps.parse-command.outputs.profile }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) - - *Build triggered by: @${{ github.event.comment.user.login }}* - *Last updated: ${{ github.event.comment.created_at }}* - edit-mode: replace - - - name: Update comment on build failure - if: steps.parse-command.outputs.build_requested == 'true' && steps.build.outputs.build_success == 'false' - uses: peter-evans/create-or-update-comment@v3 - with: - comment-id: ${{ steps.start-comment.outputs.comment-id }} - issue-number: ${{ github.event.pull_request.number || github.event.issue.number }} - body: | - ## Build Results for `${{ steps.parse-command.outputs.build_path }}` - - โŒ **Build failed!** - - Please check the [action logs](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) for more details. - - *Build triggered by: @${{ github.event.comment.user.login }}* - edit-mode: replace diff --git a/.github/workflows/build-pr-profile.yml b/.github/workflows/build-pr-profile.yml new file mode 100644 index 0000000000..3be461c354 --- /dev/null +++ b/.github/workflows/build-pr-profile.yml @@ -0,0 +1,12 @@ +name: Build PR Profile + +on: + pull_request: + types: [opened, edited, synchronize] + +permissions: + pull-requests: write + +jobs: + build-pr-profile: + uses: openwrt/actions-shared-workflows/.github/workflows/build-pr-profile.yml@main From c5f379d2f831951a8be4e52399b891dc09cdfa2f Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 22 Mar 2026 04:00:51 +0100 Subject: [PATCH 06/49] github: add Claude code review workflow triggered by /claude comment Add claude-code-review.yml using anthropics/claude-code-action@v1. The review runs when a PR comment containing "/claude" is posted, avoiding unnecessary API usage on every push. Uses issue_comment trigger restricted to PR comments only. Only triggers for users with write access (OWNER, MEMBER, COLLABORATOR) and when the comment body contains "/claude", preventing any runner from being allocated for unrelated comments. A short domain hint steers the review toward OpenWrt embedded Linux conventions. Co-Authored-By: Claude Sonnet 4.6 Link: https://github.com/openwrt/openwrt/pull/22556 Signed-off-by: Hauke Mehrtens (cherry picked from commit c0e75c9de6f263a584567d25013c52843eb4ec5e) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/claude-code-review.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml new file mode 100644 index 0000000000..9682131eaa --- /dev/null +++ b/.github/workflows/claude-code-review.yml @@ -0,0 +1,38 @@ +name: Claude Code Review + +on: + issue_comment: + types: [created] + +permissions: + contents: read + pull-requests: write + +jobs: + code-review: + name: Claude Code Review + # Only run on PR comments containing "/claude" from users with write access + if: >- + github.event.issue.pull_request && + contains(github.event.comment.body, '/claude') && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), + github.event.comment.author_association) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 1 + + - name: PR Review + uses: anthropics/claude-code-action@6062f3709600659be5e47fcddf2cf76993c235c2 # v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + github_token: ${{ secrets.GITHUB_TOKEN }} + trigger_phrase: "/claude" + prompt: >- + Review this PR for an OpenWrt embedded Linux project, + focusing on correctness, security, and coding conventions. + claude_args: >- + --allowedTools + "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" From eb680551f2bed4bfcf60887b952efa1296e41987 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 22 Mar 2026 21:28:54 +0100 Subject: [PATCH 07/49] github: remove allowedTools restriction from claude-code-review The whitelist was too restrictive, causing permission denials when Claude tried to use basic tools like Read or Grep to analyse the diff. Remove it and let the action use its default toolset. Fixes: c0e75c9de6f2 ("github: add Claude code review workflow triggered by /claude comment") Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Hauke Mehrtens (cherry picked from commit 29e1c7b85abcbb771209b2f983a069f826428cff) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 9682131eaa..df19bcd290 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -33,6 +33,3 @@ jobs: prompt: >- Review this PR for an OpenWrt embedded Linux project, focusing on correctness, security, and coding conventions. - claude_args: >- - --allowedTools - "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*)" From dbef2c036ed0e4ed8d06eba7ddbb3739c5b118e2 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 22 Mar 2026 21:31:41 +0100 Subject: [PATCH 08/49] github: add actions: read permission for CI status in claude-code-review The claude-code-action uses mcp__github_ci__get_ci_status and related tools to check workflow run status as part of the review. Without actions: read the GitHub API calls for CI status are rejected. Fixes: c0e75c9de6f2 ("github: add Claude code review workflow triggered by /claude comment") Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Hauke Mehrtens (cherry picked from commit 122b2f2ef506de26787cea79038c7d7dba9137eb) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index df19bcd290..13f1dddb64 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -7,6 +7,7 @@ on: permissions: contents: read pull-requests: write + actions: read jobs: code-review: From 12ac6b6bf036fc22998faeebd43452200edc3d62 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Mon, 23 Mar 2026 00:59:06 +0100 Subject: [PATCH 09/49] github: fix claude-code-review running in tag mode for issue_comment Without a prompt the action auto-detected mode: tag instead of agent when triggered via issue_comment, resulting in no review being posted. Remove the prompt so the action can correctly use its built-in agent mode with full PR context. Fixes: c0e75c9de6f2 ("github: add Claude code review workflow triggered by /claude comment") Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Hauke Mehrtens (cherry picked from commit 62ca0a987f790b5039f6e3b3029d4ddc9ca20b60) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 13f1dddb64..6259e79c0d 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -31,6 +31,3 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} trigger_phrase: "/claude" - prompt: >- - Review this PR for an OpenWrt embedded Linux project, - focusing on correctness, security, and coding conventions. From e298f27a7f3fcf7152785148a5cc47dac5a2b3a1 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Mon, 23 Mar 2026 02:07:43 +0100 Subject: [PATCH 10/49] github: pin claude-code-action to v1.0.49 Version 1.0.49 added support for PRs from forked repositories. This feature was reverted in v1.0.50, see: https://github.com/anthropics/claude-code-action/pull/937 v1.0.49 broke commits made by Claude, but we do not use that feature. Pin to v1.0.49 until https://github.com/anthropics/claude-code-action/pull/963 is merged and released. Fixes: c0e75c9de6f2 ("github: add Claude code review workflow triggered by /claude comment") Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Hauke Mehrtens (cherry picked from commit b048949114dd13c6621fafccf99b9dfcf6f8e929) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 6259e79c0d..6146392135 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 1 - name: PR Review - uses: anthropics/claude-code-action@6062f3709600659be5e47fcddf2cf76993c235c2 # v1 + uses: anthropics/claude-code-action@f669191d7d1e67f08a54b0c11cf5683a9a391951 # v1.0.49 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} From f369189f706ee0890a7cf371f7c4b82cb41bec3d Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Tue, 24 Mar 2026 01:01:15 +0100 Subject: [PATCH 11/49] github: switch claude-code-action to hauke/claude-code-action v1.0.77-fixed Use a patched fork while waiting for https://github.com/anthropics/claude-code-action/pull/963 to be merged and released upstream. Version v1.0.77 contains many improvments over 1.0.49 which was used before. It uses Sonnet 4.6 instead of Sonnet 4.5. Fixes: c0e75c9de6f2 ("github: add Claude code review workflow triggered by /claude comment") Co-Authored-By: Claude Sonnet 4.6 Signed-off-by: Hauke Mehrtens (cherry picked from commit ced84f697d1ed92e7a472fd86a333a991872062f) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 6146392135..aff24b8aef 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 1 - name: PR Review - uses: anthropics/claude-code-action@f669191d7d1e67f08a54b0c11cf5683a9a391951 # v1.0.49 + uses: hauke/claude-code-action@95d07da986168a9998e8e4713ec29b7c162b4dd9 # v1.0.77-fixed with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} From f5ba85d385c083aa67f4784390b6730340e41e56 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 11 Apr 2026 17:52:16 +0200 Subject: [PATCH 12/49] github: enable inline PR comments for Claude code review Allow Claude to post inline comments on specific lines when reviewing PRs via the /claude trigger phrase. Co-Authored-By: Claude Sonnet 4.6 Link: https://github.com/openwrt/openwrt/pull/22897 Signed-off-by: Hauke Mehrtens (cherry picked from commit 4517acedb55f74ba3b249bb2a4af4e4b45785ea0) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index aff24b8aef..3d7957420a 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -31,3 +31,5 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} trigger_phrase: "/claude" + claude_args: >- + --allowedTools "mcp__github_inline_comment__create_inline_comment" From f659da5dac46cb1951708325f68ca321eef74824 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 11 Apr 2026 18:06:40 +0200 Subject: [PATCH 13/49] github: prevent Claude from making commits during PR review The workflow already uses contents: read which prevents GitHub from accepting any push. The --disallowedTools setting adds a second layer by stopping Claude from even attempting git write operations. Co-Authored-By: Claude Sonnet 4.6 Link: https://github.com/openwrt/openwrt/pull/22897 Signed-off-by: Hauke Mehrtens (cherry picked from commit 05e111aa42fce08a70b0b69abae7c014896a6088) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 3d7957420a..6115d33428 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -33,3 +33,4 @@ jobs: trigger_phrase: "/claude" claude_args: >- --allowedTools "mcp__github_inline_comment__create_inline_comment" + --disallowedTools "Bash(git add:*),Bash(git commit:*),Bash(git rm:*),Bash(git push:*)" From 924a4ec92d46c4b89751a738eb0bd3f7cc34ed92 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 12 Apr 2026 00:04:33 +0200 Subject: [PATCH 14/49] github: allow grep in claude code review Allow Bash(grep:*) so Claude can search the codebase for context during PR reviews. Without this, grep calls using shell glob patterns were denied even though plain directory greps were allowed. Co-Authored-By: Claude Sonnet 4.6 Link: https://github.com/openwrt/openwrt/pull/22897 Signed-off-by: Hauke Mehrtens (cherry picked from commit a41cd892b7c5d0405fd352ced5099f8e3a6ccfa8) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 6115d33428..d53f085f22 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -32,5 +32,5 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} trigger_phrase: "/claude" claude_args: >- - --allowedTools "mcp__github_inline_comment__create_inline_comment" + --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(grep:*)" --disallowedTools "Bash(git add:*),Bash(git commit:*),Bash(git rm:*),Bash(git push:*)" From 3ccdb316c625fcc42468d84f479c4765b9774b58 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 11 Apr 2026 18:07:01 +0200 Subject: [PATCH 15/49] github: guide Claude to use inline comments and be concise Instruct Claude to prefer inline comments for file/line-specific findings and keep all feedback short and to the point. Co-Authored-By: Claude Sonnet 4.6 Link: https://github.com/openwrt/openwrt/pull/22897 Signed-off-by: Hauke Mehrtens (cherry picked from commit 34f5a169a3f81f7953480d73052633b4aa670b4f) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index d53f085f22..757743d7e5 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -34,3 +34,15 @@ jobs: claude_args: >- --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(grep:*)" --disallowedTools "Bash(git add:*),Bash(git commit:*),Bash(git rm:*),Bash(git push:*)" + env: + APPEND_SYSTEM_PROMPT: >- + IMPORTANT: For any issue tied to a specific file and line number, + you MUST call mcp__github_inline_comment__create_inline_comment to + post it as an inline comment on the diff. This is a separate + channel from mcp__github_comment__update_claude_comment and does + NOT violate the "only update your tracking comment" rule โ€” both + tools should be used. Do NOT put line-specific issues in the + top-level tracking comment. Only use the top-level tracking + comment for a brief overall verdict. Never describe or summarize + what the PR does. Only report actual problems, concerns, or + suggestions. If nothing is wrong, say so in one short sentence. From e0bd67ca6a51c08dfb8f046bd2a7cbd04fa28d54 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 11 Apr 2026 19:38:49 +0200 Subject: [PATCH 16/49] github: enable progress tracking and tag mode for Claude code review track_progress: true makes Claude post an initial comment immediately when triggered and update it with a checklist as it works, so reviewers can see what Claude is doing rather than waiting for a silent delay. It also forces tag mode on PR/issue comment events, which is the correct mode for interactive code review. Co-Authored-By: Claude Sonnet 4.6 Link: https://github.com/openwrt/openwrt/pull/22897 Signed-off-by: Hauke Mehrtens (cherry picked from commit 9f88a278999477ff03e13ac9212923b68c280148) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 757743d7e5..ba7dc5b893 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -31,6 +31,7 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} trigger_phrase: "/claude" + track_progress: true claude_args: >- --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(grep:*)" --disallowedTools "Bash(git add:*),Bash(git commit:*),Bash(git rm:*),Bash(git push:*)" From e62d8ae5f86bddb685ee49d976e2c134ac1feb00 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 11 Apr 2026 18:06:25 +0200 Subject: [PATCH 17/49] github: disable fix links in Claude code review Fix links open the Claude Code desktop app which is not useful in the OpenWrt contributor workflow. Co-Authored-By: Claude Sonnet 4.6 Link: https://github.com/openwrt/openwrt/pull/22897 Signed-off-by: Hauke Mehrtens (cherry picked from commit 31b9fce9941f2da94327701305042c0d68eea77f) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index ba7dc5b893..ea3297aa06 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -32,6 +32,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} trigger_phrase: "/claude" track_progress: true + include_fix_links: false claude_args: >- --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(grep:*)" --disallowedTools "Bash(git add:*),Bash(git commit:*),Bash(git rm:*),Bash(git push:*)" From ff3288c78d3ba6020ebedccefb361462b6d2e161 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 11 Apr 2026 18:44:23 +0200 Subject: [PATCH 18/49] github: update claude code action This is based on version 1.0.93 plus these commits: 0ca689a fix: fetch base branch to ensure correct PR-only diffs b7d533d fix: handle fork PRs by fetching via pull/N/head Link: https://github.com/openwrt/openwrt/pull/22897 Signed-off-by: Hauke Mehrtens (cherry picked from commit 746206df142413d2f1026982c1465ba9e7a71e89) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index ea3297aa06..150bd70393 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -26,7 +26,7 @@ jobs: fetch-depth: 1 - name: PR Review - uses: hauke/claude-code-action@95d07da986168a9998e8e4713ec29b7c162b4dd9 # v1.0.77-fixed + uses: hauke/claude-code-action@0ca689a0ca61147a6e7ee99f8453f2c73b8a6b40 # v1.0.93.2 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} From d45bec0b0c411205f60be6ca9e0ed9f309c75351 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 12 Apr 2026 17:33:22 +0200 Subject: [PATCH 19/49] github: github-release: update dependencies Update the dependencies to the latest version. The biggest change is the update to Node.js 24. Link: https://github.com/openwrt/openwrt/pull/22903 Signed-off-by: Hauke Mehrtens (cherry picked from commit 372e57f243456da71d1bb28df7b332d9296b76d9) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/github-release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 55d01f85cb..db10a0b742 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -15,10 +15,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v3 with: draft: true prerelease: true From bd0545f30152034f17b5ce97182d633ff642cebb Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 12 Apr 2026 17:34:13 +0200 Subject: [PATCH 20/49] github: labeler: Update dependencies Update to the most recent version. The biggest change is the switch to Node.js 24. Link: https://github.com/openwrt/openwrt/pull/22903 Signed-off-by: Hauke Mehrtens (cherry picked from commit 05b6a93173135ab86f82663523b531577f5e6062) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index ba35e8e1dd..513964de66 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -14,7 +14,7 @@ jobs: name: Pull Request Labeler runs-on: ubuntu-latest steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@v6 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' From 27ebdec580f16b9a316453ee7b9e0a2bb2b811a8 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 26 Apr 2026 16:37:12 +0200 Subject: [PATCH 21/49] github: add LLM PR review wrapper and rules Drop-in wrapper that calls the reusable LLM review workflows in openwrt/actions-shared-workflows. Triggers on pull_request_target (incl. PRs from forks), a nightly cron (03:00 UTC), and manual workflow_dispatch with a max_prs input override. A detect-kernels pre-step builds the extra_repos list at workflow runtime: it reads target/linux/generic/kernel-* in the base branch to derive gregkh/linux:v tags for each currently-targeted kernel, then appends u-boot/u-boot:master. The list updates automatically when kernel versions are bumped; the routine clones only the entries actually needed for a given diff. The bundled .github/llm-review-rules.md teaches the bot two project-specific deprecations to flag even when other in-tree files still use the legacy form: - LED label syntax `label = ":";` --> `color = ;` + `function = "";` - `mediatek,mtd-eeprom` for MAC sourcing --> `nvmem-cells` + `nvmem-cell-names = "mac-address";` Repository settings need LLM_ROUTINE_ID_PR / LLM_ROUTINE_TOKEN_PR and the *_NIGHTLY counterparts populated before the workflow can fire. See openwrt/actions-shared-workflows/docs/llm-review-setup.md for the full setup procedure. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23105 Signed-off-by: Hauke Mehrtens (cherry picked from commit af3cbd4013a2c8c82450e4a4b21a73d28ab4cda2) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/llm-review-rules.md | 17 ++++++ .github/workflows/llm-review.yml | 91 ++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 .github/llm-review-rules.md create mode 100644 .github/workflows/llm-review.yml diff --git a/.github/llm-review-rules.md b/.github/llm-review-rules.md new file mode 100644 index 0000000000..7accd80988 --- /dev/null +++ b/.github/llm-review-rules.md @@ -0,0 +1,17 @@ +# LLM review rules โ€” openwrt/openwrt + +Project-specific patterns to flag, even when other in-tree files still +use the deprecated form. The LLM review routine reads this at session +start. + +## Deprecated device-tree patterns + +- **LED label syntax.** `label = "green:status";` --> use + `color = ;` + `function = LED_FUNCTION_STATUS;` + (constants from `include/dt-bindings/leds/common.h`; pick the + `LED_COLOR_ID_` and `LED_FUNCTION_` matching the old + `:` string, falling back to `function = "";` + if no matching `LED_FUNCTION_*` constant exists). +- **MAC from MTD.** `mediatek,mtd-eeprom = <&factory 0xNNNN>;` --> use + `nvmem-cells = <&macaddr_factory_NN>;` + + `nvmem-cell-names = "mac-address";`. diff --git a/.github/workflows/llm-review.yml b/.github/workflows/llm-review.yml new file mode 100644 index 0000000000..f35c76c630 --- /dev/null +++ b/.github/workflows/llm-review.yml @@ -0,0 +1,91 @@ +name: LLM Review + +on: + pull_request_target: + types: [opened, reopened] + schedule: + - cron: '0 3 * * *' + workflow_dispatch: + inputs: + max_prs: + description: 'Max PRs to review in this nightly run' + required: false + type: number + default: 16 + +permissions: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || 'nightly' }} + cancel-in-progress: false + +jobs: + detect-kernels: + name: Detect kernel versions for extra_repos + if: github.repository_owner == 'openwrt' + runs-on: ubuntu-slim + permissions: + contents: read + outputs: + extra_repos: ${{ steps.detect.outputs.extra_repos }} + steps: + - name: Checkout kernel version files + uses: actions/checkout@v6 + with: + sparse-checkout: target/linux/generic + + - name: Build extra_repos list + id: detect + run: | + set -euo pipefail + tags=() + + # Linux stable tree, one tag per kernel-* version file. + for f in target/linux/generic/kernel-*; do + [ -f "$f" ] || continue + major=$(basename "$f" | sed 's/^kernel-//') + patch=$(awk -v k="LINUX_VERSION-$major" '$1==k{print $3; exit}' "$f") + [ -z "$patch" ] && continue + tags+=("gregkh/linux:v${major}${patch}") + done + + # U-Boot upstream tracked at master. + tags+=("u-boot/u-boot:master") + + # Other upstream/userspace projects whose code OpenWrt PRs + # often touch โ€” included as available references; the + # routine clones each only when relevant to the diff. + tags+=("https://thekelleys.org.uk/git/dnsmasq.git:master") + tags+=("https://git.w1.fi/hostap.git:main") + tags+=("mkj/dropbear:master") + tags+=("jow-/ucode:master") + tags+=("openwrt/netifd:master") + tags+=("openwrt/procd:main") + + extra=$(IFS=,; echo "${tags[*]:-}") + echo "Computed extra_repos: $extra" + echo "extra_repos=$extra" >> "$GITHUB_OUTPUT" + + pr-review: + if: github.event_name == 'pull_request_target' && github.repository_owner == 'openwrt' + needs: detect-kernels + permissions: {} + uses: openwrt/actions-shared-workflows/.github/workflows/reusable_llm-pr-review.yml@main + with: + routine_id: ${{ vars.LLM_ROUTINE_ID_PR }} + extra_repos: ${{ needs.detect-kernels.outputs.extra_repos }} + secrets: + llm_routine_token: ${{ secrets.LLM_ROUTINE_TOKEN_PR }} + + nightly: + if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository_owner == 'openwrt' + needs: detect-kernels + permissions: + pull-requests: read + uses: openwrt/actions-shared-workflows/.github/workflows/reusable_llm-nightly-digest.yml@main + with: + routine_id: ${{ vars.LLM_ROUTINE_ID_NIGHTLY }} + extra_repos: ${{ needs.detect-kernels.outputs.extra_repos }} + max_prs: ${{ fromJSON(inputs.max_prs || '16') }} + secrets: + llm_routine_token: ${{ secrets.LLM_ROUTINE_TOKEN_NIGHTLY }} From d9a0d02ef46d7f46b9aeafcbb15cf471f2cc7a65 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Fri, 1 May 2026 18:02:22 +0200 Subject: [PATCH 22/49] github: extend LLM review rules with three new categories Tightened from real bot reviews: - Patch regeneration: spell out which make ... refresh command to recommend for each patch directory class, so the bot stops suggesting git format-patch for quilt-managed patches. - Backports / cherry-picks: a backport's diff should match the upstream commit on main verbatim; flag only deviations and the missing (cherry picked from commit ) trailer, not pre-existing style issues. - New device support: require Hardware specification, Flash instructions, and MAC address layout sections in the commit message that introduces a new device. Two reference commits (986ca4c887f4, a2dcbd79a446) named so the bot can sample the expected shape. Co-Authored-By: Claude Opus 4.7 (1M context) Link: https://github.com/openwrt/openwrt/pull/23184 Signed-off-by: Hauke Mehrtens (cherry picked from commit 0bef7411885e9f6e4d4f4224ec4a94565396010f) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/llm-review-rules.md | 72 +++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/.github/llm-review-rules.md b/.github/llm-review-rules.md index 7accd80988..55fb96d384 100644 --- a/.github/llm-review-rules.md +++ b/.github/llm-review-rules.md @@ -15,3 +15,75 @@ start. - **MAC from MTD.** `mediatek,mtd-eeprom = <&factory 0xNNNN>;` --> use `nvmem-cells = <&macaddr_factory_NN>;` + `nvmem-cell-names = "mac-address";`. + +## Patch regeneration + +OpenWrt patches are quilt-managed and **not** refreshed with +`git format-patch`. When a patch's hunk headers, fuzz, or context +need to be regenerated, the project-specific commands are: + +- `target/linux//patches-/...` --> `make + target/linux/refresh` (with the matching target selected in + `.config`) +- `target/linux/generic/backport-/...`, + `target/linux/generic/pending-/...`, and + `target/linux/generic/hack-/...` --> same `make + target/linux/refresh`. Note these live under + `target/linux/generic/`, not under each `/`. +- `package///patches/...` --> `make + package//refresh` (e.g. `package/kernel/mac80211/patches/...` + --> `make package/mac80211/refresh`) + +If a patch's metadata is wrong, recommend the matching `make ... +refresh` command, not `git format-patch`. + +## New device support โ€” required commit-message sections + +A PR is adding support for a new device when it adds a new `.dts` / +`.dtsi` file under `target/linux//dts/` and a new +`define Device/_` block under any `.mk` file in +`target/linux//image/` (the block can live in +`.mk`, in a shared `common-.mk` / +`generic-.mk` include, or in another `.mk` in that +directory). The commit message introducing the device must contain +three sections, even if short: + +1. **Hardware specification** โ€” SoC, RAM (size + chip if known), + Flash (type + size + chip), WiFi (band + chains + chip), Ethernet + (port count + speed + switch chip), LEDs/Buttons (count + type), + UART (header location, pinout, baud rate), Power (voltage + + current). +2. **Flash instructions** โ€” at minimum one fully-described install + path: which image to use, how to put the device into a + flashable state (TFTP recovery, U-Boot menu, OEM web UI, + serial-console method), and a reverting-to-stock note when a + procedure exists. Verbatim button/IP/hostname strings, not + hand-wavy descriptions. +3. **MAC address layout** โ€” where each interface's MAC comes from + (factory partition + offset, board_data text, derived from + label, etc.), one line per interface (LAN/WAN/2.4G/5G/...). + Use `xx:xx:xx` for the unit-specific bytes. + +Reference well-formed examples (use `git show ` to inspect): + +- `986ca4c887f4088b6fbc703faa88884350e9274f` โ€” terse, well-structured. +- `a2dcbd79a4460617bd42151555448f8bca8ca7be` โ€” fuller, with LED layout + and notes; flash instructions cover U-Boot and OEM-dashboard paths. + +Flag missing or empty sections. Don't dictate exact wording โ€” both +references differ significantly in tone and length. The bar is +"could a stranger flash this device from the commit message alone?". +Don't apply this rule to backports, refactors, DTS-only fixes, or +PRs that only add a hardware variant of an existing device (e.g. +`DEVICE_ALT0_*` only) โ€” the original commit already carries the +spec. + +## Backports / cherry-picks + +PRs targeting `openwrt-NN.NN` branches or titled `[X.Y] ...` are +backports. Their diffs should match the upstream commit on `main` +verbatim. Code-style or design issues that already exist on the +upstream commit belong on a fix-to-main PR, not on the backport โ€” +flag only deviations introduced by the cherry-pick itself, plus +the missing `(cherry picked from commit )` trailer. +`git cherry-pick -x` adds the trailer automatically. From 266450df20e20f6d6711f9bf47263acba60a8f27 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 10 May 2026 16:27:26 +0200 Subject: [PATCH 23/49] github: add workflow_dispatch trigger to kernel and packages Allow the Build Kernel and Build all core packages workflows to be launched manually from the Actions tab. The shared workflow side detects workflow_dispatch and, for Build Kernel, rebuilds the full target/subtarget matrix including testing kernel versions, so a manual run can re-seed the s3 ccache when a queued push run got displaced from the concurrency queue by the next commit on main. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Hauke Mehrtens Link: https://github.com/openwrt/openwrt/pull/23283 (cherry picked from commit d9e6f9dce8ffcc037b89f3a35f4088dd19269ea0) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/kernel.yml | 1 + .github/workflows/packages.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/kernel.yml b/.github/workflows/kernel.yml index 43c610ab4f..d45ed6f0fc 100644 --- a/.github/workflows/kernel.yml +++ b/.github/workflows/kernel.yml @@ -19,6 +19,7 @@ on: - 'target/linux/**' branches-ignore: - master + workflow_dispatch: permissions: contents: read diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index f061c1cd37..f26b0ee94e 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -19,6 +19,7 @@ on: - 'toolchain/**' branches-ignore: - master + workflow_dispatch: permissions: contents: read From 9e560fd92a0f72265d9dba6f78c22826f4466d15 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 10 May 2026 16:34:54 +0200 Subject: [PATCH 24/49] github: separate concurrency group per event for kernel and packages Manual workflow_dispatch and push share the same ref on main, so they previously landed in the same concurrency group 'Build Kernel-refs/heads/main' (and the equivalent for the packages workflow). With cancel-in-progress: false for non-PR events, only one run can be pending per group: a newer queued run cancels the older pending one. That made the manual trigger unreliable in both directions. A push landing while a dispatch was queued displaced the dispatch (so the ccache reseed never ran), and dispatching while a push was queued displaced the push (so the legitimate per-commit build was lost). Adding github.event_name to the concurrency group puts pushes, dispatches, and pull_requests in separate buckets on the same ref, so they no longer compete with each other. Co-Authored-By: Claude Opus 4.7 (1M context) Signed-off-by: Hauke Mehrtens Link: https://github.com/openwrt/openwrt/pull/23283 (cherry picked from commit 3c7a1418c14934787c37f4a5a6dfc50c5fc5c0c1) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/kernel.yml | 2 +- .github/workflows/packages.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/kernel.yml b/.github/workflows/kernel.yml index d45ed6f0fc..c57b0e470a 100644 --- a/.github/workflows/kernel.yml +++ b/.github/workflows/kernel.yml @@ -25,7 +25,7 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: diff --git a/.github/workflows/packages.yml b/.github/workflows/packages.yml index f26b0ee94e..6ceb3e46cc 100644 --- a/.github/workflows/packages.yml +++ b/.github/workflows/packages.yml @@ -25,7 +25,7 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: From 12de37240c9025565476b6130aa4262cbf81ace1 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 21 May 2026 21:10:15 +0200 Subject: [PATCH 25/49] github: drop the claude-code-review workflow Delete .github/workflows/claude-code-review.yml. It is superseded by the LLM review action, which runs automatically and uses the Claude routine API instead of the much more expensive Claude API. Link: https://github.com/openwrt/openwrt/pull/23474 Signed-off-by: Hauke Mehrtens (cherry picked from commit b36309575a2570793b40a75659952e680de58d3d) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/claude-code-review.yml | 50 ------------------------ 1 file changed, 50 deletions(-) delete mode 100644 .github/workflows/claude-code-review.yml diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index 150bd70393..0000000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Claude Code Review - -on: - issue_comment: - types: [created] - -permissions: - contents: read - pull-requests: write - actions: read - -jobs: - code-review: - name: Claude Code Review - # Only run on PR comments containing "/claude" from users with write access - if: >- - github.event.issue.pull_request && - contains(github.event.comment.body, '/claude') && - contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), - github.event.comment.author_association) - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 1 - - - name: PR Review - uses: hauke/claude-code-action@0ca689a0ca61147a6e7ee99f8453f2c73b8a6b40 # v1.0.93.2 - with: - anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - github_token: ${{ secrets.GITHUB_TOKEN }} - trigger_phrase: "/claude" - track_progress: true - include_fix_links: false - claude_args: >- - --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(grep:*)" - --disallowedTools "Bash(git add:*),Bash(git commit:*),Bash(git rm:*),Bash(git push:*)" - env: - APPEND_SYSTEM_PROMPT: >- - IMPORTANT: For any issue tied to a specific file and line number, - you MUST call mcp__github_inline_comment__create_inline_comment to - post it as an inline comment on the diff. This is a separate - channel from mcp__github_comment__update_claude_comment and does - NOT violate the "only update your tracking comment" rule โ€” both - tools should be used. Do NOT put line-specific issues in the - top-level tracking comment. Only use the top-level tracking - comment for a brief overall verdict. Never describe or summarize - what the PR does. Only report actual problems, concerns, or - suggestions. If nothing is wrong, say so in one short sentence. From 61f9378f615fae3ea165dfb80c3275938d4fc09f Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 21 May 2026 21:11:54 +0200 Subject: [PATCH 26/49] github: LLM-review: stop reviewing new PRs automatically Drop the pull_request_target trigger so the LLM review no longer runs on opened/reopened PRs. We are limited to 15 Claude routine runs per day, and the automatic per-PR trigger exhausted that budget on several days, starving the nightly digest. The nightly schedule and manual workflow_dispatch triggers remain. Link: https://github.com/openwrt/openwrt/pull/23474 Signed-off-by: Hauke Mehrtens (cherry picked from commit 4871040d5d598e9156aacd99381f7aa7c57393e1) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/llm-review.yml | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/.github/workflows/llm-review.yml b/.github/workflows/llm-review.yml index f35c76c630..faf5330117 100644 --- a/.github/workflows/llm-review.yml +++ b/.github/workflows/llm-review.yml @@ -1,8 +1,6 @@ name: LLM Review on: - pull_request_target: - types: [opened, reopened] schedule: - cron: '0 3 * * *' workflow_dispatch: @@ -16,7 +14,7 @@ on: permissions: {} concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || 'nightly' }} + group: ${{ github.workflow }}-nightly cancel-in-progress: false jobs: @@ -66,17 +64,6 @@ jobs: echo "Computed extra_repos: $extra" echo "extra_repos=$extra" >> "$GITHUB_OUTPUT" - pr-review: - if: github.event_name == 'pull_request_target' && github.repository_owner == 'openwrt' - needs: detect-kernels - permissions: {} - uses: openwrt/actions-shared-workflows/.github/workflows/reusable_llm-pr-review.yml@main - with: - routine_id: ${{ vars.LLM_ROUTINE_ID_PR }} - extra_repos: ${{ needs.detect-kernels.outputs.extra_repos }} - secrets: - llm_routine_token: ${{ secrets.LLM_ROUTINE_TOKEN_PR }} - nightly: if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && github.repository_owner == 'openwrt' needs: detect-kernels From 99a10e8f757528082c169b0bcee0122f8eb2f1a1 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Thu, 21 May 2026 21:15:59 +0200 Subject: [PATCH 27/49] github: LLM-review: run the digest twice a day Change the schedule from once a day to twice a day (03:00 and 15:00 UTC) and raise the default max_prs from 16 to 24, so more open PRs get covered now that the per-PR trigger has been removed. Link: https://github.com/openwrt/openwrt/pull/23474 Signed-off-by: Hauke Mehrtens (cherry picked from commit 49b5093679bd029846002dba859ab3a1933baa86) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/workflows/llm-review.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/llm-review.yml b/.github/workflows/llm-review.yml index faf5330117..5a5b233bae 100644 --- a/.github/workflows/llm-review.yml +++ b/.github/workflows/llm-review.yml @@ -2,14 +2,14 @@ name: LLM Review on: schedule: - - cron: '0 3 * * *' + - cron: '0 3,15 * * *' workflow_dispatch: inputs: max_prs: description: 'Max PRs to review in this nightly run' required: false type: number - default: 16 + default: 24 permissions: {} @@ -73,6 +73,6 @@ jobs: with: routine_id: ${{ vars.LLM_ROUTINE_ID_NIGHTLY }} extra_repos: ${{ needs.detect-kernels.outputs.extra_repos }} - max_prs: ${{ fromJSON(inputs.max_prs || '16') }} + max_prs: ${{ fromJSON(inputs.max_prs || '24') }} secrets: llm_routine_token: ${{ secrets.LLM_ROUTINE_TOKEN_NIGHTLY }} From 87c745fc03b68540f2b2ec3be85c3232559257e2 Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Thu, 16 Jul 2026 19:57:35 +0200 Subject: [PATCH 28/49] CI: migrate to webhook bot and simplify labeler This commit migrates the repository validation to a webhook-based bot instead of the GitHub Actions workflow (.github/workflows/formal.yml). Since the bot handles stable branch labeling automatically (via the "branch_labeling: true" configuration option), the redundant branch check and labeling steps are removed from .github/workflows/labeler.yml. The following configuration choices are applied in formalities.json: - check_space_after_assignment: false (Disabled temporarily to catch any issues since it is a newly implemented check) - check_makefile_indentation: false (Disabled temporarily to catch any issues since it is a new feature) - check_patch_headers: 'warning' (Changed to warning instead of hard error since many legacy patches lack headers) - enable_stale_bot: false (Disabled for now) Other configurations can be adjusted in formalities.json based on the openwrt-bot-worker repository [1]. [1] https://github.com/openwrt/openwrt-bot-worker Signed-off-by: Josef Schlehofer Link: https://github.com/openwrt/openwrt/pull/24256 (cherry picked from commit 9146f931b62e9b20df84566187a837b904df1e2f) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/formalities.json | 28 ++++++++++++++++++++++++++++ .github/workflows/formal.yml | 18 ------------------ .github/workflows/labeler.yml | 14 -------------- 3 files changed, 28 insertions(+), 32 deletions(-) create mode 100644 .github/formalities.json delete mode 100644 .github/workflows/formal.yml diff --git a/.github/formalities.json b/.github/formalities.json new file mode 100644 index 0000000000..cdbfd200e4 --- /dev/null +++ b/.github/formalities.json @@ -0,0 +1,28 @@ +{ + "check_branch": true, + "check_merge_commits": true, + "check_noreply_email": true, + "check_signoff": true, + "check_signature": true, + "allow_autosquash": true, + "enable_comments": true, + "max_subject_len_soft": 60, + "max_subject_len_hard": 80, + "max_body_line_len": 100, + "warn_duplicate_body": true, + "warn_generic_subjects": true, + "require_release_notes": true, + "check_pkg_version": true, + "check_crlf": true, + "add_package_label": true, + "drop_package_label": true, + "branch_labeling": true, + "check_openwrt_meta": true, + "check_conffiles": true, + "check_patch_headers": "warning", + "check_space_after_assignment": false, + "check_makefile_indentation": false, + "check_pkg_release": "warning", + "require_linked_github_account": true, + "enable_stale_bot": false +} diff --git a/.github/workflows/formal.yml b/.github/workflows/formal.yml deleted file mode 100644 index d57b17a562..0000000000 --- a/.github/workflows/formal.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Test Formalities - -on: - pull_request: - -permissions: - contents: read - pull-requests: write - -jobs: - build: - name: Test Formalities - uses: openwrt/actions-shared-workflows/.github/workflows/formal.yml@main - # with: - # # Post formality check summaries to the PR. - # # Repo's permissions need to be updated for actions to modify PRs: - # # https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment - # post_comment: true diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 513964de66..b64d3e4a2b 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,17 +17,3 @@ jobs: - uses: actions/labeler@v6 with: repo-token: '${{ secrets.GITHUB_TOKEN }}' - - - name: Check Branch - id: check-branch - run: | - if echo "${{ github.base_ref }}" | grep -q -E 'openwrt-[0-9][0-9]\.[0-9][0-9]'; then - echo "release-tag=$(echo ${{ github.base_ref }} | sed 's/openwrt-/release\//')" >> $GITHUB_OUTPUT - fi - - - uses: buildsville/add-remove-label@v2.0.1 - if: ${{ steps.check-branch.outputs.release-tag }} - with: - token: ${{secrets.GITHUB_TOKEN}} - labels: ${{ steps.check-branch.outputs.release-tag }} - type: add From 557a8e1b8372c7c733d9c27fc90ca53f3aa10d3b Mon Sep 17 00:00:00 2001 From: Josef Schlehofer Date: Thu, 16 Jul 2026 23:52:35 +0200 Subject: [PATCH 29/49] CI: remove labeler workflow and enable webhook-based labeling Remove the .github/workflows/labeler.yml workflow and enable the "enable_labeler_yml" option in .github/formalities.json to let the webhook bot handle pull request labeling. This avoids running on GitHub Actions, so it is faster and uses fewer resources. It uses the existing .github/labeler.yml for labels. Signed-off-by: Josef Schlehofer Link: https://github.com/openwrt/openwrt/pull/24256 (cherry picked from commit 4f2dc5cc6497a6cef0a43bdefde522e734c1f40d) Link: https://github.com/openwrt/openwrt/pull/24318 Signed-off-by: Christian Marangi --- .github/formalities.json | 3 ++- .github/workflows/labeler.yml | 19 ------------------- 2 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 .github/workflows/labeler.yml diff --git a/.github/formalities.json b/.github/formalities.json index cdbfd200e4..1face8ed12 100644 --- a/.github/formalities.json +++ b/.github/formalities.json @@ -24,5 +24,6 @@ "check_makefile_indentation": false, "check_pkg_release": "warning", "require_linked_github_account": true, - "enable_stale_bot": false + "enable_stale_bot": false, + "enable_labeler_yml": true } diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index b64d3e4a2b..0000000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: 'Pull Request Labeler' -on: - - pull_request_target - -permissions: - contents: read - -jobs: - labeler: - permissions: - contents: read # to determine modified files (actions/labeler) - pull-requests: write # to add labels to PRs (actions/labeler) - - name: Pull Request Labeler - runs-on: ubuntu-latest - steps: - - uses: actions/labeler@v6 - with: - repo-token: '${{ secrets.GITHUB_TOKEN }}' From 8b4df0b66da3bbf8c73ddb9fd7881c140637411b Mon Sep 17 00:00:00 2001 From: Mieczyslaw Nalewaj Date: Wed, 15 Jul 2026 18:58:21 +0200 Subject: [PATCH 30/49] ledtrig-network: default missing family to WLAN on non-DT LEDs For LEDs that have no network trigger configuration in Device Tree (no mode or family properties), net_activate() infers the family from the LED name or function string. If that inference fails, the trigger previously returned -EINVAL and refused to activate. This is too strict for manual activation via sysfs (e.g. echo "network" > /sys/class/leds//trigger) on LEDs that were not declared for the network trigger in DT. In that case the user expects the trigger to load with sensible defaults. Change the non-DT activation path to fall back to NET_TRIG_WLAN instead of failing, while keeping the condition visible via pr_warn(). Device Tree paths with dt_mode or dt_family are left unchanged and continue to fail on invalid parameters Signed-off-by: Mieczyslaw Nalewaj Link: https://github.com/openwrt/openwrt/pull/24235 Signed-off-by: Jonas Jelonek (cherry-picked from f31b65a870f7b8b133bccf879cb3c5fc909d5d86) Signed-off-by: Jonas Jelonek --- .../generic/files/drivers/leds/trigger/ledtrig-network.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/linux/generic/files/drivers/leds/trigger/ledtrig-network.c b/target/linux/generic/files/drivers/leds/trigger/ledtrig-network.c index e2e60c3a26..e40497dd8b 100644 --- a/target/linux/generic/files/drivers/leds/trigger/ledtrig-network.c +++ b/target/linux/generic/files/drivers/leds/trigger/ledtrig-network.c @@ -1070,9 +1070,9 @@ static int net_activate(struct led_classdev *led_cdev) */ parsed = parse_family_token(fn, &online); if (parsed < 0) { - pr_info("network: unknown function '%s' for LED %s\n", + pr_warn("network: cannot infer family from '%s' for LED %s, defaulting to 'wlan'\n", fn ?: "", name); - return -EINVAL; + parsed = NET_TRIG_WLAN; } if (online) { From 0456d3d1c209f4e7b3ccb2a7cb0cc48606da26d9 Mon Sep 17 00:00:00 2001 From: rodriguezst Date: Sat, 13 Jun 2026 14:14:17 +0000 Subject: [PATCH 31/49] ramips: mt76x8: add support for Cudy LT500 Outdoor v1 Hardware: - SoC: MediaTek MT7628AN - Flash: 16 MiB SPI NOR - RAM: 128 MiB DDR2 - WLAN: 2.4 GHz (MT7628AN, 11n), 5 GHz (MediaTek MT7615E, 11ac) - Ethernet: 1x10/100 Mbps LAN - Buttons: 1 Reset button - LEDs: 8x Green - LTE: internal USB-connected modem Quectel EC200A - Serial Console: unpopulated header 115200 8n1 MAC addresses: +---------+-------------------+-----------+ | | MAC | Algorithm | +---------+-------------------+-----------+ | LAN | 80:af:ca:xx:xx:x0 | label | | WLAN 2g | 80:af:ca:xx:xx:x0 | label | | WLAN 5g | 80:af:ca:xx:xx:x2 | +2 | +---------+-------------------+-----------+ Migration to OpenWrt: - Download the RSA signed intermediate firmware: `openwrt-ramips-mt76x8-cudy_lt500-outdoor-v1-squashfs-flash.bin` - Connect computer to LAN and flash the intermediate firmware via OEM web interface - OpenWrt is now accessible via 192.168.1.1 Revert back to OEM firmware: - Press the reset button while powering on the device - Connect the LAN port to the PC - Open 192.168.1.1 in a browser and use the wizard to upload and flash OEM firmware image - When recovery process is done, OEM firmware is accessible via 192.168.10.1 again Signed-off-by: rodriguezst Link: https://github.com/openwrt/openwrt/pull/23752 Signed-off-by: Hauke Mehrtens (cherry picked from commit b5ea3abd60238804303295862a5116c123c40651) Link: https://github.com/openwrt/openwrt/pull/24213 Signed-off-by: Jonas Jelonek --- .../dts/mt7628an_cudy_lt500-outdoor-v1.dts | 231 ++++++++++++++++++ target/linux/ramips/image/mt76x8.mk | 12 + .../mt76x8/base-files/etc/board.d/01_leds | 4 + .../mt76x8/base-files/etc/board.d/02_network | 7 + 4 files changed, 254 insertions(+) create mode 100644 target/linux/ramips/dts/mt7628an_cudy_lt500-outdoor-v1.dts diff --git a/target/linux/ramips/dts/mt7628an_cudy_lt500-outdoor-v1.dts b/target/linux/ramips/dts/mt7628an_cudy_lt500-outdoor-v1.dts new file mode 100644 index 0000000000..8f09a4e45c --- /dev/null +++ b/target/linux/ramips/dts/mt7628an_cudy_lt500-outdoor-v1.dts @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: GPL-2.0-or-later OR MIT + +#include "mt7628an.dtsi" + +#include +#include +#include + +/ { + compatible = "cudy,lt500-outdoor-v1", "mediatek,mt7628an-soc"; + model = "Cudy LT500 Outdoor v1"; + + aliases { + led-boot = &led_status; + led-running = &led_status; + led-failsafe = &led_status; + led-upgrade = &led_status; + label-mac-device = ðernet; + }; + + chosen { + bootargs = "console=ttyS0,115200"; + }; + + gpio-keys { + compatible = "gpio-keys"; + + reset { + label = "reset"; + linux,code = ; + gpios = <&gpio 11 GPIO_ACTIVE_LOW>; + }; + }; + + leds { + compatible = "gpio-leds"; + + led_status: status { + gpios = <&gpio 4 GPIO_ACTIVE_LOW>; + color = ; + function = LED_FUNCTION_STATUS; + }; + + internet { + gpios = <&gpio 40 GPIO_ACTIVE_HIGH>; + color = ; + function = LED_FUNCTION_WAN_ONLINE; + }; + + lan { + gpios = <&gpio 42 GPIO_ACTIVE_LOW>; + color = ; + function = LED_FUNCTION_LAN; + }; + + wifi2g { + gpios = <&gpio 0 GPIO_ACTIVE_LOW>; + color = ; + function = LED_FUNCTION_WLAN_2GHZ; + linux,default-trigger = "phy0tpt"; + }; + + wifi5g { + gpios = <&gpio 43 GPIO_ACTIVE_LOW>; + color = ; + function = LED_FUNCTION_WLAN_5GHZ; + linux,default-trigger = "phy1tpt"; + }; + + signal1 { + gpios = <&gpio 44 GPIO_ACTIVE_LOW>; + label = "green:signal1"; + }; + + signal2 { + gpios = <&gpio 41 GPIO_ACTIVE_LOW>; + label = "green:signal2"; + }; + + signal3 { + gpios = <&gpio 39 GPIO_ACTIVE_LOW>; + label = "green:signal3"; + }; + }; + + gpio-export { + compatible = "gpio-export"; + + pwr { + gpio-export,name = "pwr"; + gpio-export,output = <0>; + gpios = <&gpio 38 GPIO_ACTIVE_HIGH>; + }; + + usb4g { + gpio-export,name = "4g"; + gpio-export,output = <0>; + gpios = <&gpio 3 GPIO_ACTIVE_HIGH>; + }; + }; +}; + +&spi0 { + status = "okay"; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <40000000>; + + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + partition@0 { + label = "u-boot"; + reg = <0x0 0x30000>; + read-only; + }; + + partition@30000 { + label = "u-boot-env"; + reg = <0x30000 0x10000>; + read-only; + }; + + factory: partition@40000 { + label = "factory"; + reg = <0x40000 0x10000>; + read-only; + + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + eeprom_factory_0: eeprom@0 { + reg = <0x0 0x400>; + }; + + eeprom_factory_8000: eeprom@8000 { + reg = <0x8000 0x4da8>; + }; + }; + }; + + partition@fd0000 { + label = "debug"; + reg = <0xfd0000 0x10000>; + read-only; + }; + + partition@fe0000 { + label = "backup"; + reg = <0xfe0000 0x10000>; + read-only; + }; + + bdinfo: partition@ff0000 { + label = "bdinfo"; + reg = <0xff0000 0x10000>; + read-only; + + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + macaddr_bdinfo_de00: macaddr@de00 { + compatible = "mac-base"; + reg = <0xde00 0x6>; + #nvmem-cell-cells = <1>; + }; + }; + }; + + partition@50000 { + compatible = "denx,uimage"; + label = "firmware"; + reg = <0x50000 0xf80000>; + }; + }; + }; +}; + +&state_default { + gpio { + groups = "refclk", "wled_an", "gpio", "spi cs1"; + function = "gpio"; + }; +}; + +&pcie { + status = "okay"; +}; + +&pcie0 { + wifi@0,0 { + compatible = "mediatek,mt76"; + reg = <0x0000 0 0 0 0>; + nvmem-cells = <&eeprom_factory_8000>, <&macaddr_bdinfo_de00 2>; + nvmem-cell-names = "eeprom", "mac-address"; + ieee80211-freq-limit = <5000000 6000000>; + }; +}; + +&wmac { + status = "okay"; + + nvmem-cells = <&eeprom_factory_0>, <&macaddr_bdinfo_de00 0>; + nvmem-cell-names = "eeprom", "mac-address"; +}; + +ðernet { + nvmem-cells = <&macaddr_bdinfo_de00 0>; + nvmem-cell-names = "mac-address"; +}; + +&ehci { + status = "okay"; +}; + +&ohci { + status = "okay"; +}; + +&esw { + mediatek,portmap = <0x3d>; + mediatek,portdisable = <0x3e>; +}; diff --git a/target/linux/ramips/image/mt76x8.mk b/target/linux/ramips/image/mt76x8.mk index 96b3aeb1b8..a079a216a8 100644 --- a/target/linux/ramips/image/mt76x8.mk +++ b/target/linux/ramips/image/mt76x8.mk @@ -210,6 +210,18 @@ define Device/cudy_lt300-v3 endef TARGET_DEVICES += cudy_lt300-v3 +define Device/cudy_lt500-outdoor-v1 + IMAGE_SIZE := 15872k + DEVICE_VENDOR := Cudy + DEVICE_MODEL := LT500 Outdoor + DEVICE_VARIANT := v1 + DEVICE_PACKAGES := kmod-mt7615e kmod-mt7663-firmware-ap kmod-usb2 \ + kmod-usb-ohci kmod-usb-net-cdc-ether kmod-usb-serial-option + UIMAGE_NAME := R35 + SUPPORTED_DEVICES += R35 +endef +TARGET_DEVICES += cudy_lt500-outdoor-v1 + define Device/cudy_m1200-v1 IMAGE_SIZE := 15872k DEVICE_VENDOR := Cudy diff --git a/target/linux/ramips/mt76x8/base-files/etc/board.d/01_leds b/target/linux/ramips/mt76x8/base-files/etc/board.d/01_leds index faee46c06e..8e4c85ef62 100644 --- a/target/linux/ramips/mt76x8/base-files/etc/board.d/01_leds +++ b/target/linux/ramips/mt76x8/base-files/etc/board.d/01_leds @@ -117,6 +117,10 @@ tplink,archer-mr200-v6) ucidef_set_led_netdev "lan" "lan" "white:lan" "eth0" ucidef_set_led_netdev "wan" "wan" "white:wan" "wwan0" ;; +cudy,lt500-outdoor-v1) + ucidef_set_led_switch "lan" "lan" "green:lan" "switch0" "0x01" + ucidef_set_led_netdev "internet" "internet" "green:wan-online" "usb0" + ;; cudy,re1200-outdoor-v1|\ mercusys,mb130-4g-v1|\ tplink,re200-v2|\ diff --git a/target/linux/ramips/mt76x8/base-files/etc/board.d/02_network b/target/linux/ramips/mt76x8/base-files/etc/board.d/02_network index fbd21f5795..c25eb3db33 100644 --- a/target/linux/ramips/mt76x8/base-files/etc/board.d/02_network +++ b/target/linux/ramips/mt76x8/base-files/etc/board.d/02_network @@ -204,6 +204,13 @@ ramips_setup_interfaces() "0:lan" "1:lan" "2:lan" "3:lan" "6t@eth0" ucidef_set_interface "wan" device "/dev/cdc-wdm0" protocol "qmi" ;; + cudy,lt500-outdoor-v1) + ucidef_add_switch "switch0" \ + "0:lan" "6@eth0" + ucidef_set_interface "wwan" device "usb0" protocol "dhcp" + uci add_list firewall.@zone[1].network='wwan' + uci add_list firewall.@zone[1].network='wwan6' + ;; cudy,re1200-outdoor-v1|\ tplink,tl-mr3020-v3) ucidef_add_switch "switch0" \ From 8450f3e69adb91ea50272fd7e1175383cdbe436c Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 02:36:13 +0200 Subject: [PATCH 32/49] wifi-scripts: ucode: do not leak SAE options onto non-SAE BSSes sae_require_mfp and sae_pwe are SAE-specific knobs but iface_auth_type() set them on every auth type that requires PMF (sae, owe, eap2, eap192, dpp) and on both PMF-optional transition modes (psk-sae, eap-eap2). hostapd silently ignores the stray settings on non-SAE BSSes, but they clutter the generated configuration and make it harder to tell at a glance which knobs actually apply. Split the grouping: keep ieee80211w (and rsn_override_mfp for transition modes) where it was, and move sae_require_mfp / sae_pwe into a separate check that only fires for the two auth types that actually run SAE (sae and psk-sae). No functional change on the air. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 9aeea77b8d36ce6ac5db1b8a6fae654f86322d4e) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 27ef4e2ac3..234e8549ed 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -82,17 +82,16 @@ function iface_accounting_server(config) { } function iface_auth_type(config) { - if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ]) { + if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ]) config.ieee80211w = 2; - config.sae_require_mfp = 1; - if (!config.ppsk) - set_default(config, 'sae_pwe', 2); - } if (config.auth_type in [ 'psk-sae', 'eap-eap2' ]) { set_default(config, 'ieee80211w', 1); if (config.rsn_override) config.rsn_override_mfp = 2; + } + + if (config.auth_type in [ 'sae', 'psk-sae' ]) { config.sae_require_mfp = 1; if (!config.ppsk) set_default(config, 'sae_pwe', 2); From cbc80966ffece05f30198a0c30e438d8a923ac2a Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 22:26:59 +0200 Subject: [PATCH 33/49] wifi-scripts: ucode: drop the generic rsn_override UCI knob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rsn_override UCI number was scaffolding that let a Transition Mode BSS (sae-mixed, wpa3-mixed) automatically overlay a WPA3 Compatibility-Mode-like layout: WPA3 AKMs were moved from the main RSNE into RSNOE/RSNO2E, and with rsn_override=2 the main RSNE even dropped SAE entirely to placate clients that refuse to associate to a mixed AKM list. This layout does not match any mode defined in the WPA3 and Wi-Fi Enhanced Open Deployment and Implementation Guide v1.1: Transition Mode (Table 5) advertises the full AKM list in a single RSNE, and Compatibility Mode (ยง2.4, Tables 6 and 7) requires a specific combination of RSNE, RSNOE and RSNO2E contents that the knob cannot express. In practice it also triggers interop failures: Pixel 10 phones refuse to associate to a Transition-Mode BSS whose SAE-EXT-KEY AKM has been shoved into RSNO2E by this scaffolding, even though the same BSS works fine when the full AKM list stays in the main RSNE. Keep the generated configuration honest by removing the knob; the RSN override plumbing stays in place for a future caller that sets the override fields explicitly. SAE-EXT-KEY advertisement will be added back in a later commit via a dedicated sae_ext_key path that places the AKM where the Deployment Guide actually requires it. Drop the rsn_override schema entry and every wifi-scripts path that read it: * parse_encryption no longer diverts the WPA3 pairwise cipher into rsn_override_pairwise. * wpa_key_mgmt no longer mirrors WPA-EAP into rsn_override_key_mgmt, moves SAE/SAE-EXT-KEY into the override for psk-sae, or drops the main RSNE AKM list when rsn_override > 1. * generate() no longer back-fills missing rsn_override_* fields from the main RSNE or duplicates the override element into an MLO-gated RSNO2E. The RSN override elements are now emitted only when each of (rsn_override_key_mgmt, rsn_override_pairwise, rsn_override_mfp) -- and their _2 counterparts -- has been populated explicitly, which keeps the machinery from firing on transition modes where it was never spec-compliant. Fixes: https://github.com/openwrt/openwrt/issues/21486 Fixes: https://github.com/openwrt/openwrt/issues/22200 Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 588330e92b307879e342ce2aef5547a94c6388af) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 5 ---- .../files-ucode/usr/share/ucode/wifi/ap.uc | 28 ++++++------------- .../files-ucode/usr/share/ucode/wifi/iface.uc | 26 +---------------- 3 files changed, 10 insertions(+), 49 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 45a409f26e..52f54f667b 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -962,11 +962,6 @@ "rsn_preauth": { "type": "boolean" }, - "rsn_override": { - "type": "number", - "description": "Use RSNE override IE WPA3 compatibility (0: disabled, 1: enabled, 2:force WPA2 for older devices)", - "default": 1 - }, "sae_password_file": { "description": "External file containing VLAN SAE MAC address triplets", "type": "string" diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 234e8549ed..3f29593a29 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -85,11 +85,8 @@ function iface_auth_type(config) { if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ]) config.ieee80211w = 2; - if (config.auth_type in [ 'psk-sae', 'eap-eap2' ]) { + if (config.auth_type in [ 'psk-sae', 'eap-eap2' ]) set_default(config, 'ieee80211w', 1); - if (config.rsn_override) - config.rsn_override_mfp = 2; - } if (config.auth_type in [ 'sae', 'psk-sae' ]) { config.sae_require_mfp = 1; @@ -514,27 +511,20 @@ export function generate(interface, data, config, vlans, stas, phy_features) { 'wpa_key_mgmt', ]); - if (config.rsn_override_key_mgmt || config.rsn_override_pairwise) { - config.rsn_override_mfp ??= config.ieee80211w; - config.rsn_override_key_mgmt ??= config.wpa_key_mgmt; - config.rsn_override_pairwise ??= config.wpa_pairwise; + if (config.rsn_override_key_mgmt && config.rsn_override_pairwise && config.rsn_override_mfp) { append_vars(config, [ 'rsn_override_key_mgmt', 'rsn_override_pairwise', 'rsn_override_mfp' ]); + } - if (config.mlo) { - config.rsn_override_mfp_2 ??= config.rsn_override_mfp; - config.rsn_override_key_mgmt_2 ??= config.rsn_override_key_mgmt; - config.rsn_override_pairwise_2 ??= config.rsn_override_pairwise; - - append_vars(config, [ - 'rsn_override_key_mgmt_2', - 'rsn_override_pairwise_2', - 'rsn_override_mfp_2' - ]); - } + if (config.rsn_override_key_mgmt_2 && config.rsn_override_pairwise_2 && config.rsn_override_mfp_2) { + append_vars(config, [ + 'rsn_override_key_mgmt_2', + 'rsn_override_pairwise_2', + 'rsn_override_mfp_2' + ]); } /* raw options */ diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index 5b7b14b6ff..1db4f14521 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -112,10 +112,7 @@ export function parse_encryption(config, dev_config) { if (!wpa3_pairwise) break; - if (config.rsn_override && wpa3_pairwise != config.wpa_pairwise) - config.rsn_override_pairwise = wpa3_pairwise; - else - config.wpa_pairwise = wpa3_pairwise; + config.wpa_pairwise = wpa3_pairwise; break; } @@ -154,9 +151,6 @@ export function wpa_key_mgmt(config) { if (config.ieee80211r) append_value(config, 'wpa_key_mgmt', 'FT-EAP'); - if (config.rsn_override) - config.rsn_override_key_mgmt = config.wpa_key_mgmt; - append_value(config, 'wpa_key_mgmt', 'WPA-EAP'); break; @@ -177,17 +171,6 @@ export function wpa_key_mgmt(config) { if (config.ieee80211r) append_value(config, 'wpa_key_mgmt', 'FT-SAE'); - if (config.rsn_override) { - config.rsn_override_key_mgmt = config.wpa_key_mgmt; - - append_value(config, 'rsn_override_key_mgmt_2', 'SAE-EXT-KEY'); - if (config.ieee80211r) - append_value(config, 'rsn_override_key_mgmt_2', 'FT-SAE-EXT-KEY'); - } - - if (config.rsn_override > 1) - delete config.wpa_key_mgmt; - append_value(config, 'wpa_key_mgmt', 'WPA-PSK'); if (config.ieee80211w) append_value(config, 'wpa_key_mgmt', 'WPA-PSK-SHA256'); @@ -214,13 +197,6 @@ export function wpa_key_mgmt(config) { append_value(config, 'wpa_key_mgmt', 'FILS-SHA256'); if (config.ieee80211r) append_value(config, 'wpa_key_mgmt', 'FT-FILS-SHA256'); - - if (!config.rsn_override_key_mgmt) - break; - - append_value(config, 'rsn_override_key_mgmt', 'FILS-SHA256'); - if (config.ieee80211r) - append_value(config, 'rsn_override_key_mgmt', 'FT-FILS-SHA256'); break; } } From c6074f6bb12d653ac26a9a5a69bb162054824490 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 22:26:59 +0200 Subject: [PATCH 34/49] wifi-scripts: ucode: simplify wpa_pairwise default selection parse_encryption() stashed a preliminary wpa_pairwise value in a local wpa3_pairwise variable, cleared it per auth_type, then let a switch default either copy it back or special-case wpa3-192. The result was three separate places where wpa_pairwise was clobbered and behavior that was awkward to trace when the explicit cipher suffix (encryption[1]) and the auth_type disagreed. Replace the scaffolding with a single block at the end of parse_encryption() that only assigns wpa_pairwise via ??= when no earlier branch (explicit cipher suffix, wpa3-192, or sae-compat) has already set one: no WPA -> null 60 GHz (hw_mode=ad) -> GCMP HE or EHT htmode -> GCMP-256 CCMP everything else -> CCMP wpa3-192 now sets wpa_pairwise='GCMP-256' directly in its switch case, so the final default block can stay short. No functional change for existing encryption values. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 1f86f4e471f81038a9b69a4f121a55637634c7cf) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../files-ucode/usr/share/ucode/wifi/iface.uc | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index 1db4f14521..06b8392bed 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -17,16 +17,8 @@ export function parse_encryption(config, dev_config) { break; } - config.wpa_pairwise = null; - if (config.wpa) - config.wpa_pairwise = (config.hw_mode == 'ad') ? 'GCMP' : 'CCMP'; - config.auth_type = encryption[0] ?? 'none'; - let wpa3_pairwise = config.wpa_pairwise; - if (wildcard(dev_config?.htmode, 'EHT*') || wildcard(dev_config?.htmode, 'HE*')) - wpa3_pairwise = 'GCMP-256 ' + wpa3_pairwise; - switch(config.auth_type) { case 'owe': config.auth_type = 'owe'; @@ -34,6 +26,7 @@ export function parse_encryption(config, dev_config) { case 'wpa3-192': config.auth_type = 'eap192'; + config.wpa_pairwise = 'GCMP-256'; break; case 'wpa3-mixed': @@ -48,7 +41,6 @@ export function parse_encryption(config, dev_config) { case 'psk2': case 'psk-mixed': config.auth_type = 'psk'; - wpa3_pairwise = null; break; case 'sae': @@ -65,12 +57,6 @@ export function parse_encryption(config, dev_config) { case 'wpa2': case 'wpa-mixed': config.auth_type = 'eap'; - wpa3_pairwise = null; - break; - - default: - config.wpa_pairwise = null; - wpa3_pairwise = null; break; } @@ -102,20 +88,16 @@ export function parse_encryption(config, dev_config) { case 'gcmp': config.wpa_pairwise = 'GCMP'; break; - - default: - if (config.encryption == 'wpa3-192') { - config.wpa_pairwise = 'GCMP-256'; - break; - } - - if (!wpa3_pairwise) - break; - - config.wpa_pairwise = wpa3_pairwise; - break; } + if (!config.wpa) + config.wpa_pairwise ??= null; + else if (config.hw_mode == 'ad') + config.wpa_pairwise ??= 'GCMP'; + else if (wildcard(dev_config?.htmode, 'EHT*') || wildcard(dev_config?.htmode, 'HE*')) + config.wpa_pairwise ??= 'GCMP-256 CCMP'; + else + config.wpa_pairwise ??= 'CCMP'; }; export function wpa_key_mgmt(config) { From bfe81eab734c68972c9e2f10b56844dfb8e2629a Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 20:28:28 +0200 Subject: [PATCH 35/49] wifi-scripts: ucode: advertise SAE-EXT-KEY AKM alongside SAE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WPA3 Specification v3.5 ยง2.5.4 mandates that an AP's BSS Configuration enables AKM suite selector 00-0F-AC:24 (SAE-EXT-KEY, SAE with a group-dependent hash) whenever EHT or MLO is enabled. The WPA3 and Wi-Fi Enhanced Open Deployment Guide v1.1 also recommends it on non-EHT APs (Tables 3, 5, 6, 8). Add a new sae_ext_key UCI option (enabled by default) that advertises SAE-EXT-KEY, and FT-SAE-EXT-KEY when 802.11r is enabled, alongside plain SAE/FT-SAE for the sae and psk-sae encryption modes. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit a12cec9ea3c8224d6dad7d3b7339e92266f90ef8) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 5 +++++ .../files-ucode/usr/share/ucode/wifi/iface.uc | 14 ++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 52f54f667b..8d0298f7f4 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -962,6 +962,11 @@ "rsn_preauth": { "type": "boolean" }, + "sae_ext_key": { + "description": "Advertise the SAE-EXT-KEY AKM alongside plain SAE. Mandatory for EHT, enabled by default.", + "type": "boolean", + "default": true + }, "sae_password_file": { "description": "External file containing VLAN SAE MAC address triplets", "type": "string" diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index 06b8392bed..ff7fe2dbcd 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -144,14 +144,24 @@ export function wpa_key_mgmt(config) { case 'sae': append_value(config, 'wpa_key_mgmt', 'SAE'); - if (config.ieee80211r) + if (config.sae_ext_key) + append_value(config, 'wpa_key_mgmt', 'SAE-EXT-KEY'); + if (config.ieee80211r) { append_value(config, 'wpa_key_mgmt', 'FT-SAE'); + if (config.sae_ext_key) + append_value(config, 'wpa_key_mgmt', 'FT-SAE-EXT-KEY'); + } break; case 'psk-sae': append_value(config, 'wpa_key_mgmt', 'SAE'); - if (config.ieee80211r) + if (config.sae_ext_key) + append_value(config, 'wpa_key_mgmt', 'SAE-EXT-KEY'); + if (config.ieee80211r) { append_value(config, 'wpa_key_mgmt', 'FT-SAE'); + if (config.sae_ext_key) + append_value(config, 'wpa_key_mgmt', 'FT-SAE-EXT-KEY'); + } append_value(config, 'wpa_key_mgmt', 'WPA-PSK'); if (config.ieee80211w) From 7a3f0768000265070cbffcbfe56fa6e1a012e9e5 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 18 Apr 2026 23:12:56 +0200 Subject: [PATCH 36/49] wifi-scripts: ucode: enable Beacon Protection by default with PMF The WPA3 and Wi-Fi Enhanced Open Deployment and Implementation Guide v1.1, Table 4 (Common security configuration) marks Beacon Protection as MAND for EHT-enabled APs and RECOM otherwise for all WPA3 and Wi-Fi Enhanced Open modes. The ucode path blindly passed beacon_prot through from UCI in iface setup, which ran before encryption and MFP had been configured, and left hostapd at its insecure default of 0 when the user did not explicitly opt in. Default beacon_prot to 1 in iface_mfp after MFP has been confirmed to be enabled, and emit it there instead of in iface_setup so the option is only written when PMF support is actually negotiated. Users can still disable it explicitly via UCI. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit ef393caee2a97508846e4c3f5e4342473cf234a1) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 3f29593a29..1644ce5a40 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -57,7 +57,7 @@ function iface_setup(config) { 'disassoc_low_ack', 'skip_inactivity_poll', 'ignore_broadcast_ssid', 'uapsd_advertisement_enabled', 'utf8_ssid', 'multi_ap', 'multi_ap_vlanid', 'multi_ap_profile', 'tdls_prohibit', 'bridge', 'wds_sta', 'wds_bridge', 'snoop_iface', 'vendor_elements', 'nas_identifier', 'radius_acct_interim_interval', - 'ocv', 'beacon_prot', 'spp_amsdu', 'multicast_to_unicast', 'preamble', 'proxy_arp', 'per_sta_vif', 'mbo', + 'ocv', 'spp_amsdu', 'multicast_to_unicast', 'preamble', 'proxy_arp', 'per_sta_vif', 'mbo', 'bss_transition', 'wnm_sleep_mode', 'wnm_sleep_mode_no_keys', 'qos_map_set', 'max_listen_int', 'dtim_period', 'wmm_enabled', 'start_disabled', 'na_mcast_to_ucast', 'no_probe_resp_if_max_sta', ]); @@ -401,8 +401,11 @@ function iface_mfp(config) { else config.group_mgmt_cipher = config.ieee80211w_mgmt_cipher ?? 'AES-128-CMAC'; + set_default(config, 'beacon_prot', 1); + append_vars(config, [ - 'ieee80211w', 'group_mgmt_cipher', 'assoc_sa_query_max_timeout', 'assoc_sa_query_retry_timeout' + 'ieee80211w', 'group_mgmt_cipher', 'beacon_prot', + 'assoc_sa_query_max_timeout', 'assoc_sa_query_retry_timeout' ]); } From f8c45b0322041d891e3478206ef8db765aa382aa Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 21:38:56 +0200 Subject: [PATCH 37/49] wifi-scripts: ucode: add WPA3-Personal Compatibility Mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The WPA3 and Wi-Fi Enhanced Open Deployment and Implementation Guide v1.1 ยง2.4 (Tables 6 and 7) defines WPA3-Personal Compatibility Mode: the AP advertises a legacy-looking RSNE (WPA-PSK, CCMP-128, PMF Disabled) while RSN Override Elements layered on top expose SAE and, on EHT, SAE-EXT-KEY. WPA2-only STAs and STAs that ignore RSN Overriding associate unchanged; modern STAs pick up the stronger WPA3 AKM via RSNOE or RSNO2E. Only the pairwise cipher differs between elements: RSNE and RSNOE advertise CCMP-128, RSNO2E advertises GCMP-256 (EHT only). Group data (CCMP-128) and group management cipher (BIP-CMAC-128) are the same in all three per Tables 6/7, so hostapd's BSS-wide group_cipher and group_mgmt_cipher singletons produce the spec-correct values. Unlike WPA3-Personal Transition Mode (sae-mixed), which puts PSK and SAE together in the main RSNE with PMF Capable, Compatibility Mode keeps the main RSNE strictly WPA2-shaped so clients that choke on a mixed AKM list or PMF=Capable still see a pure WPA2 BSS. The trade- off is that clients without RSN Overriding support never pick up SAE. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 86b9eec8f02b60dd7e74f92cb5d2c61efe6d16c8) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../files-ucode/usr/share/ucode/wifi/ap.uc | 41 ++++++++++++++----- .../files-ucode/usr/share/ucode/wifi/iface.uc | 41 ++++++++++++++++++- 2 files changed, 70 insertions(+), 12 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 1644ce5a40..0d7b30af02 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -81,14 +81,26 @@ function iface_accounting_server(config) { append_vars(config, [ 'radius_acct_req_attr' ]); } -function iface_auth_type(config) { +function iface_auth_type(config, band) { if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ]) config.ieee80211w = 2; if (config.auth_type in [ 'psk-sae', 'eap-eap2' ]) set_default(config, 'ieee80211w', 1); - if (config.auth_type in [ 'sae', 'psk-sae' ]) { + if (config.auth_type == 'psk-sae-compat') { + if (band == '6g') { + set_default(config, 'ieee80211w', 2); + } else { + set_default(config, 'ieee80211w', 0); + config.rsn_override_mfp = 2; + config.rsn_override_omit_rsnxe = 1; + } + if (config.rsn_override_pairwise_2) + config.rsn_override_mfp_2 = 2; + } + + if (config.auth_type in [ 'sae', 'psk-sae', 'psk-sae-compat' ]) { config.sae_require_mfp = 1; if (!config.ppsk) set_default(config, 'sae_pwe', 2); @@ -116,6 +128,7 @@ function iface_auth_type(config) { case 'psk2': case 'sae': case 'psk-sae': + case 'psk-sae-compat': config.vlan_possible = 1; config.wps_possible = 1; @@ -131,12 +144,12 @@ function iface_auth_type(config) { netifd.setup_failed('INVALID_WPA_PSK'); } - if (config.auth_type in [ 'psk', 'psk-sae' ]) { + if (config.auth_type in [ 'psk', 'psk-sae', 'psk-sae-compat' ] && band != '6g') { set_default(config, 'wpa_psk_file', `/var/run/hostapd-${config.ifname}.psk`); touch_file(config.wpa_psk_file); } - if (config.auth_type in [ 'sae', 'psk-sae' ]) { + if (config.auth_type in [ 'sae', 'psk-sae', 'psk-sae-compat' ]) { set_default(config, 'sae_password_file', `/var/run/hostapd-${config.ifname}.sae`); touch_file(config.sae_password_file); } @@ -187,7 +200,7 @@ function iface_auth_type(config) { } function iface_ppsk(config) { - if (!(config.auth_type in [ 'none', 'owe', 'psk', 'sae', 'psk-sae', 'wep' ]) || !config.auth_server_addr) + if (!(config.auth_type in [ 'none', 'owe', 'psk', 'sae', 'psk-sae', 'psk-sae-compat', 'wep' ]) || !config.auth_server_addr) return; iface_authentication_server(config); @@ -391,7 +404,9 @@ function iface_roaming(config) { } function iface_mfp(config) { - if (!config.ieee80211w || config.wpa < 2) { + let override_mfp = config.rsn_override_mfp || config.rsn_override_mfp_2; + + if ((!config.ieee80211w && !override_mfp) || config.wpa < 2) { append('ieee80211w', 0); return; } @@ -421,7 +436,7 @@ function iface_key_caching(config) { 'rsn_preauth', 'rsn_preauth_interfaces' ]); } else { - set_default(config, 'okc', (config.auth_type in [ 'sae', 'psk-sae', 'owe' ])); + set_default(config, 'okc', (config.auth_type in [ 'sae', 'psk-sae', 'psk-sae-compat', 'owe' ])); } if (!config.okc && !config.fils) @@ -476,12 +491,12 @@ export function generate(interface, data, config, vlans, stas, phy_features) { config.auth_type = 'eap2'; } - if (config.auth_type in [ 'psk', 'psk-sae' ]) + if (config.auth_type in [ 'psk', 'psk-sae', 'psk-sae-compat' ] && data.config.band != '6g') iface_wpa_stations(config, stas); - if (config.auth_type in [ 'sae', 'psk-sae' ]) + if (config.auth_type in [ 'sae', 'psk-sae', 'psk-sae-compat' ]) iface_sae_stations(config, stas); - iface_auth_type(config); + iface_auth_type(config, data.config.band); iface_accounting_server(config); @@ -509,7 +524,7 @@ export function generate(interface, data, config, vlans, stas, phy_features) { iface_interworking(config); - iface.wpa_key_mgmt(config); + iface.wpa_key_mgmt(config, data.config.band); append_vars(config, [ 'wpa_key_mgmt', ]); @@ -530,6 +545,10 @@ export function generate(interface, data, config, vlans, stas, phy_features) { ]); } + if (config.rsn_override_omit_rsnxe) { + append_vars(config, ['rsn_override_omit_rsnxe']); + } + /* raw options */ for (let raw in config.hostapd_bss_options) append_raw(raw); diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index ff7fe2dbcd..754c339d76 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -53,6 +53,15 @@ export function parse_encryption(config, dev_config) { config.auth_type = 'psk-sae'; break; + case 'sae-compat': + config.auth_type = 'psk-sae-compat'; + config.wpa_pairwise = 'CCMP'; + if (dev_config.band != '6g') + config.rsn_override_pairwise = 'CCMP'; + if (wildcard(dev_config.htmode ?? '', 'EHT*')) + config.rsn_override_pairwise_2 = 'GCMP-256'; + break; + case 'wpa': case 'wpa2': case 'wpa-mixed': @@ -100,7 +109,7 @@ export function parse_encryption(config, dev_config) { config.wpa_pairwise ??= 'CCMP'; }; -export function wpa_key_mgmt(config) { +export function wpa_key_mgmt(config, band) { if (!config.wpa) return; @@ -170,6 +179,36 @@ export function wpa_key_mgmt(config) { append_value(config, 'wpa_key_mgmt', 'FT-PSK'); break; + case 'psk-sae-compat': + if (band == '6g') { + append_value(config, 'wpa_key_mgmt', 'SAE'); + if (config.ieee80211r) + append_value(config, 'wpa_key_mgmt', 'FT-SAE'); + + if (config.sae_ext_key && config.rsn_override_pairwise_2) { + append_value(config, 'rsn_override_key_mgmt_2', 'SAE-EXT-KEY'); + if (config.ieee80211r) + append_value(config, 'rsn_override_key_mgmt_2', 'FT-SAE-EXT-KEY'); + } + } else { + append_value(config, 'wpa_key_mgmt', 'WPA-PSK'); + if (config.ieee80211w) + append_value(config, 'wpa_key_mgmt', 'WPA-PSK-SHA256'); + if (config.ieee80211r) + append_value(config, 'wpa_key_mgmt', 'FT-PSK'); + + append_value(config, 'rsn_override_key_mgmt', 'SAE'); + if (config.ieee80211r) + append_value(config, 'rsn_override_key_mgmt', 'FT-SAE'); + + if (config.sae_ext_key && config.rsn_override_pairwise_2) { + append_value(config, 'rsn_override_key_mgmt_2', 'SAE-EXT-KEY'); + if (config.ieee80211r) + append_value(config, 'rsn_override_key_mgmt_2', 'FT-SAE-EXT-KEY'); + } + } + break; + case 'owe': append_value(config, 'wpa_key_mgmt', 'OWE'); break; From 829a6068e8f457186a9a3ddac65684e6063ed382 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Mon, 20 Apr 2026 00:36:47 +0200 Subject: [PATCH 38/49] wifi-scripts: ucode: default sae_pwe to H2E-only on 6 GHz The WPA3 and Wi-Fi Enhanced Open Deployment and Implementation Guide v1.1 (Tables 7 and 8) mandates "H2E Only" for SAE on 6 GHz, in both WPA3-Personal Only and WPA3-Personal Compatibility Mode: the 6 GHz band disallows the legacy Hunting-and-Pecking password element, so the AP must advertise BSS Membership Selector 123 to force STAs onto H2E. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 3b2205004052b1c656fe6ab8bf0b7217546746c1) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 0d7b30af02..cb55e993c2 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -102,8 +102,12 @@ function iface_auth_type(config, band) { if (config.auth_type in [ 'sae', 'psk-sae', 'psk-sae-compat' ]) { config.sae_require_mfp = 1; - if (!config.ppsk) - set_default(config, 'sae_pwe', 2); + if (!config.ppsk) { + if (band == '6g') + set_default(config, 'sae_pwe', 1); + else + set_default(config, 'sae_pwe', 2); + } } if (config.own_ip_addr) From 0c8a037e5cc2705e21d5817f21822876a36aae71 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 12:50:08 +0200 Subject: [PATCH 39/49] wifi-scripts: ucode: advertise Transition Disable on WPA3-only BSSes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WPA3 Specification v3.5 ยง13 defines the Transition Disable element sent inside message 3 of the 4-way handshake. An AP that is no longer offering a transition mode for its SSID sets the matching bit so that compliant STAs permanently stop falling back to WPA-PSK / WPA-EAP / open for that SSID, hardening against downgrade attacks and against operator mistakes where a transition-mode BSS is briefly brought up on an SSID that previously ran WPA3-only. Expose this as a UCI list 'transition_disable' with three classes of entries: * The existing OpenWrt encryption tokens 'sae' (bit 0x01), 'sae-pk' (0x02), 'wpa3' (0x04) and 'owe' (0x08) OR into the bitmap. SAE-PK itself is not yet wired through wifi-scripts; the token only lets an operator who configured SAE-PK out of band also hand the matching bit to hostapd. * 'on' derives the bitmap from the AP's auth_type ('sae' -> 0x01, 'eap2'/'eap192' -> 0x04, pure 'owe' -> 0x08) and overrides any other explicit tokens in the same list. Transition BSSes (psk-sae, eap-eap2, owe with owe_transition set) produce no bits even under 'on' because they are by definition still in transition. * 'off' unconditionally suppresses the element regardless of any other entries. Operators who need to revert a WPA3-only SSID back to a transition mode can set this proactively, giving compliant STAs time to forget the permanent bit before the mode change. Leave the list unset by default. Advertising Transition Disable is a one-way door -- once a compliant STA has seen the permanent bit for an SSID it will refuse to associate to a transition-mode BSS of the same name ever again -- so it must be opted in to per SSID, never flipped on by a firmware bump. This also matches the WPA3 and Wi-Fi Enhanced Open Deployment and Implementation Guide v1.1 Table 4 requirement that Transition Disable be MAND disabled by default on APs. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 2db148c062fcfce9c28f7a2aaf8156b560136054) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 8 ++++ .../files-ucode/usr/share/ucode/wifi/ap.uc | 38 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 8d0298f7f4..9d02a48a97 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -1040,6 +1040,14 @@ "description": "Local time zone as specified in 8.3 of IEEE Std 1003.1-2004", "type": "string" }, + "transition_disable": { + "description": "Transition modes the AP signals as disabled per WPA3 v3.5 ยง13. Entries 'sae', 'sae-pk', 'wpa3', 'owe' are OR'd into the bitmap; 'on' (or '1') derives it from auth_type; 'off' (or '0') suppresses the element. Unset by default.", + "type": "array", + "items": { + "type": "string", + "enum": [ "on", "off", "0", "1", "sae", "sae-pk", "wpa3", "owe" ] + } + }, "uapsd": { "type": "alias", "default": "uapsd_advertisement_enabled" diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index cb55e993c2..0ad948d037 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -428,6 +428,42 @@ function iface_mfp(config) { ]); } +function iface_transition_disable(config) { + if (config.wpa < 2) + return; + + let list = config.transition_disable; + if (!list || !length(list)) + return; + + for (let s in list) + if (s == 'off' || s == '0') + return; + + let bits = 0; + for (let s in list) { + if (s == 'on' || s == '1') { + bits = 0; + switch (config.auth_type) { + case 'sae': bits = 0x01; break; + case 'eap2': + case 'eap192': bits = 0x04; break; + case 'owe': if (!config.owe_transition) bits = 0x08; break; + } + break; + } + switch (s) { + case 'sae': bits |= 0x01; break; + case 'sae-pk': bits |= 0x02; break; + case 'wpa3': bits |= 0x04; break; + case 'owe': bits |= 0x08; break; + } + } + + if (bits) + append('transition_disable', sprintf('0x%02x', bits)); +} + function iface_key_caching(config) { if (config.wpa < 2) return; @@ -522,6 +558,8 @@ export function generate(interface, data, config, vlans, stas, phy_features) { iface_mfp(config); + iface_transition_disable(config); + iface_key_caching(config); iface_hs20(config); From 190a57d867de3b7ad2668bb43965c921223e6542 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 19 Apr 2026 16:50:37 +0200 Subject: [PATCH 40/49] wifi-scripts: ucode: default BIP cipher from wpa_pairwise The WPA3 and Wi-Fi Enhanced Open Deployment and Implementation Guide v1.1 (Tables 4, 5, 6) requires the group-management cipher (BIP) to match the mode and strength of the pairwise cipher: GCM-mode pairwise ciphers pair with BIP-GMAC integrity, CCM-mode pairwise ciphers with BIP-CMAC integrity. The ucode pipeline hard-coded group_mgmt_cipher to AES-128-CMAC (BIP-CMAC-128) regardless of the pairwise cipher, except for the eap192 special case that already forced BIP-GMAC-256. An EHT WPA3-Personal BSS therefore emitted wpa_pairwise=GCMP-256 alongside group_mgmt_cipher=AES-128-CMAC -- the integrity cipher two steps weaker than the data cipher and a spec violation on EHT. hostapd has a single group_mgmt_cipher knob, so the selected BIP has to be compatible with every pairwise cipher in wpa_pairwise. Picking from the first token would mis-select on mixed lists -- e.g. wpa_pairwise=\"GCMP-256 CCMP\" would yield BIP-GMAC-256, which a CCMP-only STA cannot negotiate. Walk the wpa_pairwise tokens and pick the BIP that matches the weakest cipher present: CCMP / TKIP -> AES-128-CMAC (BIP-CMAC-128) CCMP-256 -> BIP-CMAC-256 GCMP -> BIP-GMAC-128 GCMP-256 -> BIP-GMAC-256 Token matching uses fnmatch wildcards against a copy of wpa_pairwise that is padded with leading and trailing spaces, so each token is space-bounded regardless of its position in the list. The RSN override pairwise lists are not consulted: in the only caller that sets them (WPA3-Personal Compatibility Mode), Tables 6 and 7 require BIP-CMAC-128 across RSNE/RSNOE/RSNO2E even when the override lists advertise GCMP-256, so wpa_pairwise=CCMP already yields the correct BIP. An explicit ieee80211w_mgmt_cipher UCI value still wins over the derived default. Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 68165c85c83a50e18254abef9c0866c8b8a2bcd8) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../files-ucode/usr/share/ucode/wifi/ap.uc | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 0ad948d037..b2d5a00bfa 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -407,6 +407,20 @@ function iface_roaming(config) { ]); } +function default_group_mgmt_cipher(config) { + let p = ' ' + (config.wpa_pairwise ?? '') + ' '; + + if (wildcard(p, '* CCMP *') || wildcard(p, '* TKIP *')) + return 'AES-128-CMAC'; + if (wildcard(p, '* CCMP-256 *')) + return 'BIP-CMAC-256'; + if (wildcard(p, '* GCMP *')) + return 'BIP-GMAC-128'; + if (wildcard(p, '* GCMP-256 *')) + return 'BIP-GMAC-256'; + return 'AES-128-CMAC'; +} + function iface_mfp(config) { let override_mfp = config.rsn_override_mfp || config.rsn_override_mfp_2; @@ -415,10 +429,7 @@ function iface_mfp(config) { return; } - if (config.auth_type == 'eap192') - config.group_mgmt_cipher = 'BIP-GMAC-256'; - else - config.group_mgmt_cipher = config.ieee80211w_mgmt_cipher ?? 'AES-128-CMAC'; + config.group_mgmt_cipher = config.ieee80211w_mgmt_cipher ?? default_group_mgmt_cipher(config); set_default(config, 'beacon_prot', 1); From 2e905e0ca57d7fea4ac0c5913e013c502c24656d Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Mon, 20 Apr 2026 01:42:51 +0200 Subject: [PATCH 41/49] wifi-scripts: ucode: default sae_groups to NIST ECP 19/20/21 The WPA3 and Wi-Fi Enhanced Open Deployment Guide v1.1 (Table 4, "SAE Groups") recommends that WPA3-Personal APs advertise support for SAE groups 19, 20 and 21: * group 19 - ECP 256-bit (NIST P-256) * group 20 - ECP 384-bit (NIST P-384) * group 21 - ECP 521-bit (NIST P-521) hostapd's default is group 19 only, which leaves the two larger ECP groups unavailable even though the peer may prefer them. Set sae_groups = "19 20 21" as the default for any BSS whose auth_type is sae or psk-sae (SAE, SAE Transition and SAE Compatibility modes). Co-Authored-By: Claude Opus 4.7 Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 0ca11b32eb829e27a0a9b5442a1516afda426ac1) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../files-ucode/usr/share/ucode/wifi/ap.uc | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index b2d5a00bfa..e509597c35 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -100,8 +100,14 @@ function iface_auth_type(config, band) { config.rsn_override_mfp_2 = 2; } + if (config.auth_type == 'owe') { + set_default(config, 'owe_groups', '19 20 21'); + set_default(config, 'owe_ptk_workaround', 1); + } + if (config.auth_type in [ 'sae', 'psk-sae', 'psk-sae-compat' ]) { config.sae_require_mfp = 1; + set_default(config, 'sae_groups', '19 20 21'); if (!config.ppsk) { if (band == '6g') set_default(config, 'sae_pwe', 1); @@ -193,11 +199,11 @@ function iface_auth_type(config, band) { } append_vars(config, [ - 'sae_require_mfp', 'sae_password_file', 'sae_pwe', 'sae_track_password', 'time_advertisement', 'time_zone', + 'sae_require_mfp', 'sae_password_file', 'sae_pwe', 'sae_groups', 'sae_track_password', 'time_advertisement', 'time_zone', 'wpa_group_rekey', 'wpa_ptk_rekey', 'wpa_gmk_rekey', 'wpa_strict_rekey', 'macaddr_acl', 'wpa_psk_radius', 'wpa_psk', 'wpa_passphrase', 'wpa_psk_file', 'eapol_version', 'dynamic_vlan', 'radius_request_cui', 'eap_reauth_period', - 'radius_das_client', 'radius_das_port', 'own_ip_addr', 'dynamic_own_ip_addr', + 'radius_das_client', 'radius_das_port', 'owe_groups', 'owe_ptk_workaround', 'own_ip_addr', 'dynamic_own_ip_addr', 'wpa_disable_eapol_key_retries', 'auth_algs', 'wpa', 'wpa_pairwise', 'erp_domain', 'fils_realm', 'erp_send_reauth_start', 'fils_cache_id' ]); From e87d78b14194fcc77661c01f5f3e036493119a7e Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Tue, 21 Apr 2026 00:43:50 +0200 Subject: [PATCH 42/49] iw: fix IW_FULL guards in size reduction patch Two of the IW_FULL guards in 200-reduce_size.patch were inverted or incomplete: * the "unknown event" handler unconditionally replaced the verbose print with the short form, so IW_FULL builds lost the command name decoding; * the early return before the vendor IE parser used #ifdef IW_FULL, which suppressed parsing in the full build instead of the size-reduced one. Wrap both with the correct #ifndef IW_FULL / #else so the full and reduced builds produce the intended output. Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 957b921721f97a69d2c7794c1392ae9a90c41098) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../network/utils/iw/patches/200-reduce_size.patch | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/package/network/utils/iw/patches/200-reduce_size.patch b/package/network/utils/iw/patches/200-reduce_size.patch index f1327d01cf..3a4864fc9c 100644 --- a/package/network/utils/iw/patches/200-reduce_size.patch +++ b/package/network/utils/iw/patches/200-reduce_size.patch @@ -24,15 +24,18 @@ case NL80211_CMD_JOIN_IBSS: mac_addr_n2a(macbuf, nla_data(tb[NL80211_ATTR_MAC])); printf("IBSS %s joined\n", macbuf); -@@ -1297,9 +1300,9 @@ static int print_event(struct nl_msg *ms +@@ -1297,9 +1300,14 @@ static int print_event(struct nl_msg *ms case NL80211_CMD_ASSOC_COMEBACK: /* 147 */ parse_assoc_comeback(tb, gnlh->cmd); break; +#endif default: -- printf("unknown event %d (%s)\n", -- gnlh->cmd, command_name(gnlh->cmd)); ++#ifndef IW_FULL + printf("unknown event %d\n", gnlh->cmd); ++#else + printf("unknown event %d (%s)\n", + gnlh->cmd, command_name(gnlh->cmd)); ++#endif break; } @@ -212,7 +215,7 @@ return; } -+#ifdef IW_FULL ++#ifndef IW_FULL + return; +#endif + From 311deeb64403cf1d587606dee3f089fdf56a2fda Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Tue, 21 Apr 2026 00:12:50 +0200 Subject: [PATCH 43/49] iw: backport scan print of RSN Element Override IEs Backport upstream iw commit d90618809e06 ("iw: scan: print RSN Element Override IEs") as 001-*.patch so `iw scan` decodes the RSNOE (vendor WFA type 41) and RSNO2E (type 42) elements that hostapd emits for WPA3 Compatibility / RSN Overriding APs. Also refresh the hunk offsets in 200-reduce_size.patch. Link: https://github.com/openwrt/openwrt/pull/23009 (cherry picked from commit 164428d2d6167a555e35f2258277fc5a457fb75b) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- ...-scan-print-RSN-Element-Override-IEs.patch | 58 +++++++++++++++++++ .../utils/iw/patches/200-reduce_size.patch | 14 ++--- 2 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 package/network/utils/iw/patches/001-iw-scan-print-RSN-Element-Override-IEs.patch diff --git a/package/network/utils/iw/patches/001-iw-scan-print-RSN-Element-Override-IEs.patch b/package/network/utils/iw/patches/001-iw-scan-print-RSN-Element-Override-IEs.patch new file mode 100644 index 0000000000..12b31c5494 --- /dev/null +++ b/package/network/utils/iw/patches/001-iw-scan-print-RSN-Element-Override-IEs.patch @@ -0,0 +1,58 @@ +From d90618809e06e123241f57efdb7039a7a8a43d58 Mon Sep 17 00:00:00 2001 +From: Alex Gavin +Date: Sun, 15 Mar 2026 22:16:46 -0700 +Subject: iw: scan: print RSN Element Override IEs + +Parse body of RSN Element Override IEs as RSN IEs +using existing code. + +RSN IEs are a minimum size of 4 octets, as detailed +in IEEE802.11-2024, 9.4.2.23.1 + +Example output: +RSN Element Override: + * Version: 1 + * Group cipher: CCMP + * Pairwise ciphers: CCMP + * Authentication suites: SAE + * Capabilities: 16-PTKSA-RC 1-GTKSA-RC MFP-required MFP-capable (0x00cc) +RSN Element Override 2: + * Version: 1 + * Group cipher: CCMP + * Pairwise ciphers: GCMP-256 + * Authentication suites: SAE-EXT-KEY + * Capabilities: 16-PTKSA-RC 1-GTKSA-RC MFP-required MFP-capable (0x00cc) + +Signed-off-by: Alex Gavin +Link: https://patch.msgid.link/20260316051646.18303-2-a_gavin@icloud.com +Signed-off-by: Johannes Berg +--- + scan.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +--- a/scan.c ++++ b/scan.c +@@ -1893,6 +1893,14 @@ static void print_wifi_osen(const uint8_ + print_osen_ie("OSEN", "OSEN", len, data); + } + ++static void print_wifi_rsn_override(const uint8_t type, uint8_t len, ++ const uint8_t *data, ++ const struct ie_context *ctx) ++{ ++ printf("\n\t"); ++ print_rsn_ie("CCMP", "IEEE802.1X", len, data); ++} ++ + static bool print_wifi_wmm_param(const uint8_t *data, uint8_t len) + { + int i; +@@ -2344,6 +2352,8 @@ static const struct ie_print wfa_printer + [16] = { "HotSpot 2.0 Indication", print_hs20_ind, 1, 255, BIT(PRINT_SCAN), }, + [18] = { "HotSpot 2.0 OSEN", print_wifi_osen, 1, 255, BIT(PRINT_SCAN), }, + [28] = { "OWE Transition Mode", print_wifi_owe_tarns, 7, 255, BIT(PRINT_SCAN), }, ++ [41] = { "RSN Element Override", print_wifi_rsn_override, 4, 255, BIT(PRINT_SCAN), }, ++ [42] = { "RSN Element Override 2", print_wifi_rsn_override, 4, 255, BIT(PRINT_SCAN), }, + }; + + static void print_vendor(unsigned char len, unsigned char *data, diff --git a/package/network/utils/iw/patches/200-reduce_size.patch b/package/network/utils/iw/patches/200-reduce_size.patch index 3a4864fc9c..56b5c4e632 100644 --- a/package/network/utils/iw/patches/200-reduce_size.patch +++ b/package/network/utils/iw/patches/200-reduce_size.patch @@ -200,7 +200,7 @@ }; static void print_wifi_wpa(const uint8_t type, uint8_t len, const uint8_t *data, -@@ -2213,8 +2218,10 @@ static void print_wifi_wps(const uint8_t +@@ -2221,8 +2226,10 @@ static void print_wifi_wps(const uint8_t static const struct ie_print wifiprinters[] = { [1] = { "WPA", print_wifi_wpa, 2, 255, BIT(PRINT_SCAN), }, @@ -211,7 +211,7 @@ }; static inline void print_p2p(const uint8_t type, uint8_t len, -@@ -2377,6 +2384,10 @@ static void print_vendor(unsigned char l +@@ -2387,6 +2394,10 @@ static void print_vendor(unsigned char l return; } @@ -222,7 +222,7 @@ if (len >= 4 && memcmp(data, wfa_oui, 3) == 0) { if (data[3] < ARRAY_SIZE(wfa_printers) && wfa_printers[data[3]].name && -@@ -2576,6 +2587,7 @@ static void print_capa_non_dmg(__u16 cap +@@ -2586,6 +2597,7 @@ static void print_capa_non_dmg(__u16 cap printf(" ESS"); if (capa & WLAN_CAPABILITY_IBSS) printf(" IBSS"); @@ -230,7 +230,7 @@ if (capa & WLAN_CAPABILITY_CF_POLLABLE) printf(" CfPollable"); if (capa & WLAN_CAPABILITY_CF_POLL_REQUEST) -@@ -2604,6 +2616,7 @@ static void print_capa_non_dmg(__u16 cap +@@ -2614,6 +2626,7 @@ static void print_capa_non_dmg(__u16 cap printf(" DelayedBACK"); if (capa & WLAN_CAPABILITY_IMM_BACK) printf(" ImmediateBACK"); @@ -238,7 +238,7 @@ } static int print_bss_handler(struct nl_msg *msg, void *arg) -@@ -2694,8 +2707,10 @@ static int print_bss_handler(struct nl_m +@@ -2704,8 +2717,10 @@ static int print_bss_handler(struct nl_m else printf("\tfreq: %d\n", freq); @@ -249,7 +249,7 @@ } if (bss[NL80211_BSS_BEACON_INTERVAL]) printf("\tbeacon interval: %d TUs\n", -@@ -2889,6 +2904,7 @@ static int handle_stop_sched_scan(struct +@@ -2899,6 +2914,7 @@ static int handle_stop_sched_scan(struct return 0; } @@ -257,7 +257,7 @@ COMMAND(scan, sched_start, SCHED_SCAN_OPTIONS, NL80211_CMD_START_SCHED_SCAN, 0, CIB_NETDEV, handle_start_sched_scan, -@@ -2899,3 +2915,4 @@ COMMAND(scan, sched_start, +@@ -2909,3 +2925,4 @@ COMMAND(scan, sched_start, COMMAND(scan, sched_stop, "", NL80211_CMD_STOP_SCHED_SCAN, 0, CIB_NETDEV, handle_stop_sched_scan, "Stop an ongoing scheduled scan."); From 70e7f605ccc6d7acc3ede3615f934b3d12773e29 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 5 Jul 2026 01:24:34 +0200 Subject: [PATCH 44/49] wifi-scripts: ucode: only advertise GCMP-256 when the driver supports it The GCMP-256 pairwise cipher was advertised based only on the BSS htmode (HE/EHT), without checking whether the driver actually implements it. On phys that do not support GCMP-256 this makes hostapd reject the configuration and fail to start. Query the phy's NL80211 cipher suite list in device_capabilities() and expose a phy_features.cipher_gcmp256 flag, then only offer GCMP-256 when the driver advertises the GCMP-256 cipher suite (00-0F-AC:9, 0x000fac09), mirroring how the HT/VHT feature flags are already gated on the reported phy capabilities. This covers both the sae/sae-mixed "GCMP-256 CCMP" pairwise default and the GCMP-256 pairwise cipher in the sae-compat RSNE Override 2 element. The "GCMP-256 CCMP" default now keys off driver support alone instead of the htmode; a follow-up commit reintroduces the per-mode/EHT restriction via a gcmp256 option. Fixes: 1f86f4e471f8 ("wifi-scripts: ucode: simplify wpa_pairwise default selection") Fixes: 86b9eec8f02b ("wifi-scripts: ucode: add WPA3-Personal Compatibility Mode") Assisted-by: Claude:claude-opus-4-8 Link: https://github.com/openwrt/openwrt/pull/24041 (cherry picked from commit 05e87cfcb6bdb5fc3ab9b55e93e72ae9d1d48036) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc | 2 +- .../files-ucode/usr/share/ucode/wifi/hostapd.uc | 3 +++ .../wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index e509597c35..54bc7db967 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -540,7 +540,7 @@ export function generate(interface, data, config, vlans, stas, phy_features) { config.start_disabled = data.ap_start_disabled; iface_setup(config); - iface.parse_encryption(config, data.config); + iface.parse_encryption(config, data.config, phy_features); if (data.config.band == '6g') { if (config.auth_type == 'psk-sae') config.auth_type = 'sae'; diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc index cfd652cc2f..778706e850 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/hostapd.uc @@ -14,6 +14,8 @@ import * as fs from 'fs'; const NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER = 33; const NL80211_EXT_FEATURE_RADAR_BACKGROUND = 61; +const WLAN_CIPHER_SUITE_GCMP_256 = 0x000fac09; + let phy_features = {}; let phy_capabilities = {}; @@ -478,6 +480,7 @@ function device_capabilities(config) { phy_features.ftm_responder = device_extended_features(phy.extended_features, NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER); phy_features.radar_background = device_extended_features(phy.extended_features, NL80211_EXT_FEATURE_RADAR_BACKGROUND); + phy_features.cipher_gcmp256 = WLAN_CIPHER_SUITE_GCMP_256 in (phy.cipher_suites ?? []); } function generate(config) { diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index 754c339d76..7295661706 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -3,7 +3,7 @@ import { append_value, log } from 'wifi.common'; import * as fs from 'fs'; -export function parse_encryption(config, dev_config) { +export function parse_encryption(config, dev_config, phy_features) { if (!config.encryption) return; @@ -58,7 +58,7 @@ export function parse_encryption(config, dev_config) { config.wpa_pairwise = 'CCMP'; if (dev_config.band != '6g') config.rsn_override_pairwise = 'CCMP'; - if (wildcard(dev_config.htmode ?? '', 'EHT*')) + if (phy_features?.cipher_gcmp256 && wildcard(dev_config.htmode ?? '', 'EHT*')) config.rsn_override_pairwise_2 = 'GCMP-256'; break; @@ -103,7 +103,7 @@ export function parse_encryption(config, dev_config) { config.wpa_pairwise ??= null; else if (config.hw_mode == 'ad') config.wpa_pairwise ??= 'GCMP'; - else if (wildcard(dev_config?.htmode, 'EHT*') || wildcard(dev_config?.htmode, 'HE*')) + else if (phy_features?.cipher_gcmp256) config.wpa_pairwise ??= 'GCMP-256 CCMP'; else config.wpa_pairwise ??= 'CCMP'; From fdb8a8e5d15ea3d99f9beec6bcaeb32eb0282d5b Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 4 Jul 2026 00:23:32 +0200 Subject: [PATCH 45/49] wifi-scripts: ucode: default the SAE-EXT-KEY AKM per WPA3 mode Commit a12cec9ea3c8 ("wifi-scripts: ucode: advertise SAE-EXT-KEY AKM alongside SAE") advertised the SAE-EXT-KEY AKM (00-0F-AC:24, SAE using a group-dependent hash, aka SAE-GDH) by default on every sae, sae-mixed and sae-compat BSS. WPA3 Specification v3.5 only makes this AKM mandatory when the BSS enables EHT or MLO (section 2.5, item 4); for HE and below it is merely recommended (sections 2.2 and 2.3). In practice the FT-SAE-EXT-KEY AKM (SAE-EXT-KEY combined with 802.11r), which gets added automatically once Fast Transition is enabled, keeps some clients from associating (a Samsung Galaxy Tab S10 FE) or makes them reboot shortly after connecting (a Poco X6). Plain SAE-EXT-KEY without FT was seen to work on the same tablet, but its interoperability is not well tested. Drop the fixed schema default and pick the default from the auth type in parse_encryption instead. Only default it on where it is both mandatory and safe to offer: on Compatibility mode (sae-compat) BSSes running an EHT htmode, which carry it in a separate RSN Override element that legacy clients ignore. Keep it off for WPA3-Personal (sae) and Transition (sae-mixed) mode, where it would sit in the main RSNE that a choking client cannot ignore, and off on non-EHT BSSes. An explicit 'option sae_ext_key 0/1' still overrides this per BSS. The SAE-EXT-KEY AKM in the sae-compat RSN Override 2 element now keys off config.sae_ext_key alone; the extra config.rsn_override_pairwise_2 guard is dropped, decoupling the AKM from the GCMP-256 pairwise cipher so each follows its own option. Fixes: a12cec9ea3c8 ("wifi-scripts: ucode: advertise SAE-EXT-KEY AKM alongside SAE") Assisted-by: Claude:claude-opus-4-8 Link: https://github.com/openwrt/openwrt/pull/24041 (cherry picked from commit fc652db52a20afd4e5aa1c7b9d4456425780b814) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 5 ++--- .../files-ucode/usr/share/ucode/wifi/iface.uc | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 9d02a48a97..9fde13dc03 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -963,9 +963,8 @@ "type": "boolean" }, "sae_ext_key": { - "description": "Advertise the SAE-EXT-KEY AKM alongside plain SAE. Mandatory for EHT, enabled by default.", - "type": "boolean", - "default": true + "description": "Advertise the SAE-EXT-KEY AKM (SAE using a group-dependent hash) alongside plain SAE. Mandatory for EHT, recommended otherwise. Defaults off for sae and sae-mixed because some clients misbehave when SAE-EXT-KEY is offered, and on for sae-compat BSSes using an EHT htmode (where SAE-EXT-KEY is mandatory), carried in a separate RSN Override element that legacy clients ignore.", + "type": "boolean" }, "sae_password_file": { "description": "External file containing VLAN SAE MAC address triplets", diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index 7295661706..eb0dc7f94e 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -19,6 +19,18 @@ export function parse_encryption(config, dev_config, phy_features) { config.auth_type = encryption[0] ?? 'none'; + /* + * The SAE-EXT-KEY (SAE-GDH) AKM is only mandatory for EHT/MLO and breaks + * interoperability with some clients, so only default it on where it is + * both required and safe to offer: on Compatibility mode (sae-compat) + * BSSes that run an EHT htmode, which carry it in a separate RSN Override + * element that legacy clients ignore. It stays off for WPA3-Personal (sae) + * and Transition (sae-mixed) mode and on non-EHT BSSes. An explicit + * sae_ext_key option overrides this per BSS. + */ + let compat = (config.auth_type == 'sae-compat'); + config.sae_ext_key ??= compat && wildcard(dev_config?.htmode ?? '', 'EHT*'); + switch(config.auth_type) { case 'owe': config.auth_type = 'owe'; @@ -185,7 +197,7 @@ export function wpa_key_mgmt(config, band) { if (config.ieee80211r) append_value(config, 'wpa_key_mgmt', 'FT-SAE'); - if (config.sae_ext_key && config.rsn_override_pairwise_2) { + if (config.sae_ext_key) { append_value(config, 'rsn_override_key_mgmt_2', 'SAE-EXT-KEY'); if (config.ieee80211r) append_value(config, 'rsn_override_key_mgmt_2', 'FT-SAE-EXT-KEY'); @@ -201,7 +213,7 @@ export function wpa_key_mgmt(config, band) { if (config.ieee80211r) append_value(config, 'rsn_override_key_mgmt', 'FT-SAE'); - if (config.sae_ext_key && config.rsn_override_pairwise_2) { + if (config.sae_ext_key) { append_value(config, 'rsn_override_key_mgmt_2', 'SAE-EXT-KEY'); if (config.ieee80211r) append_value(config, 'rsn_override_key_mgmt_2', 'FT-SAE-EXT-KEY'); From dd8cdc8a59289151aa13d726260a28bc3ec6ae40 Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sat, 4 Jul 2026 00:23:32 +0200 Subject: [PATCH 46/49] wifi-scripts: ucode: add gcmp256 option, default GCMP-256 per WPA3 mode Commit 1f86f4e471f8 ("wifi-scripts: ucode: simplify wpa_pairwise default selection") made HE and EHT BSSes default their pairwise cipher to "GCMP-256 CCMP", and commit 86b9eec8f02b ("wifi-scripts: ucode: add WPA3-Personal Compatibility Mode") always put GCMP-256 into the RSNE Override 2 element on EHT compatibility-mode BSSes. WPA3 Specification v3.5 only makes GCMP-256 mandatory when the BSS enables EHT or MLO (section 2.5, item 5); for HE and below the WPA3 and Wi-Fi Enhanced Open Deployment Guide v1.1 lists it as recommended, not required. Advertising it by default causes interoperability problems: several clients fail to associate when GCMP-256 is offered as a pairwise cipher and connect again with CCMP only (Nanoleaf devices, a Motorola/Unisoc phone, a Linux/iwd laptop). Add a gcmp256 UCI option and, like sae_ext_key, default it on only where GCMP-256 is both mandatory and safe: on Compatibility mode (sae-compat) BSSes running an EHT htmode, which carry it in a separate RSNE Override 2 element that legacy clients ignore. It defaults off for WPA3-Personal (sae) and Transition (sae-mixed) mode and on non-EHT BSSes. An explicit 'option gcmp256 0/1' overrides the default per BSS. Both GCMP-256 pairwise defaults now key off config.gcmp256 (and, from the earlier driver-support change, the phy actually implementing the cipher): the sae/sae-mixed "GCMP-256 CCMP" pairwise cipher and the sae-compat RSNE Override 2 element. The 'encryption sae+gcmp256' suffix and the wpa3-192 mode still force GCMP-256 as before. Fixes: 1f86f4e471f8 ("wifi-scripts: ucode: simplify wpa_pairwise default selection") Fixes: 86b9eec8f02b ("wifi-scripts: ucode: add WPA3-Personal Compatibility Mode") Assisted-by: Claude:claude-opus-4-8 Link: https://github.com/openwrt/openwrt/pull/24041 (cherry picked from commit 5e067465ff03e3dc3f0ab1c0bd5eb093445a7430) Link: https://github.com/openwrt/openwrt/pull/23011 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 4 ++++ .../files-ucode/usr/share/ucode/wifi/iface.uc | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 9fde13dc03..3d6442e915 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -371,6 +371,10 @@ "gas_address3": { "type": "string" }, + "gcmp256": { + "description": "Advertise the GCMP-256 pairwise cipher on HE/EHT BSSes (alongside CCMP for sae/sae-mixed, in the RSNE Override 2 element for sae-compat). Mandatory for EHT, recommended otherwise. Defaults off for sae and sae-mixed because some clients and chipsets misbehave when GCMP-256 is offered, and on for sae-compat BSSes using an EHT htmode (where GCMP-256 is mandatory), carried in a separate RSN Override element that legacy clients ignore. Only advertised when the driver supports the GCMP-256 cipher.", + "type": "boolean" + }, "hidden": { "type": "alias", "default": "ignore_broadcast_ssid" diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index eb0dc7f94e..428df2d2fa 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -20,15 +20,16 @@ export function parse_encryption(config, dev_config, phy_features) { config.auth_type = encryption[0] ?? 'none'; /* - * The SAE-EXT-KEY (SAE-GDH) AKM is only mandatory for EHT/MLO and breaks - * interoperability with some clients, so only default it on where it is - * both required and safe to offer: on Compatibility mode (sae-compat) - * BSSes that run an EHT htmode, which carry it in a separate RSN Override - * element that legacy clients ignore. It stays off for WPA3-Personal (sae) - * and Transition (sae-mixed) mode and on non-EHT BSSes. An explicit - * sae_ext_key option overrides this per BSS. + * GCMP-256 and the SAE-EXT-KEY (SAE-GDH) AKM are only mandatory for + * EHT/MLO and break interoperability with many clients, so only default + * them on where they are both required and safe to offer: on Compatibility + * mode (sae-compat) BSSes that run an EHT htmode, which carry them in a + * separate RSN Override element that legacy clients ignore. They stay off + * for WPA3-Personal (sae) and Transition (sae-mixed) mode and on non-EHT + * BSSes. Explicit gcmp256 and sae_ext_key options override this per BSS. */ let compat = (config.auth_type == 'sae-compat'); + config.gcmp256 ??= compat && wildcard(dev_config?.htmode ?? '', 'EHT*'); config.sae_ext_key ??= compat && wildcard(dev_config?.htmode ?? '', 'EHT*'); switch(config.auth_type) { @@ -70,7 +71,7 @@ export function parse_encryption(config, dev_config, phy_features) { config.wpa_pairwise = 'CCMP'; if (dev_config.band != '6g') config.rsn_override_pairwise = 'CCMP'; - if (phy_features?.cipher_gcmp256 && wildcard(dev_config.htmode ?? '', 'EHT*')) + if (config.gcmp256 && phy_features?.cipher_gcmp256) config.rsn_override_pairwise_2 = 'GCMP-256'; break; @@ -115,7 +116,7 @@ export function parse_encryption(config, dev_config, phy_features) { config.wpa_pairwise ??= null; else if (config.hw_mode == 'ad') config.wpa_pairwise ??= 'GCMP'; - else if (phy_features?.cipher_gcmp256) + else if (config.gcmp256 && phy_features?.cipher_gcmp256) config.wpa_pairwise ??= 'GCMP-256 CCMP'; else config.wpa_pairwise ??= 'CCMP'; From eca8636293df4674aa5cffa6ccbbd1d24e326a8a Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 5 Jul 2026 19:27:35 +0200 Subject: [PATCH 47/49] wifi-scripts: ucode: fix EAP phase2 authentication method The supplicant config generator emitted the phase2 directive as phase2="auth=${auth}" for every PEAP/TTLS/FAST configuration. That is wrong whenever the configured inner method is an EAP method: for auth='EAP-MSCHAPV2' it produced phase2="auth=EAP-MSCHAPV2", which wpa_supplicant rejects with: TLS: Unsupported Phase2 EAP method 'EAP-MSCHAPV2' breaking WPA-Enterprise clients that use an EAP inner method. Mirror the shell config generator (hostapd.sh): strip the "EAP-" prefix and pick the phase2 prefix from the method type, i.e. "autheap=" for a tunneled EAP method with TTLS and "auth=" for a non-EAP method or a full "auth=..." spec provided by the user. Fixes: https://github.com/openwrt/openwrt/issues/24086 Fixes: c92ded2f6e7a ("wifi-scripts: fix EAP STA support in supplicant config generation") Assisted-by: Claude:claude-opus-4-8 Link: https://github.com/openwrt/openwrt/pull/24088 (cherry picked from commit 0cdf956ee1fe729ef2ccb81affcadb807a751c43) Link: https://github.com/openwrt/openwrt/pull/24116 Signed-off-by: Hauke Mehrtens --- .../usr/share/ucode/wifi/supplicant.uc | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc index 5688a1aef1..7969ea310b 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc @@ -160,7 +160,20 @@ function setup_sta(data, config) { case 'peap': case 'ttls': set_default(config, 'auth', 'MSCHAPV2'); - config.phase2 = `"auth=${config.auth}"`; + + let auth = config.auth; + let phase2proto = 'auth='; + if (index(auth, 'auth') == 0) { + /* user already provided a full "auth=..." spec */ + phase2proto = ''; + } else if (index(auth, 'EAP-') == 0) { + /* inner EAP method, e.g. EAP-MSCHAPV2 -> MSCHAPV2 */ + auth = substr(auth, 4); + if (config.eap_type == 'ttls') + phase2proto = 'autheap='; + } + config.phase2 = `"${phase2proto}${auth}"`; + if (config.auth == 'EAP-TLS') { if (config.ca_cert2_usesystem && fs.stat('/etc/ssl/certs/ca-certificates.crt')) config.ca_cert2 = '/etc/ssl/certs/ca-certificates.crt'; From d9a452e7cd82c2d6a8719c78bc8e659acf4a291a Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 5 Jul 2026 19:38:25 +0200 Subject: [PATCH 48/49] wifi-scripts: ucode: fix EAP certificate constraint handling The supplicant config generator emitted the altsubject_match, domain_match and domain_suffix_match server certificate constraints through the plain (unquoted) variable list. As these are UCI arrays, they were rendered space-separated and without quotes, e.g. altsubject_match=DNS:a.example.com DNS:b.example.com wpa_supplicant parses an unquoted string value as a hex blob, so such a line fails to parse and the constraint is dropped. wpa_supplicant expects a single quoted, semicolon-separated string: altsubject_match="DNS:a.example.com;DNS:b.example.com" Join these lists with semicolons and emit them as quoted strings. The inner-tunnel (phase 2) constraints subject_match2, altsubject_match2, domain_match2 and domain_suffix_match2 were not written to the config at all; emit them as well. Add the matching inner EAP-TLS options ca_cert2, client_cert2, private_key2 and private_key2_passwd to the schema so they validate cleanly. Fixes: 218f3884d2fb ("wifi-scripts: add ucode based scripts") Assisted-by: Claude:claude-opus-4-8 Link: https://github.com/openwrt/openwrt/pull/24088 (cherry picked from commit 7be144ad83e33544b8f354429eb012a63b2aeecb) Link: https://github.com/openwrt/openwrt/pull/24116 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 16 ++++++++++++++++ .../usr/share/ucode/wifi/supplicant.uc | 18 ++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 3d6442e915..b3ca5f3bad 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -196,6 +196,10 @@ "description": "Specifies the path the CA certificate used for authentication", "type": "string" }, + "ca_cert2": { + "description": "Path to the inner-tunnel (phase 2) CA certificate for EAP-TLS", + "type": "string" + }, "ca_cert2_usesystem": { "type": "boolean" }, @@ -215,6 +219,10 @@ "description": "File path to client certificate file (PEM/DER)", "type": "string" }, + "client_cert2": { + "description": "File path to the inner-tunnel (phase 2) client certificate for EAP-TLS", + "type": "string" + }, "dae_client": { "type": "alias", "default": "radius_das_client" @@ -854,10 +862,18 @@ "description": "Private key matching with the server certificate for EAP-TLS/PEAP/TTLS", "type": "string" }, + "private_key2": { + "description": "Inner-tunnel (phase 2) private key for EAP-TLS", + "type": "string" + }, "private_key_passwd": { "description": "Passphrase for private key", "type": "string" }, + "private_key2_passwd": { + "description": "Passphrase for the inner-tunnel (phase 2) private key", + "type": "string" + }, "proxy_arp": { "description": "Proxy ARP", "type": "boolean" diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc index 7969ea310b..d889b4b25a 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/supplicant.uc @@ -207,9 +207,24 @@ function setup_sta(data, config) { config.mcast_rate = ratestr(config.mcast_rate); + /* + * Certificate constraint lists are semicolon-separated strings in the + * wpa_supplicant config, while UCI stores them as arrays. Join them here + * so they are emitted as a single quoted value below. + */ + for (let key in [ 'altsubject_match', 'altsubject_match2', + 'domain_match', 'domain_match2', + 'domain_suffix_match', 'domain_suffix_match2' ]) + if (type(config[key]) == 'array') + config[key] = length(config[key]) ? join(';', config[key]) : null; + network_append_string_vars(config, [ 'ssid', 'identity', 'anonymous_identity', 'password', - 'ca_cert', 'ca_cert2', 'client_cert', 'client_cert2', 'subject_match', + 'ca_cert', 'ca_cert2', 'client_cert', 'client_cert2', + 'subject_match', 'subject_match2', + 'altsubject_match', 'altsubject_match2', + 'domain_match', 'domain_match2', + 'domain_suffix_match', 'domain_suffix_match2', 'private_key', 'private_key_passwd', 'private_key2', 'private_key2_passwd', ]); network_append_vars(config, [ @@ -218,7 +233,6 @@ function setup_sta(data, config) { 'proto', 'mesh_fwding', 'mesh_rssi_threshold', 'frequency', 'fixed_freq', 'disable_ht', 'disable_ht40', 'disable_vht', 'vht', 'max_oper_chwidth', 'ht40', 'beacon_int', 'ieee80211w', 'rates', 'mesh_basic_rates', 'mcast_rate', - 'altsubject_match', 'domain_match', 'domain_suffix_match', 'bssid_blacklist', 'bssid_whitelist', 'erp', 'eap', 'phase2', ]); } From da9b1abb5fad11462c65dfe8fe4b24bb499bf29b Mon Sep 17 00:00:00 2001 From: Hauke Mehrtens Date: Sun, 5 Jul 2026 19:38:53 +0200 Subject: [PATCH 49/49] wifi-scripts: restore priv_key/priv_key_pwd as config aliases The shell config generator read the client private key from the UCI options priv_key / priv_key_pwd (and priv_key2 / priv_key2_pwd for the inner tunnel). The ucode generator was switched to private_key / private_key_passwd (matching the wpa_supplicant field names), and LuCI was updated accordingly, but existing configurations still carry the old option names. Such configs silently lose their private key: for an EAP-TLS client this leaves wpa_supplicant without a client key and authentication fails after upgrading from 24.10. The schema already lists priv_key / priv_key_pwd, but as plain strings, so validate() never migrates them. Declare them (and the missing priv_key2 / priv_key2_pwd) as aliases of the private_key* options so the old names keep working. Fixes: https://github.com/openwrt/openwrt/issues/22599 Fixes: 218f3884d2fb ("wifi-scripts: add ucode based scripts") Assisted-by: Claude:claude-opus-4-8 Link: https://github.com/openwrt/openwrt/pull/24088 (cherry picked from commit 649b42331c8332db5f91074b0c6143c7919a6973) Link: https://github.com/openwrt/openwrt/pull/24116 Signed-off-by: Hauke Mehrtens --- .../usr/share/schema/wireless.wifi-iface.json | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index b3ca5f3bad..1c31a6b9da 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -853,10 +853,20 @@ "default": true }, "priv_key": { - "type": "string" + "type": "alias", + "default": "private_key" }, "priv_key_pwd": { - "type": "string" + "type": "alias", + "default": "private_key_passwd" + }, + "priv_key2": { + "type": "alias", + "default": "private_key2" + }, + "priv_key2_pwd": { + "type": "alias", + "default": "private_key2_passwd" }, "private_key": { "description": "Private key matching with the server certificate for EAP-TLS/PEAP/TTLS",