[openwrt/openwrt] hostapd: backport from master, including ucode based reload support

LEDE Commits lede-commits at lists.infradead.org
Mon Sep 18 08:00:29 PDT 2023


nbd pushed a commit to openwrt/openwrt.git, branch openwrt-23.05:
https://git.openwrt.org/9720b094aef89802327683f25824820581fed0b9

commit 9720b094aef89802327683f25824820581fed0b9
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Tue Aug 29 14:32:42 2023 +0200

    hostapd: backport from master, including ucode based reload support
    
    This significantly improves config reload behavior and also fixes some
    corner cases related to running AP + mesh interfaces at the same time.
    
    Signed-off-by: Felix Fietkau <nbd at nbd.name>
---
 .../mac80211/files/lib/netifd/wireless/mac80211.sh | 627 +++++++---------
 package/network/services/hostapd/Config.in         |   5 -
 package/network/services/hostapd/Makefile          |  80 +-
 package/network/services/hostapd/files/common.uc   | 318 ++++++++
 package/network/services/hostapd/files/hostapd.sh  |  38 +-
 package/network/services/hostapd/files/hostapd.uc  | 809 +++++++++++++++++++++
 .../network/services/hostapd/files/radius.clients  |   1 +
 .../network/services/hostapd/files/radius.config   |   9 +
 package/network/services/hostapd/files/radius.init |  42 ++
 .../network/services/hostapd/files/radius.users    |  14 +
 package/network/services/hostapd/files/wdev.uc     | 207 ++++++
 .../hostapd/files/wpa_supplicant-basic.config      |   2 +-
 .../hostapd/files/wpa_supplicant-full.config       |   2 +-
 .../hostapd/files/wpa_supplicant-mini.config       |   2 +-
 .../hostapd/files/wpa_supplicant-p2p.config        |   2 +-
 .../services/hostapd/files/wpa_supplicant.uc       | 330 +++++++++
 .../network/services/hostapd/files/wpad_acl.json   |   6 +
 ...e-deterministic-channel-on-channel-switch.patch |   6 +-
 ...021-fix-sta-add-after-previous-connection.patch |   4 +-
 ...211-rewrite-neigh-code-to-not-depend-on-l.patch |   6 +-
 ...processing-authentication-frames-in-block.patch |   2 +-
 .../hostapd/patches/100-daemonize_fix.patch        |  97 ---
 ...s-Makefile-make-run-tests-with-CONFIG_TLS.patch |  14 +-
 ...0-hostapd-update-cfs0-and-cfs1-for-160MHz.patch |   4 +-
 ...211-fix-setting-QoS-map-on-secondary-BSSs.patch |  20 +
 ...211-update-drv-ifindex-on-removing-the-fi.patch |  18 +
 ...e-nl80211_put_freq_params-call-outside-of.patch |  34 +
 .../services/hostapd/patches/200-multicall.patch   |  22 +-
 .../services/hostapd/patches/300-noscan.patch      |   4 +-
 .../hostapd/patches/310-rescan_immediately.patch   |   2 +-
 .../hostapd/patches/340-reload_freq_change.patch   |  80 --
 .../hostapd/patches/360-ctrl_iface_reload.patch    | 106 ---
 .../hostapd/patches/370-ap_sta_support.patch       | 392 ----------
 .../patches/380-disable_ctrl_iface_mib.patch       |  14 +-
 .../patches/390-wpa_ie_cap_workaround.patch        |   4 +-
 .../hostapd/patches/420-indicate-features.patch    |  12 +-
 .../hostapd/patches/432-missing-typedef.patch      |  10 -
 .../services/hostapd/patches/450-scan_wait.patch   |  73 --
 ...ant-add-new-config-params-to-be-used-with.patch |   2 +-
 .../patches/463-add-mcast_rate-to-11s.patch        |   4 +-
 .../hostapd/patches/464-fix-mesh-obss-check.patch  |   2 +-
 .../patches/500-lto-jobserver-support.patch        |   2 +-
 .../hostapd/patches/600-ubus_support.patch         | 233 ++++--
 .../hostapd/patches/601-ucode_support.patch        | 543 ++++++++++++++
 .../services/hostapd/patches/700-wifi-reload.patch | 194 -----
 .../hostapd/patches/701-reload_config_inline.patch |  33 +
 .../hostapd/patches/710-vlan_no_bridge.patch       |   2 +-
 .../hostapd/patches/720-iface_max_num_sta.patch    |  15 +-
 .../services/hostapd/patches/730-ft_iface.patch    |   2 +-
 .../services/hostapd/patches/740-snoop_iface.patch |  85 ++-
 .../750-qos_map_set_without_interworking.patch     |   6 +-
 .../hostapd/patches/760-dynamic_own_ip.patch       |   2 +-
 .../hostapd/patches/761-shared_das_port.patch      |   2 +-
 .../hostapd/patches/770-radius_server.patch        | 154 ++++
 ...e-WNM_AP-functions-dependant-on-CONFIG_AP.patch |   4 +-
 .../hostapd/patches/991-Fix-OpenWrt-13156.patch    |   4 +-
 .../network/services/hostapd/src/hostapd/radius.c  | 715 ++++++++++++++++++
 package/network/services/hostapd/src/src/ap/ubus.c | 215 ++----
 .../network/services/hostapd/src/src/ap/ucode.c    | 808 ++++++++++++++++++++
 .../network/services/hostapd/src/src/ap/ucode.h    |  54 ++
 .../network/services/hostapd/src/src/utils/ucode.c | 335 +++++++++
 .../network/services/hostapd/src/src/utils/ucode.h |  29 +
 .../services/hostapd/src/wpa_supplicant/ubus.c     | 162 +----
 .../services/hostapd/src/wpa_supplicant/ubus.h     |  11 -
 .../services/hostapd/src/wpa_supplicant/ucode.c    | 281 +++++++
 .../services/hostapd/src/wpa_supplicant/ucode.h    |  49 ++
 66 files changed, 5512 insertions(+), 1854 deletions(-)

diff --git a/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh b/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh
index 5aaba9af26..860609305f 100644
--- a/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh
+++ b/package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh
@@ -15,12 +15,9 @@ MP_CONFIG_INT="mesh_retry_timeout mesh_confirm_timeout mesh_holding_timeout mesh
 MP_CONFIG_BOOL="mesh_auto_open_plinks mesh_fwding"
 MP_CONFIG_STRING="mesh_power_mode"
 
-NEWAPLIST=
-OLDAPLIST=
-NEWSPLIST=
-OLDSPLIST=
-NEWUMLIST=
-OLDUMLIST=
+wdev_tool() {
+	ucode /usr/share/hostap/wdev.uc "$@"
+}
 
 drv_mac80211_init_device_config() {
 	hostapd_common_add_device_config
@@ -29,7 +26,8 @@ drv_mac80211_init_device_config() {
 	config_add_string tx_burst
 	config_add_string distance
 	config_add_int beacon_int chanbw frag rts
-	config_add_int rxantenna txantenna antenna_gain txpower min_tx_power
+	config_add_int rxantenna txantenna txpower min_tx_power
+	config_add_int num_global_macaddr multiple_bssid
 	config_add_boolean noscan ht_coex acs_exclude_dfs background_radar
 	config_add_array ht_capab
 	config_add_array channels
@@ -490,12 +488,12 @@ ${channel:+channel=$channel}
 ${channel_list:+chanlist=$channel_list}
 ${hostapd_noscan:+noscan=1}
 ${tx_burst:+tx_queue_data2_burst=$tx_burst}
+${multiple_bssid:+mbssid=$multiple_bssid}
+#num_global_macaddr=$num_global_macaddr
 $base_cfg
 
 EOF
 	json_select ..
-	radio_md5sum=$(md5sum $hostapd_conf_file | cut -d" " -f1)
-	echo "radio_config_id=${radio_md5sum}" >> $hostapd_conf_file
 }
 
 mac80211_hostapd_setup_bss() {
@@ -522,6 +520,7 @@ mac80211_hostapd_setup_bss() {
 	cat >> /var/run/hostapd-$phy.conf <<EOF
 $hostapd_cfg
 bssid=$macaddr
+${default_macaddr:+#default_macaddr}
 ${dtim_period:+dtim_period=$dtim_period}
 ${max_listen_int:+max_listen_interval=$max_listen_int}
 EOF
@@ -538,47 +537,7 @@ mac80211_generate_mac() {
 	local phy="$1"
 	local id="${macidx:-0}"
 
-	local ref="$(cat /sys/class/ieee80211/${phy}/macaddress)"
-	local mask="$(cat /sys/class/ieee80211/${phy}/address_mask)"
-
-	[ "$mask" = "00:00:00:00:00:00" ] && {
-		mask="ff:ff:ff:ff:ff:ff";
-
-		[ "$(wc -l < /sys/class/ieee80211/${phy}/addresses)" -gt $id ] && {
-			addr="$(mac80211_get_addr "$phy" "$id")"
-			[ -n "$addr" ] && {
-				echo "$addr"
-				return
-			}
-		}
-	}
-
-	local oIFS="$IFS"; IFS=":"; set -- $mask; IFS="$oIFS"
-
-	local mask1=$1
-	local mask6=$6
-
-	local oIFS="$IFS"; IFS=":"; set -- $ref; IFS="$oIFS"
-
-	macidx=$(($id + 1))
-	[ "$((0x$mask1))" -gt 0 ] && {
-		b1="0x$1"
-		[ "$id" -gt 0 ] && \
-			b1=$(($b1 ^ ((($id - !($b1 & 2)) << 2)) | 0x2))
-		printf "%02x:%s:%s:%s:%s:%s" $b1 $2 $3 $4 $5 $6
-		return
-	}
-
-	[ "$((0x$mask6))" -lt 255 ] && {
-		printf "%s:%s:%s:%s:%s:%02x" $1 $2 $3 $4 $5 $(( 0x$6 ^ $id ))
-		return
-	}
-
-	off2=$(( (0x$6 + $id) / 0x100 ))
-	printf "%s:%s:%s:%s:%02x:%02x" \
-		$1 $2 $3 $4 \
-		$(( (0x$5 + $off2) % 0x100 )) \
-		$(( (0x$6 + $id) % 0x100 ))
+	wdev_tool "$phy" get_macaddr id=$id num_global=$num_global_macaddr mbssid=${multiple_bssid:-0}
 }
 
 get_board_phy_name() (
@@ -661,74 +620,6 @@ mac80211_check_ap() {
 	has_ap=1
 }
 
-mac80211_iw_interface_add() {
-	local phy="$1"
-	local ifname="$2"
-	local type="$3"
-	local wdsflag="$4"
-	local rc
-	local oldifname
-
-	iw phy "$phy" interface add "$ifname" type "$type" $wdsflag >/dev/null 2>&1
-	rc="$?"
-
-	[ "$rc" = 233 ] && {
-		# Device might have just been deleted, give the kernel some time to finish cleaning it up
-		sleep 1
-
-		iw phy "$phy" interface add "$ifname" type "$type" $wdsflag >/dev/null 2>&1
-		rc="$?"
-	}
-
-	[ "$rc" = 233 ] && {
-		# Keep matching pre-existing interface
-		[ -d "/sys/class/ieee80211/${phy}/device/net/${ifname}" ] && \
-		case "$(iw dev $ifname info | grep "^\ttype" | cut -d' ' -f2- 2>/dev/null)" in
-			"AP")
-				[ "$type" = "__ap" ] && rc=0
-				;;
-			"IBSS")
-				[ "$type" = "adhoc" ] && rc=0
-				;;
-			"managed")
-				[ "$type" = "managed" ] && rc=0
-				;;
-			"mesh point")
-				[ "$type" = "mp" ] && rc=0
-				;;
-			"monitor")
-				[ "$type" = "monitor" ] && rc=0
-				;;
-		esac
-	}
-
-	[ "$rc" = 233 ] && {
-		iw dev "$ifname" del >/dev/null 2>&1
-		[ "$?" = 0 ] && {
-			sleep 1
-
-			iw phy "$phy" interface add "$ifname" type "$type" $wdsflag >/dev/null 2>&1
-			rc="$?"
-		}
-	}
-
-	[ "$rc" != 0 ] && {
-		# Device might not support virtual interfaces, so the interface never got deleted in the first place.
-		# Check if the interface already exists, and avoid failing in this case.
-		[ -d "/sys/class/ieee80211/${phy}/device/net/${ifname}" ] && rc=0
-	}
-
-	[ "$rc" != 0 ] && {
-		# Device doesn't support virtual interfaces and may have existing interface other than ifname.
-		oldifname="$(basename "/sys/class/ieee80211/${phy}/device/net"/* 2>/dev/null)"
-		[ "$oldifname" ] && ip link set "$oldifname" name "$ifname" 1>/dev/null 2>&1
-		rc="$?"
-	}
-
-	[ "$rc" != 0 ] && echo "Failed to create interface $ifname"
-	return $rc
-}
-
 mac80211_set_ifname() {
 	local phy="$1"
 	local prefix="$2"
@@ -752,21 +643,23 @@ mac80211_prepare_vif() {
 		mac80211_set_ifname "$phy" "$prefix"
 	}
 
+	append active_ifnames "$ifname"
 	set_default wds 0
 	set_default powersave 0
+	json_add_string _ifname "$ifname"
 
-	json_select ..
-
+	default_macaddr=
 	if [ -z "$macaddr" ]; then
 		macaddr="$(mac80211_generate_mac $phy)"
 		macidx="$(($macidx + 1))"
+		default_macaddr=1
 	elif [ "$macaddr" = 'random' ]; then
 		macaddr="$(macaddr_random)"
 	fi
+	json_add_string _macaddr "$macaddr"
+	json_add_string _default_macaddr "$default_macaddr"
+	json_select ..
 
-	json_add_object data
-	json_add_string ifname "$ifname"
-	json_close_object
 
 	[ "$mode" == "ap" ] && {
 		[ -z "$wpa_psk_file" ] && hostapd_set_psk "$ifname"
@@ -777,9 +670,6 @@ mac80211_prepare_vif() {
 
 	# It is far easier to delete and create the desired interface
 	case "$mode" in
-		adhoc)
-			mac80211_iw_interface_add "$phy" "$ifname" adhoc || return
-		;;
 		ap)
 			# Hostapd will handle recreating the interface and
 			# subsequent virtual APs belonging to the same PHY
@@ -791,114 +681,16 @@ mac80211_prepare_vif() {
 
 			mac80211_hostapd_setup_bss "$phy" "$ifname" "$macaddr" "$type" || return
 
-			NEWAPLIST="${NEWAPLIST}$ifname "
 			[ -n "$hostapd_ctrl" ] || {
 				ap_ifname="${ifname}"
 				hostapd_ctrl="${hostapd_ctrl:-/var/run/hostapd/$ifname}"
 			}
 		;;
-		mesh)
-			mac80211_iw_interface_add "$phy" "$ifname" mp || return
-		;;
-		monitor)
-			mac80211_iw_interface_add "$phy" "$ifname" monitor || return
-		;;
-		sta)
-			local wdsflag=
-			[ "$enable" = 0 ] || staidx="$(($staidx + 1))"
-			[ "$wds" -gt 0 ] && wdsflag="4addr on"
-			mac80211_iw_interface_add "$phy" "$ifname" managed "$wdsflag" || return
-			if [ "$wds" -gt 0 ]; then
-				iw "$ifname" set 4addr on
-			else
-				iw "$ifname" set 4addr off
-			fi
-			[ "$powersave" -gt 0 ] && powersave="on" || powersave="off"
-			iw "$ifname" set power_save "$powersave"
-		;;
-	esac
-
-	case "$mode" in
-		monitor|mesh)
-			[ "$auto_channel" -gt 0 ] || iw dev "$ifname" set channel "$channel" $iw_htmode
-		;;
 	esac
 
-	if [ "$mode" != "ap" ]; then
-		# ALL ap functionality will be passed to hostapd
-		# All interfaces must have unique mac addresses
-		# which can either be explicitly set in the device
-		# section, or automatically generated
-		ip link set dev "$ifname" address "$macaddr"
-	fi
-
 	json_select ..
 }
 
-mac80211_setup_supplicant() {
-	local enable=$1
-	local add_sp=0
-	local spobj="$(ubus -S list | grep wpa_supplicant.${ifname})"
-
-	[ "$enable" = 0 ] && {
-		ubus call wpa_supplicant.${phy} config_remove "{\"iface\":\"$ifname\"}"
-		ip link set dev "$ifname" down
-		iw dev "$ifname" del
-		return 0
-	}
-
-	wpa_supplicant_prepare_interface "$ifname" nl80211 || {
-		iw dev "$ifname" del
-		return 1
-	}
-	if [ "$mode" = "sta" ]; then
-		wpa_supplicant_add_network "$ifname"
-	else
-		wpa_supplicant_add_network "$ifname" "$freq" "$htmode" "$noscan"
-	fi
-
-	NEWSPLIST="${NEWSPLIST}$ifname "
-
-	if [ "${NEWAPLIST%% *}" != "${OLDAPLIST%% *}" ]; then
-		[ "$spobj" ] && ubus call wpa_supplicant config_remove "{\"iface\":\"$ifname\"}"
-		add_sp=1
-	fi
-	[ -z "$spobj" ] && add_sp=1
-
-	NEW_MD5_SP=$(test -e "${_config}" && md5sum ${_config})
-	OLD_MD5_SP=$(uci -q -P /var/state get wireless._${phy}.md5_${ifname})
-	if [ "$add_sp" = "1" ]; then
-		wpa_supplicant_run "$ifname" "$hostapd_ctrl"
-	else
-		[ "${NEW_MD5_SP}" == "${OLD_MD5_SP}" ] || ubus call $spobj reload
-	fi
-	uci -q -P /var/state set wireless._${phy}.md5_${ifname}="${NEW_MD5_SP}"
-	return 0
-}
-
-mac80211_setup_supplicant_noctl() {
-	local enable=$1
-	local spobj="$(ubus -S list | grep wpa_supplicant.${ifname})"
-	wpa_supplicant_prepare_interface "$ifname" nl80211 || {
-		iw dev "$ifname" del
-		return 1
-	}
-
-	wpa_supplicant_add_network "$ifname" "$freq" "$htmode" "$noscan"
-
-	NEWSPLIST="${NEWSPLIST}$ifname "
-	[ "$enable" = 0 ] && {
-		ubus call wpa_supplicant config_remove "{\"iface\":\"$ifname\"}"
-		ip link set dev "$ifname" down
-		return 0
-	}
-	if [ -z "$spobj" ]; then
-		wpa_supplicant_run "$ifname"
-	else
-		ubus call $spobj reload
-	fi
-}
-
 mac80211_prepare_iw_htmode() {
 	case "$htmode" in
 		VHT20|HT20|HE20) iw_htmode=HT20;;
@@ -936,6 +728,13 @@ mac80211_prepare_iw_htmode() {
 	esac
 }
 
+mac80211_add_mesh_params() {
+	for var in $MP_CONFIG_INT $MP_CONFIG_BOOL $MP_CONFIG_STRING; do
+		eval "mp_val=\"\$$var\""
+		[ -n "$mp_val" ] && json_add_string "$var" "$mp_val"
+	done
+}
+
 mac80211_setup_adhoc() {
 	local enable=$1
 	json_get_vars bssid ssid key mcast_rate
@@ -977,82 +776,215 @@ mac80211_setup_adhoc() {
 	mcval=
 	[ -n "$mcast_rate" ] && wpa_supplicant_add_rate mcval "$mcast_rate"
 
-	iw dev "$ifname" set type ibss
-	iw dev "$ifname" ibss join "$ssid" $freq $iw_htmode fixed-freq $bssid \
-		beacon-interval $beacon_int \
-		${brstr:+basic-rates $brstr} \
-		${mcval:+mcast-rate $mcval} \
-		${keyspec:+keys $keyspec}
+	local prev
+	json_set_namespace wdev_uc prev
+
+	json_add_object "$ifname"
+	json_add_string mode adhoc
+	[ -n "$default_macaddr" ] || json_add_string macaddr "$macaddr"
+	json_add_string ssid "$ssid"
+	json_add_string freq "$freq"
+	json_add_string htmode "$iw_htmode"
+	[ -n "$bssid" ] && json_add_string bssid "$bssid"
+	json_add_int beacon-interval "$beacon_int"
+	[ -n "$brstr" ] && json_add_string basic-rates "$brstr"
+	[ -n "$mcval" ] && json_add_string mcast-rate "$mcval"
+	[ -n "$keyspec" ] && json_add_string keys "$keyspec"
+	json_close_object
+
+	json_set_namespace "$prev"
 }
 
 mac80211_setup_mesh() {
-	local enable=$1
 	json_get_vars ssid mesh_id mcast_rate
 
-	NEWUMLIST="${NEWUMLIST}$ifname "
-
-	[ "$enable" = 0 ] && {
-		ip link set dev "$ifname" down
-		return 0
-	}
-
 	mcval=
 	[ -n "$mcast_rate" ] && wpa_supplicant_add_rate mcval "$mcast_rate"
 	[ -n "$mesh_id" ] && ssid="$mesh_id"
 
-	iw dev "$ifname" mesh join "$ssid" freq $freq $iw_htmode \
-		${mcval:+mcast-rate $mcval} \
-		beacon-interval $beacon_int
+	local prev
+	json_set_namespace wdev_uc prev
+
+	json_add_object "$ifname"
+	json_add_string mode mesh
+	[ -n "$default_macaddr" ] || json_add_string macaddr "$macaddr"
+	json_add_string ssid "$ssid"
+	json_add_string freq "$freq"
+	json_add_string htmode "$iw_htmode"
+	[ -n "$mcval" ] && json_add_string mcast-rate "$mcval"
+	json_add_int beacon-interval "$beacon_int"
+	mac80211_add_mesh_params
+
+	json_close_object
+
+	json_set_namespace "$prev"
 }
 
-mac80211_setup_vif() {
+mac80211_setup_monitor() {
+	local prev
+	json_set_namespace wdev_uc prev
+
+	json_add_object "$ifname"
+	json_add_string mode monitor
+	[ -n "$freq" ] && json_add_string freq "$freq"
+	json_add_string htmode "$iw_htmode"
+	json_close_object
+
+	json_set_namespace "$prev"
+}
+
+mac80211_set_vif_txpower() {
 	local name="$1"
-	local failed
-	local action=up
 
-	json_select data
-	json_get_vars ifname
+	json_select config
+	json_get_var ifname _ifname
+	json_get_vars vif_txpower
 	json_select ..
 
-	json_select config
-	json_get_vars mode
-	json_get_var vif_txpower
-	json_get_var vif_enable enable 1
-
-	[ "$vif_enable" = 1 ] || action=down
-	if [ "$mode" != "ap" ] || [ "$ifname" = "$ap_ifname" ]; then
-		ip link set dev "$ifname" "$action" || {
-			wireless_setup_vif_failed IFUP_ERROR
-			json_select ..
-			return
-		}
-		[ -z "$vif_txpower" ] || iw dev "$ifname" set txpower fixed "${vif_txpower%%.*}00"
+	[ -z "$vif_txpower" ] || iw dev "$ifname" set txpower fixed "${vif_txpower%%.*}00"
+}
+
+wpa_supplicant_init_config() {
+	json_set_namespace wpa_supp prev
+
+	json_init
+	json_add_array config
+
+	json_set_namespace "$prev"
+}
+
+wpa_supplicant_add_interface() {
+	local ifname="$1"
+	local mode="$2"
+	local prev
+
+	_wpa_supplicant_common "$ifname"
+
+	json_set_namespace wpa_supp prev
+
+	json_add_object
+	json_add_string ctrl "$_rpath"
+	json_add_string iface "$ifname"
+	json_add_string mode "$mode"
+	json_add_string config "$_config"
+	[ -n "$default_macaddr" ] || json_add_string macaddr "$macaddr"
+	[ -n "$network_bridge" ] && json_add_string bridge "$network_bridge"
+	[ -n "$wds" ] && json_add_boolean 4addr "$wds"
+	json_add_boolean powersave "$powersave"
+	[ "$mode" = "mesh" ] && mac80211_add_mesh_params
+	json_close_object
+
+	json_set_namespace "$prev"
+
+	wpa_supp_init=1
+}
+
+wpa_supplicant_set_config() {
+	local phy="$1"
+	local prev
+
+	json_set_namespace wpa_supp prev
+	json_close_array
+	json_add_string phy "$phy"
+	json_add_boolean defer 1
+	local data="$(json_dump)"
+
+	json_cleanup
+	json_set_namespace "$prev"
+
+	ubus -S -t 0 wait_for wpa_supplicant || {
+		[ -n "$wpa_supp_init" ] || return 0
+
+		ubus wait_for wpa_supplicant
+	}
+
+	local supplicant_res="$(ubus call wpa_supplicant config_set "$data")"
+	ret="$?"
+	[ "$ret" != 0 -o -z "$supplicant_res" ] && wireless_setup_vif_failed WPA_SUPPLICANT_FAILED
+
+	wireless_add_process "$(jsonfilter -s "$supplicant_res" -l 1 -e @.pid)" "/usr/sbin/wpa_supplicant" 1 1
+
+}
+
+hostapd_set_config() {
+	[ -n "$hostapd_ctrl" ] || {
+		ubus call hostapd config_set '{ "phy": "'"$phy"'", "config": "", "prev_config": "'"${hostapd_conf_file}.prev"'" }' > /dev/null
+		return 0;
+	}
+
+	ubus wait_for hostapd
+	local hostapd_res="$(ubus call hostapd config_set "{ \"phy\": \"$phy\", \"config\":\"${hostapd_conf_file}\", \"prev_config\": \"${hostapd_conf_file}.prev\"}")"
+	ret="$?"
+	[ "$ret" != 0 -o -z "$hostapd_res" ] && {
+		wireless_setup_failed HOSTAPD_START_FAILED
+		return
+	}
+	wireless_add_process "$(jsonfilter -s "$hostapd_res" -l 1 -e @.pid)" "/usr/sbin/hostapd" 1 1
+}
+
+
+wpa_supplicant_start() {
+	local phy="$1"
+
+	[ -n "$wpa_supp_init" ] || return 0
+
+	ubus call wpa_supplicant config_set '{ "phy": "'"$phy"'" }' > /dev/null
+}
+
+mac80211_setup_supplicant() {
+	local enable=$1
+	local add_sp=0
+
+	wpa_supplicant_prepare_interface "$ifname" nl80211 || return 1
+
+	if [ "$mode" = "sta" ]; then
+		wpa_supplicant_add_network "$ifname"
+	else
+		wpa_supplicant_add_network "$ifname" "$freq" "$htmode" "$noscan"
 	fi
 
+	wpa_supplicant_add_interface "$ifname" "$mode"
+
+	return 0
+}
+
+mac80211_setup_vif() {
+	local name="$1"
+	local failed
+
+	json_select config
+	json_get_var ifname _ifname
+	json_get_var macaddr _macaddr
+	json_get_var default_macaddr _default_macaddr
+	json_get_vars mode wds powersave
+
+	set_default powersave 0
+	set_default wds 0
+
 	case "$mode" in
 		mesh)
+			json_get_vars $MP_CONFIG_INT $MP_CONFIG_BOOL $MP_CONFIG_STRING
 			wireless_vif_parse_encryption
 			[ -z "$htmode" ] && htmode="NOHT";
-			if wpa_supplicant -vmesh || [ "$wpa" -gt 0 -o "$auto_channel" -gt 0 ] || chan_is_dfs "$phy" "$channel"; then
-				mac80211_setup_supplicant $vif_enable || failed=1
+			if wpa_supplicant -vmesh; then
+				mac80211_setup_supplicant || failed=1
 			else
-				mac80211_setup_mesh $vif_enable
+				mac80211_setup_mesh
 			fi
-			for var in $MP_CONFIG_INT $MP_CONFIG_BOOL $MP_CONFIG_STRING; do
-				json_get_var mp_val "$var"
-				[ -n "$mp_val" ] && iw dev "$ifname" set mesh_param "$var" "$mp_val"
-			done
 		;;
 		adhoc)
 			wireless_vif_parse_encryption
 			if [ "$wpa" -gt 0 -o "$auto_channel" -gt 0 ]; then
-				mac80211_setup_supplicant_noctl $vif_enable || failed=1
+				mac80211_setup_supplicant || failed=1
 			else
-				mac80211_setup_adhoc $vif_enable
+				mac80211_setup_adhoc
 			fi
 		;;
 		sta)
-			mac80211_setup_supplicant $vif_enable || failed=1
+			mac80211_setup_supplicant || failed=1
+		;;
+		monitor)
+			mac80211_setup_monitor
 		;;
 	esac
 
@@ -1085,7 +1017,6 @@ band_match && $3 == "MHz" && $4 == channel {
 '
 }
 
-
 chan_is_dfs() {
 	local phy="$1"
 	local chan="$2"
@@ -1093,27 +1024,6 @@ chan_is_dfs() {
 	return $!
 }
 
-mac80211_vap_cleanup() {
-	local service="$1"
-	local vaps="$2"
-
-	for wdev in $vaps; do
-		[ "$service" != "none" ] && ubus call ${service} config_remove "{\"iface\":\"$wdev\"}"
-		ip link set dev "$wdev" down 2>/dev/null
-		iw dev "$wdev" del
-	done
-}
-
-mac80211_interface_cleanup() {
-	local phy="$1"
-	local primary_ap=$(uci -q -P /var/state get wireless._${phy}.aplist)
-	primary_ap=${primary_ap%% *}
-
-	mac80211_vap_cleanup hostapd "${primary_ap}"
-	mac80211_vap_cleanup wpa_supplicant "$(uci -q -P /var/state get wireless._${phy}.splist)"
-	mac80211_vap_cleanup none "$(uci -q -P /var/state get wireless._${phy}.umlist)"
-}
-
 mac80211_set_noscan() {
 	hostapd_noscan=1
 }
@@ -1122,49 +1032,44 @@ drv_mac80211_cleanup() {
 	hostapd_common_cleanup
 }
 
+mac80211_reset_config() {
+	local phy="$1"
+
+	hostapd_conf_file="/var/run/hostapd-$phy.conf"
+	ubus call hostapd config_set '{ "phy": "'"$phy"'", "config": "", "prev_config": "'"$hostapd_conf_file"'" }' > /dev/null
+	ubus call wpa_supplicant config_set '{ "phy": "'"$phy"'", "config": [] }' > /dev/null
+	wdev_tool "$phy" set_config '{}'
+}
+
 drv_mac80211_setup() {
 	json_select config
 	json_get_vars \
 		phy macaddr path \
 		country chanbw distance \
-		txpower antenna_gain \
+		txpower \
 		rxantenna txantenna \
-		frag rts beacon_int:100 htmode
+		frag rts beacon_int:100 htmode \
+		num_global_macaddr:1 multiple_bssid
 	json_get_values basic_rate_list basic_rate
 	json_get_values scan_list scan_list
 	json_select ..
 
+	json_select data && {
+		json_get_var prev_rxantenna rxantenna
+		json_get_var prev_txantenna txantenna
+		json_select ..
+	}
+
 	find_phy || {
 		echo "Could not find PHY for device '$1'"
 		wireless_set_retry 0
 		return 1
 	}
 
-	wireless_set_data phy="$phy"
-	[ -z "$(uci -q -P /var/state show wireless._${phy})" ] && uci -q -P /var/state set wireless._${phy}=phy
-
-	OLDAPLIST=$(uci -q -P /var/state get wireless._${phy}.aplist)
-	OLDSPLIST=$(uci -q -P /var/state get wireless._${phy}.splist)
-	OLDUMLIST=$(uci -q -P /var/state get wireless._${phy}.umlist)
-
 	local wdev
 	local cwdev
 	local found
 
-	for wdev in $(list_phy_interfaces "$phy"); do
-		found=0
-		for cwdev in $OLDAPLIST $OLDSPLIST $OLDUMLIST; do
-			if [ "$wdev" = "$cwdev" ]; then
-				found=1
-				break
-			fi
-		done
-		if [ "$found" = "0" ]; then
-			ip link set dev "$wdev" down
-			iw dev "$wdev" del
-		fi
-	done
-
 	# convert channel to frequency
 	[ "$auto_channel" -gt 0 ] || freq="$(get_freq "$phy" "$channel" "$band")"
 
@@ -1177,7 +1082,6 @@ drv_mac80211_setup() {
 
 	hostapd_conf_file="/var/run/hostapd-$phy.conf"
 
-	no_ap=1
 	macidx=0
 	staidx=0
 
@@ -1190,13 +1094,14 @@ drv_mac80211_setup() {
 	set_default rxantenna 0xffffffff
 	set_default txantenna 0xffffffff
 	set_default distance 0
-	set_default antenna_gain 0
 
 	[ "$txantenna" = "all" ] && txantenna=0xffffffff
 	[ "$rxantenna" = "all" ] && rxantenna=0xffffffff
 
+	[ "$rxantenna" = "$prev_rxantenna" -a "$txantenna" = "$prev_txantenna" ] || mac80211_reset_config "$phy"
+	wireless_set_data phy="$phy" txantenna="$txantenna" rxantenna="$rxantenna"
+
 	iw phy "$phy" set antenna $txantenna $rxantenna >/dev/null 2>&1
-	iw phy "$phy" set antenna_gain $antenna_gain >/dev/null 2>&1
 	iw phy "$phy" set distance "$distance" >/dev/null 2>&1
 
 	if [ -n "$txpower" ]; then
@@ -1212,78 +1117,36 @@ drv_mac80211_setup() {
 	hostapd_ctrl=
 	ap_ifname=
 	hostapd_noscan=
+	wpa_supp_init=
 	for_each_interface "ap" mac80211_check_ap
 
-	rm -f "$hostapd_conf_file"
+	[ -f "$hostapd_conf_file" ] && mv "$hostapd_conf_file" "$hostapd_conf_file.prev"
 
 	for_each_interface "sta adhoc mesh" mac80211_set_noscan
 	[ -n "$has_ap" ] && mac80211_hostapd_setup_base "$phy"
 
-	mac80211_prepare_iw_htmode
-	for_each_interface "sta adhoc mesh monitor" mac80211_prepare_vif
-	NEWAPLIST=
-	for_each_interface "ap" mac80211_prepare_vif
-	NEW_MD5=$(test -e "${hostapd_conf_file}" && md5sum ${hostapd_conf_file})
-	OLD_MD5=$(uci -q -P /var/state get wireless._${phy}.md5)
-	if [ "${NEWAPLIST}" != "${OLDAPLIST}" ]; then
-		mac80211_vap_cleanup hostapd "${OLDAPLIST}"
-	fi
-	[ -n "${NEWAPLIST}" ] && mac80211_iw_interface_add "$phy" "${NEWAPLIST%% *}" __ap
-	local add_ap=0
-	local primary_ap=${NEWAPLIST%% *}
-	[ -n "$hostapd_ctrl" ] && {
-		local no_reload=1
-		if [ -n "$(ubus list | grep hostapd.$primary_ap)" ]; then
-			no_reload=0
-			[ "${NEW_MD5}" = "${OLD_MD5}" ] || {
-				ubus call hostapd.$primary_ap reload
-				no_reload=$?
-				if [ "$no_reload" != "0" ]; then
-					mac80211_vap_cleanup hostapd "${OLDAPLIST}"
-					mac80211_vap_cleanup wpa_supplicant "$(uci -q -P /var/state get wireless._${phy}.splist)"
-					mac80211_vap_cleanup none "$(uci -q -P /var/state get wireless._${phy}.umlist)"
-					sleep 2
-					mac80211_iw_interface_add "$phy" "${NEWAPLIST%% *}" __ap
-					for_each_interface "sta adhoc mesh monitor" mac80211_prepare_vif
-				fi
-			}
-		fi
-		if [ "$no_reload" != "0" ]; then
-			add_ap=1
-			ubus wait_for hostapd
-			local hostapd_res="$(ubus call hostapd config_add "{\"iface\":\"$primary_ap\", \"config\":\"${hostapd_conf_file}\"}")"
-			ret="$?"
-			[ "$ret" != 0 -o -z "$hostapd_res" ] && {
-				wireless_setup_failed HOSTAPD_START_FAILED
-				return
-			}
-			wireless_add_process "$(jsonfilter -s "$hostapd_res" -l 1 -e @.pid)" "/usr/sbin/hostapd" 1 1
-		fi
-	}
-	uci -q -P /var/state set wireless._${phy}.aplist="${NEWAPLIST}"
-	uci -q -P /var/state set wireless._${phy}.md5="${NEW_MD5}"
+	local prev
+	json_set_namespace wdev_uc prev
+	json_init
+	json_set_namespace "$prev"
+
+	wpa_supplicant_init_config
 
-	[ "${add_ap}" = 1 ] && sleep 1
-	for_each_interface "ap" mac80211_setup_vif
+	mac80211_prepare_iw_htmode
+	active_ifnames=
+	for_each_interface "ap sta adhoc mesh monitor" mac80211_prepare_vif
+	for_each_interface "ap sta adhoc mesh monitor" mac80211_setup_vif
 
-	NEWSPLIST=
-	NEWUMLIST=
+	[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_set_config "$phy"
+	[ -x /usr/sbin/hostapd ] && hostapd_set_config "$phy"
 
-	for_each_interface "sta adhoc mesh monitor" mac80211_setup_vif
+	[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_start "$phy"
 
-	uci -q -P /var/state set wireless._${phy}.splist="${NEWSPLIST}"
-	uci -q -P /var/state set wireless._${phy}.umlist="${NEWUMLIST}"
+	json_set_namespace wdev_uc prev
+	wdev_tool "$phy" set_config "$(json_dump)" $active_ifnames
+	json_set_namespace "$prev"
 
-	local foundvap
-	local dropvap=""
-	for oldvap in $OLDSPLIST; do
-		foundvap=0
-		for newvap in $NEWSPLIST; do
-			[ "$oldvap" = "$newvap" ] && foundvap=1
-		done
-		[ "$foundvap" = "0" ] && dropvap="$dropvap $oldvap"
-	done
-	[ -n "$dropvap" ] && mac80211_vap_cleanup wpa_supplicant "$dropvap"
+	for_each_interface "ap sta adhoc mesh monitor" mac80211_set_vif_txpower
 	wireless_set_up
 }
 
@@ -1314,8 +1177,12 @@ drv_mac80211_teardown() {
 		return 1
 	}
 
-	mac80211_interface_cleanup "$phy"
-	uci -q -P /var/state revert wireless._${phy}
+	mac80211_reset_config "$phy"
+
+	for wdev in $(list_phy_interfaces "$phy"); do
+		ip link set dev "$wdev" down
+		iw dev "$wdev" del
+	done
 }
 
 add_driver mac80211
diff --git a/package/network/services/hostapd/Config.in b/package/network/services/hostapd/Config.in
index 8f28eb2bd4..87ad7e093e 100644
--- a/package/network/services/hostapd/Config.in
+++ b/package/network/services/hostapd/Config.in
@@ -73,11 +73,6 @@ config WPA_WOLFSSL
 	select WOLFSSL_HAS_SESSION_TICKET
 	select WOLFSSL_HAS_WPAS
 
-config DRIVER_WEXT_SUPPORT
-	bool
-	select KERNEL_WIRELESS_EXT
-	default n
-
 config DRIVER_11AC_SUPPORT
 	bool
 	default n
diff --git a/package/network/services/hostapd/Makefile b/package/network/services/hostapd/Makefile
index 287a70c80e..7b94709e20 100644
--- a/package/network/services/hostapd/Makefile
+++ b/package/network/services/hostapd/Makefile
@@ -5,13 +5,13 @@
 include $(TOPDIR)/rules.mk
 
 PKG_NAME:=hostapd
-PKG_RELEASE:=1.2
+PKG_RELEASE:=3
 
 PKG_SOURCE_URL:=http://w1.fi/hostap.git
 PKG_SOURCE_PROTO:=git
-PKG_SOURCE_DATE:=2023-06-22
-PKG_SOURCE_VERSION:=599d00be9de2846c6ea18c1487d8329522ade22b
-PKG_MIRROR_HASH:=828810c558ea181e45ed0c8b940f5c41e55775e2979a15aed8cf0ab17dd7723c
+PKG_SOURCE_DATE:=2023-09-08
+PKG_SOURCE_VERSION:=e5ccbfc69ecf297590341ae8b461edba9d8e964c
+PKG_MIRROR_HASH:=fcc6550f46c7f8bbdbf71e63f8f699b9a0878565ad1b90a17855f5ec21283b8f
 
 PKG_MAINTAINER:=Felix Fietkau <nbd at nbd.name>
 PKG_LICENSE:=BSD-3-Clause
@@ -21,13 +21,10 @@ PKG_BUILD_PARALLEL:=1
 PKG_ASLR_PIE_REGULAR:=1
 
 PKG_CONFIG_DEPENDS:= \
-	CONFIG_PACKAGE_kmod-ath9k \
-	CONFIG_PACKAGE_kmod-cfg80211 \
 	CONFIG_PACKAGE_hostapd \
 	CONFIG_PACKAGE_hostapd-basic \
 	CONFIG_PACKAGE_hostapd-mini \
 	CONFIG_WPA_RFKILL_SUPPORT \
-	CONFIG_DRIVER_WEXT_SUPPORT \
 	CONFIG_DRIVER_11AC_SUPPORT \
 	CONFIG_DRIVER_11AX_SUPPORT \
 	CONFIG_WPA_ENABLE_WEP
@@ -82,13 +79,14 @@ ifneq ($(CONFIG_DRIVER_11AX_SUPPORT),)
   HOSTAPD_IEEE80211AX:=y
 endif
 
+CORE_DEPENDS = +ucode +libubus +libucode +ucode-mod-fs +ucode-mod-nl80211 +ucode-mod-rtnl +ucode-mod-ubus +ucode-mod-uloop +libblobmsg-json
+
 DRIVER_MAKEOPTS= \
-	CONFIG_ACS=$(CONFIG_PACKAGE_kmod-cfg80211) \
-	CONFIG_DRIVER_NL80211=$(CONFIG_PACKAGE_kmod-cfg80211) \
+	CONFIG_ACS=y CONFIG_DRIVER_NL80211=y \
 	CONFIG_IEEE80211AC=$(HOSTAPD_IEEE80211AC) \
 	CONFIG_IEEE80211AX=$(HOSTAPD_IEEE80211AX) \
-	CONFIG_DRIVER_WEXT=$(CONFIG_DRIVER_WEXT_SUPPORT) \
-	CONFIG_MBO=$(CONFIG_WPA_MBO_SUPPORT)
+	CONFIG_MBO=$(CONFIG_WPA_MBO_SUPPORT) \
+	CONFIG_UCODE=y
 
 ifeq ($(SSL_VARIANT),openssl)
   DRIVER_MAKEOPTS += CONFIG_TLS=openssl CONFIG_SAE=y
@@ -141,7 +139,7 @@ ifneq ($(LOCAL_TYPE),hostapd)
   endif
 endif
 
-DRV_DEPENDS:=+PACKAGE_kmod-cfg80211:libnl-tiny
+DRV_DEPENDS:=+libnl-tiny
 
 
 define Package/hostapd/Default
@@ -150,7 +148,7 @@ define Package/hostapd/Default
   SUBMENU:=WirelessAPD
   TITLE:=IEEE 802.1x Authenticator
   URL:=http://hostap.epitest.fi/
-  DEPENDS:=$(DRV_DEPENDS) +hostapd-common +libubus
+  DEPENDS:=$(DRV_DEPENDS) +hostapd-common $(CORE_DEPENDS)
   EXTRA_DEPENDS:=hostapd-common (=$(PKG_VERSION)-$(PKG_RELEASE))
   USERID:=network=101:network=101
   PROVIDES:=hostapd
@@ -255,7 +253,7 @@ define Package/wpad/Default
   CATEGORY:=Network
   SUBMENU:=WirelessAPD
   TITLE:=IEEE 802.1x Auth/Supplicant
-  DEPENDS:=$(DRV_DEPENDS) +hostapd-common +libubus
+  DEPENDS:=$(DRV_DEPENDS) +hostapd-common $(CORE_DEPENDS)
   EXTRA_DEPENDS:=hostapd-common (=$(PKG_VERSION)-$(PKG_RELEASE))
   USERID:=network=101:network=101
   URL:=http://hostap.epitest.fi/
@@ -358,7 +356,7 @@ endef
 
 define Package/wpad-mesh
 $(call Package/wpad/Default,$(1))
-  DEPENDS+=@PACKAGE_kmod-cfg80211 @(!TARGET_uml||BROKEN)
+  DEPENDS+=@(!TARGET_uml||BROKEN)
   PROVIDES+=wpa-supplicant-mesh wpad-mesh
 endef
 
@@ -400,7 +398,7 @@ define Package/wpa-supplicant/Default
   SUBMENU:=WirelessAPD
   TITLE:=WPA Supplicant
   URL:=http://hostap.epitest.fi/wpa_supplicant/
-  DEPENDS:=$(DRV_DEPENDS) +hostapd-common +libubus
+  DEPENDS:=$(DRV_DEPENDS) +hostapd-common $(CORE_DEPENDS)
   EXTRA_DEPENDS:=hostapd-common (=$(PKG_VERSION)-$(PKG_RELEASE))
   USERID:=network=101:network=101
   PROVIDES:=wpa-supplicant
@@ -442,13 +440,12 @@ endef
 define Package/wpa-supplicant-p2p
 $(call Package/wpa-supplicant/Default,$(1))
   TITLE+= (Wi-Fi P2P support)
-  DEPENDS+=@PACKAGE_kmod-cfg80211
   VARIANT:=supplicant-p2p-internal
 endef
 
 define Package/wpa-supplicant-mesh/Default
 $(call Package/wpa-supplicant/Default,$(1))
-  DEPENDS+=@PACKAGE_kmod-cfg80211 @(!TARGET_uml||BROKEN)
+  DEPENDS+=@(!TARGET_uml||BROKEN)
   PROVIDES+=wpa-supplicant-mesh
 endef
 
@@ -522,7 +519,7 @@ define Package/eapol-test/Default
   SECTION:=net
   SUBMENU:=WirelessAPD
   CATEGORY:=Network
-  DEPENDS:=$(DRV_DEPENDS) +libubus
+  DEPENDS:=$(DRV_DEPENDS) $(CORE_DEPENDS)
 endef
 
 define Package/eapol-test
@@ -587,11 +584,7 @@ TARGET_CPPFLAGS := \
 	-D_GNU_SOURCE \
 	$(if $(CONFIG_WPA_MSG_MIN_PRIORITY),-DCONFIG_MSG_MIN_PRIORITY=$(CONFIG_WPA_MSG_MIN_PRIORITY))
 
-TARGET_LDFLAGS += -lubox -lubus
-
-ifdef CONFIG_PACKAGE_kmod-cfg80211
-  TARGET_LDFLAGS += -lm -lnl-tiny
-endif
+TARGET_LDFLAGS += -lubox -lubus -lblobmsg_json -lucode -lm -lnl-tiny
 
 ifdef CONFIG_WPA_ENABLE_WEP
     DRIVER_MAKEOPTS += CONFIG_WEP=y
@@ -676,22 +669,55 @@ define Build/Compile
 	$(Build/Compile/$(BUILD_VARIANT))
 endef
 
+define Install/hostapd/full
+	$(INSTALL_DIR) $(1)/etc/init.d $(1)/etc/config $(1)/etc/radius
+	ln -sf hostapd $(1)/usr/sbin/hostapd-radius
+	$(INSTALL_BIN) ./files/radius.init $(1)/etc/init.d/radius
+	$(INSTALL_DATA) ./files/radius.config $(1)/etc/config/radius
+	$(INSTALL_DATA) ./files/radius.clients $(1)/etc/radius/clients
+	$(INSTALL_DATA) ./files/radius.users $(1)/etc/radius/users
+endef
+
+define Package/hostapd-full/conffiles
+/etc/config/radius
+/etc/radius
+endef
+
+ifeq ($(CONFIG_VARIANT),full)
+Package/wpad-mesh-openssl/conffiles = $(Package/hostapd-full/conffiles)
+Package/wpad-mesh-wolfssl/conffiles = $(Package/hostapd-full/conffiles)
+Package/wpad-mesh-mbedtls/conffiles = $(Package/hostapd-full/conffiles)
+Package/wpad/conffiles = $(Package/hostapd-full/conffiles)
+Package/wpad-openssl/conffiles = $(Package/hostapd-full/conffiles)
+Package/wpad-wolfssl/conffiles = $(Package/hostapd-full/conffiles)
+Package/wpad-mbedtls/conffiles = $(Package/hostapd-full/conffiles)
+Package/hostapd/conffiles = $(Package/hostapd-full/conffiles)
+Package/hostapd-openssl/conffiles = $(Package/hostapd-full/conffiles)
+Package/hostapd-wolfssl/conffiles = $(Package/hostapd-full/conffiles)
+Package/hostapd-mbedtls/conffiles = $(Package/hostapd-full/conffiles)
+endif
+
 define Install/hostapd
-	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/hostap
+	$(INSTALL_DATA) ./files/hostapd.uc $(1)/usr/share/hostap/
+	$(if $(findstring full,$(CONFIG_VARIANT)),$(Install/hostapd/full))
 endef
 
 define Install/supplicant
-	$(INSTALL_DIR) $(1)/usr/sbin
+	$(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/hostap
+	$(INSTALL_DATA) ./files/wpa_supplicant.uc $(1)/usr/share/hostap/
 endef
 
 define Package/hostapd-common/install
-	$(INSTALL_DIR) $(1)/etc/capabilities $(1)/etc/rc.button $(1)/etc/hotplug.d/ieee80211 $(1)/etc/init.d $(1)/lib/netifd  $(1)/usr/share/acl.d
+	$(INSTALL_DIR) $(1)/etc/capabilities $(1)/etc/rc.button $(1)/etc/hotplug.d/ieee80211 $(1)/etc/init.d $(1)/lib/netifd  $(1)/usr/share/acl.d $(1)/usr/share/hostap
 	$(INSTALL_BIN) ./files/dhcp-get-server.sh $(1)/lib/netifd/dhcp-get-server.sh
 	$(INSTALL_DATA) ./files/hostapd.sh $(1)/lib/netifd/hostapd.sh
 	$(INSTALL_BIN) ./files/wpad.init $(1)/etc/init.d/wpad
 	$(INSTALL_BIN) ./files/wps-hotplug.sh $(1)/etc/rc.button/wps
 	$(INSTALL_DATA) ./files/wpad_acl.json $(1)/usr/share/acl.d
 	$(INSTALL_DATA) ./files/wpad.json $(1)/etc/capabilities
+	$(INSTALL_DATA) ./files/common.uc $(1)/usr/share/hostap/
+	$(INSTALL_DATA) ./files/wdev.uc $(1)/usr/share/hostap/
 endef
 
 define Package/hostapd/install
diff --git a/package/network/services/hostapd/files/common.uc b/package/network/services/hostapd/files/common.uc
new file mode 100644
index 0000000000..ccffe3eb43
--- /dev/null
+++ b/package/network/services/hostapd/files/common.uc
@@ -0,0 +1,318 @@
+import * as nl80211 from "nl80211";
+import * as rtnl from "rtnl";
+import { readfile, glob, basename, readlink } from "fs";
+
+const iftypes = {
+	ap: nl80211.const.NL80211_IFTYPE_AP,
+	mesh: nl80211.const.NL80211_IFTYPE_MESH_POINT,
+	sta: nl80211.const.NL80211_IFTYPE_STATION,
+	adhoc: nl80211.const.NL80211_IFTYPE_ADHOC,
+	monitor: nl80211.const.NL80211_IFTYPE_MONITOR,
+};
+
+function wdev_remove(name)
+{
+	nl80211.request(nl80211.const.NL80211_CMD_DEL_INTERFACE, 0, { dev: name });
+}
+
+function __phy_is_fullmac(phyidx)
+{
+	let data = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, 0, { wiphy: phyidx });
+
+	return !data.software_iftypes.ap_vlan;
+}
+
+function phy_is_fullmac(phy)
+{
+	let phyidx = int(trim(readfile(`/sys/class/ieee80211/${phy}/index`)));
+
+	return __phy_is_fullmac(phyidx);
+}
+
+function find_reusable_wdev(phyidx)
+{
+	if (!__phy_is_fullmac(phyidx))
+		return null;
+
+	let data = nl80211.request(
+		nl80211.const.NL80211_CMD_GET_INTERFACE,
+		nl80211.const.NLM_F_DUMP,
+		{ wiphy: phyidx });
+	for (let res in data)
+		if (trim(readfile(`/sys/class/net/${res.ifname}/operstate`)) == "down")
+			return res.ifname;
+	return null;
+}
+
+function wdev_create(phy, name, data)
+{
+	let phyidx = int(readfile(`/sys/class/ieee80211/${phy}/index`));
+
+	wdev_remove(name);
+
+	if (!iftypes[data.mode])
+		return `Invalid mode: ${data.mode}`;
+
+	let req = {
+		wiphy: phyidx,
+		ifname: name,
+		iftype: iftypes[data.mode],
+	};
+
+	if (data["4addr"])
+		req["4addr"] = data["4addr"];
+	if (data.macaddr)
+		req.mac = data.macaddr;
+
+	nl80211.error();
+
+	let reuse_ifname = find_reusable_wdev(phyidx);
+	if (reuse_ifname &&
+	    (reuse_ifname == name ||
+	     rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: reuse_ifname, ifname: name}) != false))
+		nl80211.request(
+			nl80211.const.NL80211_CMD_SET_INTERFACE, 0, {
+				wiphy: phyidx,
+				dev: name,
+				iftype: iftypes[data.mode],
+			});
+	else
+		nl80211.request(
+			nl80211.const.NL80211_CMD_NEW_INTERFACE,
+			nl80211.const.NLM_F_CREATE,
+			req);
+
+	let error = nl80211.error();
+	if (error)
+		return error;
+
+	if (data.powersave != null) {
+		nl80211.request(nl80211.const.NL80211_CMD_SET_POWER_SAVE, 0,
+			{ dev: name, ps_state: data.powersave ? 1 : 0});
+	}
+
+	return null;
+}
+
+function phy_sysfs_file(phy, name)
+{
+	return trim(readfile(`/sys/class/ieee80211/${phy}/${name}`));
+}
+
+function macaddr_split(str)
+{
+	return map(split(str, ":"), (val) => hex(val));
+}
+
+function macaddr_join(addr)
+{
+	return join(":", map(addr, (val) => sprintf("%02x", val)));
+}
+
+function wdev_macaddr(wdev)
+{
+	return trim(readfile(`/sys/class/net/${wdev}/address`));
+}
+
+const phy_proto = {
+	macaddr_init: function(used, options) {
+		this.macaddr_options = options ?? {};
+		this.macaddr_list = {};
+
+		if (type(used) == "object")
+			for (let addr in used)
+				this.macaddr_list[addr] = used[addr];
+		else
+			for (let addr in used)
+				this.macaddr_list[addr] = -1;
+
+		this.for_each_wdev((wdev) => {
+			let macaddr = wdev_macaddr(wdev);
+			this.macaddr_list[macaddr] ??= -1;
+		});
+
+		return this.macaddr_list;
+	},
+
+	macaddr_generate: function(data) {
+		let phy = this.name;
+		let idx = int(data.id ?? 0);
+		let mbssid = int(data.mbssid ?? 0) > 0;
+		let num_global = int(data.num_global ?? 1);
+		let use_global = !mbssid && idx < num_global;
+
+		let base_addr = phy_sysfs_file(phy, "macaddress");
+		if (!base_addr)
+			return null;
+
+		if (!idx && !mbssid)
+			return base_addr;
+
+		let base_mask = phy_sysfs_file(phy, "address_mask");
+		if (!base_mask)
+			return null;
+
+		if (base_mask == "00:00:00:00:00:00" && idx >= num_global) {
+			let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
+
+			if (idx < length(addrs))
+				return addrs[idx];
+
+			base_mask = "ff:ff:ff:ff:ff:ff";
+		}
+
+		let addr = macaddr_split(base_addr);
+		let mask = macaddr_split(base_mask);
+		let type;
+
+		if (mbssid)
+			type = "b5";
+		else if (use_global)
+			type = "add";
+		else if (mask[0] > 0)
+			type = "b1";
+		else if (mask[5] < 0xff)
+			type = "b5";
+		else
+			type = "add";
+
+		switch (type) {
+		case "b1":
+			if (!(addr[0] & 2))
+				idx--;
+			addr[0] |= 2;
+			addr[0] ^= idx << 2;
+			break;
+		case "b5":
+			if (mbssid)
+				addr[0] |= 2;
+			addr[5] ^= idx;
+			break;
+		default:
+			for (let i = 5; i > 0; i--) {
+				addr[i] += idx;
+				if (addr[i] < 256)
+					break;
+				addr[i] %= 256;
+			}
+			break;
+		}
+
+		return macaddr_join(addr);
+	},
+
+	macaddr_next: function(val) {
+		let data = this.macaddr_options ?? {};
+		let list = this.macaddr_list;
+
+		for (let i = 0; i < 32; i++) {
+			data.id = i;
+
+			let mac = this.macaddr_generate(data);
+			if (!mac)
+				return null;
+
+			if (list[mac] != null)
+				continue;
+
+			list[mac] = val != null ? val : -1;
+			return mac;
+		}
+	},
+
+	for_each_wdev: function(cb) {
+		let wdevs = glob(`/sys/class/ieee80211/${this.name}/device/net/*`);
+		wdevs = map(wdevs, (arg) => basename(arg));
+		for (let wdev in wdevs) {
+			if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != this.name)
+				continue;
+
+			cb(wdev);
+		}
+	}
+};
+
+function phy_open(phy)
+{
+	let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`);
+	if (!phyidx)
+		return null;
+
+	return proto({
+		name: phy,
+		idx: int(phyidx)
+	}, phy_proto);
+}
+
+const vlist_proto = {
+	update: function(values, arg) {
+		let data = this.data;
+		let cb = this.cb;
+		let seq = { };
+		let new_data = {};
+		let old_data = {};
+
+		this.data = new_data;
+
+		if (type(values) == "object") {
+			for (let key in values) {
+				old_data[key] = data[key];
+				new_data[key] = values[key];
+				delete data[key];
+			}
+		} else {
+			for (let val in values) {
+				let cur_key = val[0];
+				let cur_obj = val[1];
+
+				old_data[cur_key] = data[cur_key];
+				new_data[cur_key] = val[1];
+				delete data[cur_key];
+			}
+		}
+
+		for (let key in data) {
+			cb(null, data[key], arg);
+			delete data[key];
+		}
+		for (let key in new_data)
+			cb(new_data[key], old_data[key], arg);
+	}
+};
+
+function is_equal(val1, val2) {
+	let t1 = type(val1);
+
+	if (t1 != type(val2))
+		return false;
+
+	if (t1 == "array") {
+		if (length(val1) != length(val2))
+			return false;
+
+		for (let i = 0; i < length(val1); i++)
+			if (!is_equal(val1[i], val2[i]))
+				return false;
+
+		return true;
+	} else if (t1 == "object") {
+		for (let key in val1)
+			if (!is_equal(val1[key], val2[key]))
+				return false;
+		for (let key in val2)
+			if (val1[key] == null)
+				return false;
+		return true;
+	} else {
+		return val1 == val2;
+	}
+}
+
+function vlist_new(cb) {
+	return proto({
+			cb: cb,
+			data: {}
+		}, vlist_proto);
+}
+
+export { wdev_remove, wdev_create, is_equal, vlist_new, phy_is_fullmac, phy_open };
diff --git a/package/network/services/hostapd/files/hostapd.sh b/package/network/services/hostapd/files/hostapd.sh
index 28bd210623..c6ae6fb98b 100644
--- a/package/network/services/hostapd/files/hostapd.sh
+++ b/package/network/services/hostapd/files/hostapd.sh
@@ -121,6 +121,7 @@ hostapd_common_add_device_config() {
 	config_add_array hostapd_options
 
 	config_add_int airtime_mode
+	config_add_int mbssid
 
 	hostapd_add_log_config
 }
@@ -133,7 +134,8 @@ hostapd_prepare_device_config() {
 
 	json_get_vars country country3 country_ie beacon_int:100 doth require_mode legacy_rates \
 		acs_chan_bias local_pwr_constraint spectrum_mgmt_required airtime_mode cell_density \
-		rts_threshold beacon_rate rssi_reject_assoc_rssi rssi_ignore_probe_request maxassoc
+		rts_threshold beacon_rate rssi_reject_assoc_rssi rssi_ignore_probe_request maxassoc \
+		mbssid:0
 
 	hostapd_set_log_options base_cfg
 
@@ -234,6 +236,7 @@ hostapd_prepare_device_config() {
 	[ -n "$rts_threshold" ] && append base_cfg "rts_threshold=$rts_threshold" "$N"
 	[ "$airtime_mode" -gt 0 ] && append base_cfg "airtime_mode=$airtime_mode" "$N"
 	[ -n "$maxassoc" ] && append base_cfg "iface_max_num_sta=$maxassoc" "$N"
+	[ "$mbssid" -gt 0 ] && [ "$mbssid" -le 2 ] && append base_cfg "mbssid=$mbssid" "$N"
 
 	json_get_values opts hostapd_options
 	for val in $opts; do
@@ -625,8 +628,7 @@ hostapd_set_bss_options() {
 		[ -n "$wpa_strict_rekey" ] && append bss_conf "wpa_strict_rekey=$wpa_strict_rekey" "$N"
 	}
 
-	set_default nasid "${macaddr//\:}"
-	append bss_conf "nas_identifier=$nasid" "$N"
+	[ -n "$nasid" ] && append bss_conf "nas_identifier=$nasid" "$N"
 
 	[ -n "$acct_interval" ] && \
 		append bss_conf "radius_acct_interim_interval=$acct_interval" "$N"
@@ -864,8 +866,9 @@ hostapd_set_bss_options() {
 	[ "$bss_transition" -eq "1" ] && append bss_conf "bss_transition=1" "$N"
 	[ "$mbo" -eq 1 ] && append bss_conf "mbo=1" "$N"
 
-	json_get_vars ieee80211k rrm_neighbor_report rrm_beacon_report
+	json_get_vars ieee80211k rrm_neighbor_report rrm_beacon_report rnr
 	set_default ieee80211k 0
+	set_default rnr 0
 	if [ "$ieee80211k" -eq "1" ]; then
 		set_default rrm_neighbor_report 1
 		set_default rrm_beacon_report 1
@@ -876,6 +879,7 @@ hostapd_set_bss_options() {
 
 	[ "$rrm_neighbor_report" -eq "1" ] && append bss_conf "rrm_neighbor_report=1" "$N"
 	[ "$rrm_beacon_report" -eq "1" ] && append bss_conf "rrm_beacon_report=1" "$N"
+	[ "$rnr" -eq "1" ] && append bss_conf "rnr=1" "$N"
 
 	json_get_vars ftm_responder stationary_ap lci civic
 	set_default ftm_responder 0
@@ -1156,9 +1160,6 @@ hostapd_set_bss_options() {
 		append bss_conf "$val" "$N"
 	done
 
-	bss_md5sum="$(echo $bss_conf | md5sum | cut -d" " -f1)"
-	append bss_conf "config_id=$bss_md5sum" "$N"
-
 	append "$var" "$bss_conf" "$N"
 	return 0
 }
@@ -1588,29 +1589,6 @@ EOF
 	return 0
 }
 
-wpa_supplicant_run() {
-	local ifname="$1"
-	local hostapd_ctrl="$2"
-
-	_wpa_supplicant_common "$ifname"
-
-	ubus wait_for wpa_supplicant
-	local supplicant_res="$(ubus call wpa_supplicant config_add "{ \
-		\"driver\": \"${_w_driver:-wext}\", \"ctrl\": \"$_rpath\", \
-		\"iface\": \"$ifname\", \"config\": \"$_config\" \
-		${network_bridge:+, \"bridge\": \"$network_bridge\"} \
-		${hostapd_ctrl:+, \"hostapd_ctrl\": \"$hostapd_ctrl\"} \
-		}")"
-
-	ret="$?"
-
-	[ "$ret" != 0 -o -z "$supplicant_res" ] && wireless_setup_vif_failed WPA_SUPPLICANT_FAILED
-
-	wireless_add_process "$(jsonfilter -s "$supplicant_res" -l 1 -e @.pid)" "/usr/sbin/wpa_supplicant" 1 1
-
-	return $ret
-}
-
 hostapd_common_cleanup() {
 	killall meshd-nl80211
 }
diff --git a/package/network/services/hostapd/files/hostapd.uc b/package/network/services/hostapd/files/hostapd.uc
new file mode 100644
index 0000000000..ebf732bea5
--- /dev/null
+++ b/package/network/services/hostapd/files/hostapd.uc
@@ -0,0 +1,809 @@
+let libubus = require("ubus");
+import { open, readfile } from "fs";
+import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open } from "common";
+
+let ubus = libubus.connect();
+
+hostapd.data.config = {};
+
+hostapd.data.file_fields = {
+	vlan_file: true,
+	wpa_psk_file: true,
+	accept_mac_file: true,
+	deny_mac_file: true,
+	eap_user_file: true,
+	ca_cert: true,
+	server_cert: true,
+	server_cert2: true,
+	private_key: true,
+	private_key2: true,
+	dh_file: true,
+	eap_sim_db: true,
+};
+
+function iface_remove(cfg)
+{
+	if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname)
+		return;
+
+	hostapd.remove_iface(cfg.bss[0].ifname);
+	for (let bss in cfg.bss)
+		wdev_remove(bss.ifname);
+}
+
+function iface_gen_config(phy, config, start_disabled)
+{
+	let str = `data:
+${join("\n", config.radio.data)}
+channel=${config.radio.channel}
+`;
+
+	for (let i = 0; i < length(config.bss); i++) {
+		let bss = config.bss[i];
+		let type = i > 0 ? "bss" : "interface";
+		let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
+
+		str += `
+${type}=${bss.ifname}
+bssid=${bss.bssid}
+${join("\n", bss.data)}
+nas_identifier=${nasid}
+`;
+		if (start_disabled)
+			str += `
+start_disabled=1
+`;
+	}
+
+	return str;
+}
+
+function iface_freq_info(iface, config, params)
+{
+	let freq = params.frequency;
+	if (!freq)
+		return null;
+
+	let sec_offset = params.sec_chan_offset;
+	if (sec_offset != -1 && sec_offset != 1)
+		sec_offset = 0;
+
+	let width = 0;
+	for (let line in config.radio.data) {
+		if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
+			sec_offset = null; // auto-detect
+			continue;
+		}
+
+		let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
+		if (!val)
+			continue;
+
+		val = int(val[2]);
+		if (val > width)
+			width = val;
+	}
+
+	if (freq < 4000)
+		width = 0;
+
+	return hostapd.freq_info(freq, sec_offset, width);
+}
+
+function iface_add(phy, config, phy_status)
+{
+	let config_inline = iface_gen_config(phy, config, !!phy_status);
+
+	let bss = config.bss[0];
+	let ret = hostapd.add_iface(`bss_config=${bss.ifname}:${config_inline}`);
+	if (ret < 0)
+		return false;
+
+	if (!phy_status)
+		return true;
+
+	let iface = hostapd.interfaces[bss.ifname];
+	if (!iface)
+		return false;
+
+	let freq_info = iface_freq_info(iface, config, phy_status);
+
+	return iface.start(freq_info) >= 0;
+}
+
+function iface_config_macaddr_list(config)
+{
+	let macaddr_list = {};
+	for (let i = 0; i < length(config.bss); i++) {
+		let bss = config.bss[i];
+		if (!bss.default_macaddr)
+			macaddr_list[bss.bssid] = i;
+	}
+
+	return macaddr_list;
+}
+
+function iface_restart(phydev, config, old_config)
+{
+	let phy = phydev.name;
+
+	iface_remove(old_config);
+	iface_remove(config);
+
+	if (!config.bss || !config.bss[0]) {
+		hostapd.printf(`No bss for phy ${phy}`);
+		return;
+	}
+
+	phydev.macaddr_init(iface_config_macaddr_list(config));
+	for (let i = 0; i < length(config.bss); i++) {
+		let bss = config.bss[i];
+		if (bss.default_macaddr)
+			bss.bssid = phydev.macaddr_next();
+	}
+
+	let bss = config.bss[0];
+	let err = wdev_create(phy, bss.ifname, { mode: "ap" });
+	if (err)
+		hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
+
+	let ubus = hostapd.data.ubus;
+	let phy_status = ubus.call("wpa_supplicant", "phy_status", { phy: phy });
+	if (phy_status && phy_status.state == "COMPLETED") {
+		if (iface_add(phy, config, phy_status))
+			return;
+
+		hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
+	}
+
+	ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
+	if (!iface_add(phy, config))
+		hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
+	ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
+}
+
+function array_to_obj(arr, key, start)
+{
+	let obj = {};
+
+	start ??= 0;
+	for (let i = start; i < length(arr); i++) {
+		let cur = arr[i];
+		obj[cur[key]] = cur;
+	}
+
+	return obj;
+}
+
+function find_array_idx(arr, key, val)
+{
+	for (let i = 0; i < length(arr); i++)
+		if (arr[i][key] == val)
+			return i;
+
+	return -1;
+}
+
+function bss_reload_psk(bss, config, old_config)
+{
+	if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
+		return;
+
+	old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
+	if (!is_equal(old_config, config))
+		return;
+
+	let ret = bss.ctrl("RELOAD_WPA_PSK");
+	ret ??= "failed";
+
+	hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
+}
+
+function remove_file_fields(config)
+{
+	return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]);
+}
+
+function bss_remove_file_fields(config)
+{
+	let new_cfg = {};
+
+	for (let key in config)
+		new_cfg[key] = config[key];
+	new_cfg.data = remove_file_fields(new_cfg.data);
+	new_cfg.hash = {};
+	for (let key in config.hash)
+		new_cfg.hash[key] = config.hash[key];
+	delete new_cfg.hash.wpa_psk_file;
+	delete new_cfg.hash.vlan_file;
+
+	return new_cfg;
+}
+
+function bss_config_hash(config)
+{
+	return hostapd.sha1(remove_file_fields(config) + "");
+}
+
+function bss_find_existing(config, prev_config, prev_hash)
+{
+	let hash = bss_config_hash(config.data);
+
+	for (let i = 0; i < length(prev_config.bss); i++) {
+		if (!prev_hash[i] || hash != prev_hash[i])
+			continue;
+
+		prev_hash[i] = null;
+		return i;
+	}
+
+	return -1;
+}
+
+function get_config_bss(config, idx)
+{
+	if (!config.bss[idx]) {
+		hostapd.printf(`Invalid bss index ${idx}`);
+		return null;
+	}
+
+	let ifname = config.bss[idx].ifname;
+	if (!ifname)
+		hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`);
+
+	return hostapd.bss[ifname];
+}
+
+function iface_reload_config(phydev, config, old_config)
+{
+	let phy = phydev.name;
+
+	if (!old_config || !is_equal(old_config.radio, config.radio))
+		return false;
+
+	if (is_equal(old_config.bss, config.bss))
+		return true;
+
+	if (!old_config.bss || !old_config.bss[0])
+		return false;
+
+	let iface_name = old_config.bss[0].ifname;
+	let iface = hostapd.interfaces[iface_name];
+	if (!iface) {
+		hostapd.printf(`Could not find previous interface ${iface_name}`);
+		return false;
+	}
+
+	let first_bss = hostapd.bss[iface_name];
+	if (!first_bss) {
+		hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
+		return false;
+	}
+
+	let macaddr_list = iface_config_macaddr_list(config);
+	let bss_list = [];
+	let bss_list_cfg = [];
+	let prev_bss_hash = [];
+
+	for (let bss in old_config.bss) {
+		let hash = bss_config_hash(bss.data);
+		push(prev_bss_hash, bss_config_hash(bss.data));
+	}
+
+	// Step 1: find (possibly renamed) interfaces with the same config
+	// and store them in the new order (with gaps)
+	for (let i = 0; i < length(config.bss); i++) {
+		let prev;
+
+		// For fullmac devices, the first interface needs to be preserved,
+		// since it's treated as the master
+		if (!i && phy_is_fullmac(phy)) {
+			prev = 0;
+			prev_bss_hash[0] = null;
+		} else {
+			prev = bss_find_existing(config.bss[i], old_config, prev_bss_hash);
+		}
+		if (prev < 0)
+			continue;
+
+		let cur_config = config.bss[i];
+		let prev_config = old_config.bss[prev];
+
+		let prev_bss = get_config_bss(old_config, prev);
+		if (!prev_bss)
+			return false;
+
+		// try to preserve MAC address of this BSS by reassigning another
+		// BSS if necessary
+		if (cur_config.default_macaddr &&
+		    !macaddr_list[prev_config.bssid]) {
+			macaddr_list[prev_config.bssid] = i;
+			cur_config.bssid = prev_config.bssid;
+		}
+
+		bss_list[i] = prev_bss;
+		bss_list_cfg[i] = old_config.bss[prev];
+	}
+
+	if (config.mbssid && !bss_list_cfg[0]) {
+		hostapd.printf("First BSS changed with MBSSID enabled");
+		return false;
+	}
+
+	// Step 2: if none were found, rename and preserve the first one
+	if (length(bss_list) == 0) {
+		// can't change the bssid of the first bss
+		if (config.bss[0].bssid != old_config.bss[0].bssid) {
+			if (!config.bss[0].default_macaddr) {
+				hostapd.printf(`BSSID of first interface changed: ${lc(old_config.bss[0].bssid)} -> ${lc(config.bss[0].bssid)}`);
+				return false;
+			}
+
+			config.bss[0].bssid = old_config.bss[0].bssid;
+		}
+
+		let prev_bss = get_config_bss(old_config, 0);
+		if (!prev_bss)
+			return false;
+
+		macaddr_list[config.bss[0].bssid] = 0;
+		bss_list[0] = prev_bss;
+		bss_list_cfg[0] = old_config.bss[0];
+		prev_bss_hash[0] = null;
+	}
+
+	// Step 3: delete all unused old interfaces
+	for (let i = 0; i < length(prev_bss_hash); i++) {
+		if (!prev_bss_hash[i])
+			continue;
+
+		let prev_bss = get_config_bss(old_config, i);
+		if (!prev_bss)
+			return false;
+
+		let ifname = old_config.bss[i].ifname;
+		hostapd.printf(`Remove bss '${ifname}' on phy '${phy}'`);
+		prev_bss.delete();
+		wdev_remove(ifname);
+	}
+
+	// Step 4: rename preserved interfaces, use temporary name on duplicates
+	let rename_list = [];
+	for (let i = 0; i < length(bss_list); i++) {
+		if (!bss_list[i])
+			continue;
+
+		let old_ifname = bss_list_cfg[i].ifname;
+		let new_ifname = config.bss[i].ifname;
+		if (old_ifname == new_ifname)
+			continue;
+
+		if (hostapd.bss[new_ifname]) {
+			new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8);
+			push(rename_list, i);
+		}
+
+		hostapd.printf(`Rename bss ${old_ifname} to ${new_ifname}`);
+		if (!bss_list[i].rename(new_ifname)) {
+			hostapd.printf(`Failed to rename bss ${old_ifname} to ${new_ifname}`);
+			return false;
+		}
+
+		bss_list_cfg[i].ifname = new_ifname;
+	}
+
+	// Step 5: rename interfaces with temporary names
+	for (let i in rename_list) {
+		let new_ifname = config.bss[i].ifname;
+		if (!bss_list[i].rename(new_ifname)) {
+			hostapd.printf(`Failed to rename bss to ${new_ifname}`);
+			return false;
+		}
+		bss_list_cfg[i].ifname = new_ifname;
+	}
+
+	// Step 6: assign BSSID for newly created interfaces
+	let macaddr_data = {
+		num_global: config.num_global_macaddr ?? 1,
+		mbssid: config.mbssid ?? 0,
+	};
+	macaddr_list = phydev.macaddr_init(macaddr_list, macaddr_data);
+	for (let i = 0; i < length(config.bss); i++) {
+		if (bss_list[i])
+			continue;
+		let bsscfg = config.bss[i];
+
+		let mac_idx = macaddr_list[bsscfg.bssid];
+		if (mac_idx < 0)
+			macaddr_list[bsscfg.bssid] = i;
+		if (mac_idx == i)
+			continue;
+
+		// statically assigned bssid of the new interface is in conflict
+		// with the bssid of a reused interface. reassign the reused interface
+		if (!bsscfg.default_macaddr) {
+			// can't update bssid of the first BSS, need to restart
+			if (!mac_idx < 0)
+				return false;
+
+			bsscfg = config.bss[mac_idx];
+		}
+
+		let addr = phydev.macaddr_next(i);
+		if (!addr) {
+			hostapd.printf(`Failed to generate mac address for phy ${phy}`);
+			return false;
+		}
+		bsscfg.bssid = addr;
+	}
+
+	let config_inline = iface_gen_config(phy, config);
+
+	// Step 7: fill in the gaps with new interfaces
+	for (let i = 0; i < length(config.bss); i++) {
+		let ifname = config.bss[i].ifname;
+		let bss = bss_list[i];
+
+		if (bss)
+			continue;
+
+		hostapd.printf(`Add bss ${ifname} on phy ${phy}`);
+		bss_list[i] = iface.add_bss(config_inline, i);
+		if (!bss_list[i]) {
+			hostapd.printf(`Failed to add new bss ${ifname} on phy ${phy}`);
+			return false;
+		}
+	}
+
+	// Step 8: update interface bss order
+	if (!iface.set_bss_order(bss_list)) {
+		hostapd.printf(`Failed to update BSS order on phy '${phy}'`);
+		return false;
+	}
+
+	// Step 9: update config
+	for (let i = 0; i < length(config.bss); i++) {
+		if (!bss_list_cfg[i])
+			continue;
+
+		let ifname = config.bss[i].ifname;
+		let bss = bss_list[i];
+
+		if (is_equal(config.bss[i], bss_list_cfg[i]))
+			continue;
+
+		if (is_equal(bss_remove_file_fields(config.bss[i]),
+		             bss_remove_file_fields(bss_list_cfg[i]))) {
+			hostapd.printf(`Update config data files for bss ${ifname}`);
+			if (bss.set_config(config_inline, i, true) < 0) {
+				hostapd.printf(`Could not update config data files for bss ${ifname}`);
+				return false;
+			} else {
+				bss.ctrl("RELOAD_WPA_PSK");
+				continue;
+			}
+		}
+
+		bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
+		if (is_equal(config.bss[i], bss_list_cfg[i]))
+			continue;
+
+		hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
+		if (bss.set_config(config_inline, i) < 0) {
+			hostapd.printf(`Failed to set config for bss ${ifname}`);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+function iface_update_supplicant_macaddr(phy, config)
+{
+	let macaddr_list = [];
+	for (let i = 0; i < length(config.bss); i++)
+		push(macaddr_list, config.bss[i].bssid);
+	ubus.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
+}
+
+function iface_set_config(phy, config)
+{
+	let old_config = hostapd.data.config[phy];
+
+	hostapd.data.config[phy] = config;
+
+	if (!config)
+		return iface_remove(old_config);
+
+	let phydev = phy_open(phy);
+	if (!phydev) {
+		hostapd.printf(`Failed to open phy ${phy}`);
+		return false;
+	}
+
+	try {
+		let ret = iface_reload_config(phydev, config, old_config);
+		if (ret) {
+			iface_update_supplicant_macaddr(phy, config);
+			hostapd.printf(`Reloaded settings for phy ${phy}`);
+			return 0;
+		}
+	} catch (e) {
+			hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
+	}
+
+	hostapd.printf(`Restart interface for phy ${phy}`);
+	let ret = iface_restart(phydev, config, old_config);
+	iface_update_supplicant_macaddr(phy, config);
+
+	return ret;
+}
+
+function config_add_bss(config, name)
+{
+	let bss = {
+		ifname: name,
+		data: [],
+		hash: {}
+	};
+
+	push(config.bss, bss);
+
+	return bss;
+}
+
+function iface_load_config(filename)
+{
+	let f = open(filename, "r");
+	if (!f)
+		return null;
+
+	let config = {
+		radio: {
+			data: []
+		},
+		bss: [],
+		orig_file: filename,
+	};
+
+	let bss;
+	let line;
+	while ((line = trim(f.read("line"))) != null) {
+		let val = split(line, "=", 2);
+		if (!val[0])
+			continue;
+
+		if (val[0] == "interface") {
+			bss = config_add_bss(config, val[1]);
+			break;
+		}
+
+		if (val[0] == "channel") {
+			config.radio.channel = val[1];
+			continue;
+		}
+
+		if (val[0] == "#num_global_macaddr" ||
+		    val[0] == "mbssid")
+			config[val[0]] = int(val[1]);
+
+		push(config.radio.data, line);
+	}
+
+	while ((line = trim(f.read("line"))) != null) {
+		if (line == "#default_macaddr")
+			bss.default_macaddr = true;
+
+		let val = split(line, "=", 2);
+		if (!val[0])
+			continue;
+
+		if (val[0] == "bssid") {
+			bss.bssid = lc(val[1]);
+			continue;
+		}
+
+		if (val[0] == "nas_identifier")
+			bss.nasid = val[1];
+
+		if (val[0] == "bss") {
+			bss = config_add_bss(config, val[1]);
+			continue;
+		}
+
+		if (hostapd.data.file_fields[val[0]])
+			bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
+
+		push(bss.data, line);
+	}
+	f.close();
+
+	return config;
+}
+
+function ex_wrap(func) {
+	return (req) => {
+		try {
+			let ret = func(req);
+			return ret;
+		} catch(e) {
+			hostapd.printf(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}`);
+		}
+		return libubus.STATUS_UNKNOWN_ERROR;
+	};
+}
+
+let main_obj = {
+	reload: {
+		args: {
+			phy: "",
+		},
+		call: ex_wrap(function(req) {
+			let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
+			for (let phy_name in phy_list) {
+				let phy = hostapd.data.config[phy_name];
+				let config = iface_load_config(phy.orig_file);
+				iface_set_config(phy_name, config);
+			}
+
+			return 0;
+		})
+	},
+	apsta_state: {
+		args: {
+			phy: "",
+			up: true,
+			frequency: 0,
+			sec_chan_offset: 0,
+			csa: true,
+			csa_count: 0,
+		},
+		call: ex_wrap(function(req) {
+			if (req.args.up == null || !req.args.phy)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			let phy = req.args.phy;
+			let config = hostapd.data.config[phy];
+			if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
+				return 0;
+
+			let iface = hostapd.interfaces[config.bss[0].ifname];
+			if (!iface)
+				return 0;
+
+			if (!req.args.up) {
+				iface.stop();
+				return 0;
+			}
+
+			if (!req.args.frequency)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			let freq_info = iface_freq_info(iface, config, req.args);
+			if (!freq_info)
+				return libubus.STATUS_UNKNOWN_ERROR;
+
+			let ret;
+			if (req.args.csa) {
+				freq_info.csa_count = req.args.csa_count ?? 10;
+				ret = iface.switch_channel(freq_info);
+			} else {
+				ret = iface.start(freq_info);
+			}
+			if (!ret)
+				return libubus.STATUS_UNKNOWN_ERROR;
+
+			return 0;
+		})
+	},
+	config_get_macaddr_list: {
+		args: {
+			phy: ""
+		},
+		call: ex_wrap(function(req) {
+			let phy = req.args.phy;
+			if (!phy)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			let ret = {
+				macaddr: [],
+			};
+
+			let config = hostapd.data.config[phy];
+			if (!config)
+				return ret;
+
+			ret.macaddr = map(config.bss, (bss) => bss.bssid);
+			return ret;
+		})
+	},
+	config_set: {
+		args: {
+			phy: "",
+			config: "",
+			prev_config: "",
+		},
+		call: ex_wrap(function(req) {
+			let phy = req.args.phy;
+			let file = req.args.config;
+			let prev_file = req.args.prev_config;
+
+			if (!phy)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			if (prev_file && !hostapd.data.config[phy]) {
+				let config = iface_load_config(prev_file);
+				if (config)
+					config.radio.data = [];
+				hostapd.data.config[phy] = config;
+			}
+
+			let config = iface_load_config(file);
+
+			hostapd.printf(`Set new config for phy ${phy}: ${file}`);
+			iface_set_config(phy, config);
+
+			return {
+				pid: hostapd.getpid()
+			};
+		})
+	},
+	config_add: {
+		args: {
+			iface: "",
+			config: "",
+		},
+		call: ex_wrap(function(req) {
+			if (!req.args.iface || !req.args.config)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			return {
+				pid: hostapd.getpid()
+			};
+		})
+	},
+	config_remove: {
+		args: {
+			iface: ""
+		},
+		call: ex_wrap(function(req) {
+			if (!req.args.iface)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			hostapd.remove_iface(req.args.iface);
+			return 0;
+		})
+	},
+};
+
+hostapd.data.ubus = ubus;
+hostapd.data.obj = ubus.publish("hostapd", main_obj);
+
+function bss_event(type, name, data) {
+	let ubus = hostapd.data.ubus;
+
+	data ??= {};
+	data.name = name;
+	hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
+	ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
+}
+
+return {
+	shutdown: function() {
+		for (let phy in hostapd.data.config)
+			iface_set_config(phy, null);
+		hostapd.ubus.disconnect();
+	},
+	bss_add: function(name, obj) {
+		bss_event("add", name);
+	},
+	bss_reload: function(name, obj, reconf) {
+		bss_event("reload", name, { reconf: reconf != 0 });
+	},
+	bss_remove: function(name, obj) {
+		bss_event("remove", name);
+	}
+};
diff --git a/package/network/services/hostapd/files/radius.clients b/package/network/services/hostapd/files/radius.clients
new file mode 100644
index 0000000000..3175dcfd04
--- /dev/null
+++ b/package/network/services/hostapd/files/radius.clients
@@ -0,0 +1 @@
+0.0.0.0/0 radius
diff --git a/package/network/services/hostapd/files/radius.config b/package/network/services/hostapd/files/radius.config
new file mode 100644
index 0000000000..ad8730748b
--- /dev/null
+++ b/package/network/services/hostapd/files/radius.config
@@ -0,0 +1,9 @@
+config radius
+	option disabled '1'
+	option ca_cert '/etc/radius/ca.pem'
+	option cert '/etc/radius/cert.pem'
+	option key '/etc/radius/key.pem'
+	option users '/etc/radius/users'
+	option clients '/etc/radius/clients'
+	option auth_port '1812'
+	option acct_port '1813'
diff --git a/package/network/services/hostapd/files/radius.init b/package/network/services/hostapd/files/radius.init
new file mode 100644
index 0000000000..4c562c2473
--- /dev/null
+++ b/package/network/services/hostapd/files/radius.init
@@ -0,0 +1,42 @@
+#!/bin/sh /etc/rc.common
+
+START=30
+
+USE_PROCD=1
+NAME=radius
+
+radius_start() {
+	local cfg="$1"
+
+	config_get_bool disabled "$cfg" disabled 0
+
+	[ "$disabled" -gt 0 ] && return
+
+	config_get ca "$cfg" ca_cert
+	config_get key "$cfg" key
+	config_get cert "$cfg" cert
+	config_get users "$cfg" users
+	config_get clients "$cfg" clients
+	config_get auth_port "$cfg" auth_port 1812
+	config_get acct_port "$cfg" acct_port 1813
+	config_get identity "$cfg" identity "$(cat /proc/sys/kernel/hostname)"
+
+	procd_open_instance $cfg
+	procd_set_param command /usr/sbin/hostapd-radius \
+		-C "$ca" \
+		-c "$cert" -k "$key" \
+		-s "$clients" -u "$users" \
+		-p "$auth_port" -P "$acct_port" \
+		-i "$identity"
+	procd_close_instance
+}
+
+start_service() {
+	config_load radius
+	config_foreach radius_start radius
+}
+
+service_triggers()
+{
+	procd_add_reload_trigger "radius"
+}
diff --git a/package/network/services/hostapd/files/radius.users b/package/network/services/hostapd/files/radius.users
new file mode 100644
index 0000000000..03e2fc8fae
--- /dev/null
+++ b/package/network/services/hostapd/files/radius.users
@@ -0,0 +1,14 @@
+{
+	"phase1": {
+		"wildcard": [
+			{
+				"name": "*",
+				"methods": [ "PEAP" ]
+			}
+		]
+	},
+	"phase2": {
+		"users": {
+		}
+	}
+}
diff --git a/package/network/services/hostapd/files/wdev.uc b/package/network/services/hostapd/files/wdev.uc
new file mode 100644
index 0000000000..8a031b40b9
--- /dev/null
+++ b/package/network/services/hostapd/files/wdev.uc
@@ -0,0 +1,207 @@
+#!/usr/bin/env ucode
+'use strict';
+import { vlist_new, is_equal, wdev_create, wdev_remove, phy_open } from "/usr/share/hostap/common.uc";
+import { readfile, writefile, basename, readlink, glob } from "fs";
+let libubus = require("ubus");
+
+let keep_devices = {};
+let phy = shift(ARGV);
+let command = shift(ARGV);
+let phydev;
+
+const mesh_params = [
+	"mesh_retry_timeout", "mesh_confirm_timeout", "mesh_holding_timeout", "mesh_max_peer_links",
+	"mesh_max_retries", "mesh_ttl", "mesh_element_ttl", "mesh_hwmp_max_preq_retries",
+	"mesh_path_refresh_time", "mesh_min_discovery_timeout", "mesh_hwmp_active_path_timeout",
+	"mesh_hwmp_preq_min_interval", "mesh_hwmp_net_diameter_traversal_time", "mesh_hwmp_rootmode",
+	"mesh_hwmp_rann_interval", "mesh_gate_announcements", "mesh_sync_offset_max_neighor",
+	"mesh_rssi_threshold", "mesh_hwmp_active_path_to_root_timeout", "mesh_hwmp_root_interval",
+	"mesh_hwmp_confirmation_interval", "mesh_awake_window", "mesh_plink_timeout",
+	"mesh_auto_open_plinks", "mesh_fwding", "mesh_power_mode"
+];
+
+function iface_stop(wdev)
+{
+	if (keep_devices[wdev.ifname])
+		return;
+
+	wdev_remove(wdev.ifname);
+}
+
+function iface_start(wdev)
+{
+	let ifname = wdev.ifname;
+
+	if (readfile(`/sys/class/net/${ifname}/ifindex`)) {
+		system([ "ip", "link", "set", "dev", ifname, "down" ]);
+		wdev_remove(ifname);
+	}
+	let wdev_config = {};
+	for (let key in wdev)
+		wdev_config[key] = wdev[key];
+	if (!wdev_config.macaddr && wdev.mode != "monitor")
+		wdev_config.macaddr = phydev.macaddr_next();
+	wdev_create(phy, ifname, wdev);
+	system([ "ip", "link", "set", "dev", ifname, "up" ]);
+	if (wdev.freq)
+		system(`iw dev ${ifname} set freq ${wdev.freq} ${wdev.htmode}`);
+	if (wdev.mode == "adhoc") {
+		let cmd = ["iw", "dev", ifname, "ibss", "join", wdev.ssid, wdev.freq, wdev.htmode, "fixed-freq" ];
+		if (wdev.bssid)
+			push(cmd, wdev.bssid);
+		for (let key in [ "beacon-interval", "basic-rates", "mcast-rate", "keys" ])
+			if (wdev[key])
+				push(cmd, key, wdev[key]);
+		system(cmd);
+	} else if (wdev.mode == "mesh") {
+		let cmd = [ "iw", "dev", ifname, "mesh", "join", wdev.ssid, "freq", wdev.freq, wdev.htmode ];
+		for (let key in [ "mcast-rate", "beacon-interval" ])
+			if (wdev[key])
+				push(cmd, key, wdev[key]);
+		system(cmd);
+
+		cmd = ["iw", "dev", ifname, "set", "mesh_param" ];
+		let len = length(cmd);
+
+		for (let param in mesh_params)
+			if (wdev[param])
+				push(cmd, param, wdev[param]);
+
+		if (len == length(cmd))
+			return;
+
+		system(cmd);
+	}
+
+}
+
+function iface_cb(new_if, old_if)
+{
+	if (old_if && new_if && is_equal(old_if, new_if))
+		return;
+
+	if (old_if)
+		iface_stop(old_if);
+	if (new_if)
+		iface_start(new_if);
+}
+
+function drop_inactive(config)
+{
+	for (let key in config) {
+		if (!readfile(`/sys/class/net/${key}/ifindex`))
+			delete config[key];
+	}
+}
+
+function add_ifname(config)
+{
+	for (let key in config)
+		config[key].ifname = key;
+}
+
+function delete_ifname(config)
+{
+	for (let key in config)
+		delete config[key].ifname;
+}
+
+function add_existing(phy, config)
+{
+	let wdevs = glob(`/sys/class/ieee80211/${phy}/device/net/*`);
+	wdevs = map(wdevs, (arg) => basename(arg));
+	for (let wdev in wdevs) {
+		if (config[wdev])
+			continue;
+
+		if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != phy)
+			continue;
+
+		if (trim(readfile(`/sys/class/net/${wdev}/operstate`)) == "down")
+			config[wdev] = {};
+	}
+}
+
+function usage()
+{
+	warn(`Usage: ${basename(sourcepath())} <phy> <command> [<arguments>]
+
+Commands:
+	set_config <config> [<device]...] - set phy configuration
+	get_macaddr <id>		  - get phy MAC address for vif index <id>
+`);
+	exit(1);
+}
+
+const commands = {
+	set_config: function(args) {
+		let statefile = `/var/run/wdev-${phy}.json`;
+
+		let new_config = shift(args);
+		for (let dev in ARGV)
+			keep_devices[dev] = true;
+
+		if (!new_config)
+			usage();
+
+		new_config = json(new_config);
+		if (!new_config) {
+			warn("Invalid configuration\n");
+			exit(1);
+		}
+
+		let old_config = readfile(statefile);
+		if (old_config)
+			old_config = json(old_config);
+
+		let config = vlist_new(iface_cb);
+		if (type(old_config) == "object")
+			config.data = old_config;
+
+		add_existing(phy, config.data);
+		add_ifname(config.data);
+		drop_inactive(config.data);
+
+		let ubus = libubus.connect();
+		let data = ubus.call("hostapd", "config_get_macaddr_list", { phy: phy });
+		let macaddr_list = [];
+		if (type(data) == "object" && data.macaddr)
+			macaddr_list = data.macaddr;
+		ubus.disconnect();
+		phydev.macaddr_init(macaddr_list);
+
+		add_ifname(new_config);
+		config.update(new_config);
+
+		drop_inactive(config.data);
+		delete_ifname(config.data);
+		writefile(statefile, sprintf("%J", config.data));
+	},
+	get_macaddr: function(args) {
+		let data = {};
+
+		for (let arg in args) {
+			arg = split(arg, "=", 2);
+			data[arg[0]] = arg[1];
+		}
+
+		let macaddr = phydev.macaddr_generate(data);
+		if (!macaddr) {
+			warn(`Could not get MAC address for phy ${phy}\n`);
+			exit(1);
+		}
+
+		print(macaddr + "\n");
+	},
+};
+
+if (!phy || !command | !commands[command])
+	usage();
+
+phydev = phy_open(phy);
+if (!phydev) {
+	warn(`PHY ${phy} does not exist\n`);
+	exit(1);
+}
+
+commands[command](ARGV);
diff --git a/package/network/services/hostapd/files/wpa_supplicant-basic.config b/package/network/services/hostapd/files/wpa_supplicant-basic.config
index 6abd8e2331..944b4d9287 100644
--- a/package/network/services/hostapd/files/wpa_supplicant-basic.config
+++ b/package/network/services/hostapd/files/wpa_supplicant-basic.config
@@ -26,7 +26,7 @@
 # replacement for WEXT and its use allows wpa_supplicant to properly control
 # the driver to improve existing functionality like roaming and to support new
 # functionality.
-CONFIG_DRIVER_WEXT=y
+#CONFIG_DRIVER_WEXT=y
 
 # Driver interface for Linux drivers using the nl80211 kernel interface
 CONFIG_DRIVER_NL80211=y
diff --git a/package/network/services/hostapd/files/wpa_supplicant-full.config b/package/network/services/hostapd/files/wpa_supplicant-full.config
index d24fbbb01f..b39dabca06 100644
--- a/package/network/services/hostapd/files/wpa_supplicant-full.config
+++ b/package/network/services/hostapd/files/wpa_supplicant-full.config
@@ -26,7 +26,7 @@
 # replacement for WEXT and its use allows wpa_supplicant to properly control
 # the driver to improve existing functionality like roaming and to support new
 # functionality.
-CONFIG_DRIVER_WEXT=y
+#CONFIG_DRIVER_WEXT=y
 
 # Driver interface for Linux drivers using the nl80211 kernel interface
 CONFIG_DRIVER_NL80211=y
diff --git a/package/network/services/hostapd/files/wpa_supplicant-mini.config b/package/network/services/hostapd/files/wpa_supplicant-mini.config
index 9eb1111e52..2a3f8fb69d 100644
--- a/package/network/services/hostapd/files/wpa_supplicant-mini.config
+++ b/package/network/services/hostapd/files/wpa_supplicant-mini.config
@@ -26,7 +26,7 @@
 # replacement for WEXT and its use allows wpa_supplicant to properly control
 # the driver to improve existing functionality like roaming and to support new
 # functionality.
-CONFIG_DRIVER_WEXT=y
+#CONFIG_DRIVER_WEXT=y
 
 # Driver interface for Linux drivers using the nl80211 kernel interface
 CONFIG_DRIVER_NL80211=y
diff --git a/package/network/services/hostapd/files/wpa_supplicant-p2p.config b/package/network/services/hostapd/files/wpa_supplicant-p2p.config
index 0dcc88e648..7f5140622c 100644
--- a/package/network/services/hostapd/files/wpa_supplicant-p2p.config
+++ b/package/network/services/hostapd/files/wpa_supplicant-p2p.config
@@ -26,7 +26,7 @@
 # replacement for WEXT and its use allows wpa_supplicant to properly control
 # the driver to improve existing functionality like roaming and to support new
 # functionality.
-CONFIG_DRIVER_WEXT=y
+#CONFIG_DRIVER_WEXT=y
 
 # Driver interface for Linux drivers using the nl80211 kernel interface
 CONFIG_DRIVER_NL80211=y
diff --git a/package/network/services/hostapd/files/wpa_supplicant.uc b/package/network/services/hostapd/files/wpa_supplicant.uc
new file mode 100644
index 0000000000..cb5f41b1af
--- /dev/null
+++ b/package/network/services/hostapd/files/wpa_supplicant.uc
@@ -0,0 +1,330 @@
+let libubus = require("ubus");
+import { open, readfile } from "fs";
+import { wdev_create, wdev_remove, is_equal, vlist_new, phy_open } from "common";
+
+let ubus = libubus.connect();
+
+wpas.data.config = {};
+wpas.data.iface_phy = {};
+wpas.data.macaddr_list = {};
+
+function iface_stop(iface)
+{
+	let ifname = iface.config.iface;
+
+	if (!iface.running)
+		return;
+
+	delete wpas.data.iface_phy[ifname];
+	wpas.remove_iface(ifname);
+	wdev_remove(ifname);
+	iface.running = false;
+}
+
+function iface_start(phydev, iface, macaddr_list)
+{
+	let phy = phydev.name;
+
+	if (iface.running)
+		return;
+
+	let ifname = iface.config.iface;
+	let wdev_config = {};
+	for (let field in iface.config)
+		wdev_config[field] = iface.config[field];
+	if (!wdev_config.macaddr)
+		wdev_config.macaddr = phydev.macaddr_next();
+
+	wpas.data.iface_phy[ifname] = phy;
+	wdev_remove(ifname);
+	let ret = wdev_create(phy, ifname, wdev_config);
+	if (ret)
+		wpas.printf(`Failed to create device ${ifname}: ${ret}`);
+	wpas.add_iface(iface.config);
+	iface.running = true;
+}
+
+function iface_cb(new_if, old_if)
+{
+	if (old_if && new_if && is_equal(old_if.config, new_if.config)) {
+		new_if.running = old_if.running;
+		return;
+	}
+
+	if (new_if && old_if)
+		wpas.printf(`Update configuration for interface ${old_if.config.iface}`);
+	else if (old_if)
+		wpas.printf(`Remove interface ${old_if.config.iface}`);
+
+	if (old_if)
+		iface_stop(old_if);
+}
+
+function prepare_config(config)
+{
+	config.config_data = readfile(config.config);
+
+	return { config: config };
+}
+
+function set_config(phy_name, config_list)
+{
+	let phy = wpas.data.config[phy_name];
+
+	if (!phy) {
+		phy = vlist_new(iface_cb, false);
+		wpas.data.config[phy_name] = phy;
+	}
+
+	let values = [];
+	for (let config in config_list)
+		push(values, [ config.iface, prepare_config(config) ]);
+
+	phy.update(values);
+}
+
+function start_pending(phy_name)
+{
+	let phy = wpas.data.config[phy_name];
+	let ubus = wpas.data.ubus;
+
+	if (!phy || !phy.data)
+		return;
+
+	let phydev = phy_open(phy_name);
+	if (!phydev) {
+		wpas.printf(`Could not open phy ${phy_name}`);
+		return;
+	}
+
+	let macaddr_list = wpas.data.macaddr_list[phy_name];
+	phydev.macaddr_init(macaddr_list);
+
+	for (let ifname in phy.data)
+		iface_start(phydev, phy.data[ifname]);
+}
+
+let main_obj = {
+	phy_set_state: {
+		args: {
+			phy: "",
+			stop: true,
+		},
+		call: function(req) {
+			if (!req.args.phy || req.args.stop == null)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			let phy = wpas.data.config[req.args.phy];
+			if (!phy)
+				return libubus.STATUS_NOT_FOUND;
+
+			try {
+				if (req.args.stop) {
+					for (let ifname in phy.data)
+						iface_stop(phy.data[ifname]);
+				} else {
+					start_pending(req.args.phy);
+				}
+			} catch (e) {
+				wpas.printf(`Error chaging state: ${e}\n${e.stacktrace[0].context}`);
+				return libubus.STATUS_INVALID_ARGUMENT;
+			}
+			return 0;
+		}
+	},
+	phy_set_macaddr_list: {
+		args: {
+			phy: "",
+			macaddr: [],
+		},
+		call: function(req) {
+			let phy = req.args.phy;
+			if (!phy)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			wpas.data.macaddr_list[phy] = req.args.macaddr;
+			return 0;
+		}
+	},
+	phy_status: {
+		args: {
+			phy: ""
+		},
+		call: function(req) {
+			if (!req.args.phy)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			let phy = wpas.data.config[req.args.phy];
+			if (!phy)
+				return libubus.STATUS_NOT_FOUND;
+
+			for (let ifname in phy.data) {
+				try {
+					let iface = wpas.interfaces[ifname];
+					if (!iface)
+						continue;
+
+					let status = iface.status();
+					if (!status)
+						continue;
+
+					if (status.state == "INTERFACE_DISABLED")
+						continue;
+
+					status.ifname = ifname;
+					return status;
+				} catch (e) {
+					continue;
+				}
+			}
+
+			return libubus.STATUS_NOT_FOUND;
+		}
+	},
+	config_set: {
+		args: {
+			phy: "",
+			config: [],
+			defer: true,
+		},
+		call: function(req) {
+			if (!req.args.phy)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			wpas.printf(`Set new config for phy ${req.args.phy}`);
+			try {
+				if (req.args.config)
+					set_config(req.args.phy, req.args.config);
+
+				if (!req.args.defer)
+					start_pending(req.args.phy);
+			} catch (e) {
+				wpas.printf(`Error loading config: ${e}\n${e.stacktrace[0].context}`);
+				return libubus.STATUS_INVALID_ARGUMENT;
+			}
+
+			return {
+				pid: wpas.getpid()
+			};
+		}
+	},
+	config_add: {
+		args: {
+			driver: "",
+			iface: "",
+			bridge: "",
+			hostapd_ctrl: "",
+			ctrl: "",
+			config: "",
+		},
+		call: function(req) {
+			if (!req.args.iface || !req.args.config)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			if (wpas.add_iface(req.args) < 0)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			return {
+				pid: wpas.getpid()
+			};
+		}
+	},
+	config_remove: {
+		args: {
+			iface: ""
+		},
+		call: function(req) {
+			if (!req.args.iface)
+				return libubus.STATUS_INVALID_ARGUMENT;
+
+			wpas.remove_iface(req.args.iface);
+			return 0;
+		}
+	},
+};
+
+wpas.data.ubus = ubus;
+wpas.data.obj = ubus.publish("wpa_supplicant", main_obj);
+
+function iface_event(type, name, data) {
+	let ubus = wpas.data.ubus;
+
+	data ??= {};
+	data.name = name;
+	wpas.data.obj.notify(`iface.${type}`, data, null, null, null, -1);
+	ubus.call("service", "event", { type: `wpa_supplicant.${name}.${type}`, data: {} });
+}
+
+function iface_hostapd_notify(phy, ifname, iface, state)
+{
+	let ubus = wpas.data.ubus;
+	let status = iface.status();
+	let msg = { phy: phy };
+
+	switch (state) {
+	case "DISCONNECTED":
+	case "AUTHENTICATING":
+	case "SCANNING":
+		msg.up = false;
+		break;
+	case "INTERFACE_DISABLED":
+	case "INACTIVE":
+		msg.up = true;
+		break;
+	case "COMPLETED":
+		msg.up = true;
+		msg.frequency = status.frequency;
+		msg.sec_chan_offset = status.sec_chan_offset;
+		break;
+	default:
+		return;
+	}
+
+	ubus.call("hostapd", "apsta_state", msg);
+}
+
+function iface_channel_switch(phy, ifname, iface, info)
+{
+	let msg = {
+		phy: phy,
+		up: true,
+		csa: true,
+		csa_count: info.csa_count ? info.csa_count - 1 : 0,
+		frequency: info.frequency,
+		sec_chan_offset: info.sec_chan_offset,
+	};
+	ubus.call("hostapd", "apsta_state", msg);
+}
+
+return {
+	shutdown: function() {
+		for (let phy in wpas.data.config)
+			set_config(phy, []);
+		wpas.ubus.disconnect();
+	},
+	iface_add: function(name, obj) {
+		iface_event("add", name);
+	},
+	iface_remove: function(name, obj) {
+		iface_event("remove", name);
+	},
+	state: function(ifname, iface, state) {
+		let phy = wpas.data.iface_phy[ifname];
+		if (!phy) {
+			wpas.printf(`no PHY for ifname ${ifname}`);
+			return;
+		}
+
+		iface_hostapd_notify(phy, ifname, iface, state);
+	},
+	event: function(ifname, iface, ev, info) {
+		let phy = wpas.data.iface_phy[ifname];
+		if (!phy) {
+			wpas.printf(`no PHY for ifname ${ifname}`);
+			return;
+		}
+
+		if (ev == "CH_SWITCH_STARTED")
+			iface_channel_switch(phy, ifname, iface, info);
+	}
+};
diff --git a/package/network/services/hostapd/files/wpad_acl.json b/package/network/services/hostapd/files/wpad_acl.json
index c77ccd8ea0..d00fd945ba 100644
--- a/package/network/services/hostapd/files/wpad_acl.json
+++ b/package/network/services/hostapd/files/wpad_acl.json
@@ -3,6 +3,12 @@
 	"access": {
 		"service": {
 			"methods": [ "event" ]
+		},
+		"wpa_supplicant": {
+			"methods": [ "phy_set_state", "phy_set_macaddr_list", "phy_status" ]
+		},
+		"hostapd": {
+			"methods": [ "apsta_state" ]
 		}
 	},
 	"publish": [ "hostapd", "hostapd.*", "wpa_supplicant", "wpa_supplicant.*" ],
diff --git a/package/network/services/hostapd/patches/011-mesh-use-deterministic-channel-on-channel-switch.patch b/package/network/services/hostapd/patches/011-mesh-use-deterministic-channel-on-channel-switch.patch
index 9b11f0e803..07b7a5971d 100644
--- a/package/network/services/hostapd/patches/011-mesh-use-deterministic-channel-on-channel-switch.patch
+++ b/package/network/services/hostapd/patches/011-mesh-use-deterministic-channel-on-channel-switch.patch
@@ -29,7 +29,7 @@ Signed-off-by: Markus Theil <markus.theil at tu-ilmenau.de>
  
  
  enum dfs_channel_type {
-@@ -521,9 +522,14 @@ dfs_get_valid_channel(struct hostapd_ifa
+@@ -526,9 +527,14 @@ dfs_get_valid_channel(struct hostapd_ifa
  	int num_available_chandefs;
  	int chan_idx, chan_idx2;
  	int sec_chan_idx_80p80 = -1;
@@ -44,7 +44,7 @@ Signed-off-by: Markus Theil <markus.theil at tu-ilmenau.de>
  	wpa_printf(MSG_DEBUG, "DFS: Selecting random channel");
  	*secondary_channel = 0;
  	*oper_centr_freq_seg0_idx = 0;
-@@ -543,8 +549,20 @@ dfs_get_valid_channel(struct hostapd_ifa
+@@ -548,8 +554,20 @@ dfs_get_valid_channel(struct hostapd_ifa
  	if (num_available_chandefs == 0)
  		return NULL;
  
@@ -68,7 +68,7 @@ Signed-off-by: Markus Theil <markus.theil at tu-ilmenau.de>
  	if (!chan) {
 --- a/src/drivers/driver_nl80211.c
 +++ b/src/drivers/driver_nl80211.c
-@@ -10977,6 +10977,10 @@ static int nl80211_switch_channel(void *
+@@ -11017,6 +11017,10 @@ static int nl80211_switch_channel(void *
  	if (ret)
  		goto error;
  
diff --git a/package/network/services/hostapd/patches/021-fix-sta-add-after-previous-connection.patch b/package/network/services/hostapd/patches/021-fix-sta-add-after-previous-connection.patch
index 4ee43b5186..edf599e3e2 100644
--- a/package/network/services/hostapd/patches/021-fix-sta-add-after-previous-connection.patch
+++ b/package/network/services/hostapd/patches/021-fix-sta-add-after-previous-connection.patch
@@ -1,6 +1,6 @@
 --- a/src/ap/ieee802_11.c
 +++ b/src/ap/ieee802_11.c
-@@ -4601,6 +4601,13 @@ static int add_associated_sta(struct hos
+@@ -4621,6 +4621,13 @@ static int add_associated_sta(struct hos
  	 * drivers to accept the STA parameter configuration. Since this is
  	 * after a new FT-over-DS exchange, a new TK has been derived, so key
  	 * reinstallation is not a concern for this case.
@@ -14,7 +14,7 @@
  	 */
  	wpa_printf(MSG_DEBUG, "Add associated STA " MACSTR
  		   " (added_unassoc=%d auth_alg=%u ft_over_ds=%u reassoc=%d authorized=%d ft_tk=%d fils_tk=%d)",
-@@ -4614,7 +4621,8 @@ static int add_associated_sta(struct hos
+@@ -4634,7 +4641,8 @@ static int add_associated_sta(struct hos
  	    (!(sta->flags & WLAN_STA_AUTHORIZED) ||
  	     (reassoc && sta->ft_over_ds && sta->auth_alg == WLAN_AUTH_FT) ||
  	     (!wpa_auth_sta_ft_tk_already_set(sta->wpa_sm) &&
diff --git a/package/network/services/hostapd/patches/030-driver_nl80211-rewrite-neigh-code-to-not-depend-on-l.patch b/package/network/services/hostapd/patches/030-driver_nl80211-rewrite-neigh-code-to-not-depend-on-l.patch
index 19248e80d8..ef2bb408fb 100644
--- a/package/network/services/hostapd/patches/030-driver_nl80211-rewrite-neigh-code-to-not-depend-on-l.patch
+++ b/package/network/services/hostapd/patches/030-driver_nl80211-rewrite-neigh-code-to-not-depend-on-l.patch
@@ -92,7 +92,7 @@ Signed-off-by: Felix Fietkau <nbd at nbd.name>
  
  	if (drv->capa.flags2 & WPA_DRIVER_FLAGS2_CONTROL_PORT_RX) {
  		wpa_printf(MSG_DEBUG,
-@@ -11843,13 +11840,14 @@ static int wpa_driver_br_add_ip_neigh(vo
+@@ -11883,13 +11880,14 @@ static int wpa_driver_br_add_ip_neigh(vo
  				      const u8 *ipaddr, int prefixlen,
  				      const u8 *addr)
  {
@@ -112,7 +112,7 @@ Signed-off-by: Felix Fietkau <nbd at nbd.name>
  	int res;
  
  	if (!ipaddr || prefixlen == 0 || !addr)
-@@ -11868,85 +11866,66 @@ static int wpa_driver_br_add_ip_neigh(vo
+@@ -11908,85 +11906,66 @@ static int wpa_driver_br_add_ip_neigh(vo
  	}
  
  	if (version == 4) {
@@ -220,7 +220,7 @@ Signed-off-by: Felix Fietkau <nbd at nbd.name>
  		addrsize = 16;
  	} else {
  		return -EINVAL;
-@@ -11964,41 +11943,30 @@ static int wpa_driver_br_delete_ip_neigh
+@@ -12004,41 +11983,30 @@ static int wpa_driver_br_delete_ip_neigh
  		return -1;
  	}
  
diff --git a/package/network/services/hostapd/patches/040-mesh-allow-processing-authentication-frames-in-block.patch b/package/network/services/hostapd/patches/040-mesh-allow-processing-authentication-frames-in-block.patch
index f98d3806dc..b7bf9e351e 100644
--- a/package/network/services/hostapd/patches/040-mesh-allow-processing-authentication-frames-in-block.patch
+++ b/package/network/services/hostapd/patches/040-mesh-allow-processing-authentication-frames-in-block.patch
@@ -16,7 +16,7 @@ Signed-off-by: Felix Fietkau <nbd at nbd.name>
 
 --- a/src/ap/ieee802_11.c
 +++ b/src/ap/ieee802_11.c
-@@ -3012,15 +3012,6 @@ static void handle_auth(struct hostapd_d
+@@ -3020,15 +3020,6 @@ static void handle_auth(struct hostapd_d
  				       seq_ctrl);
  			return;
  		}
diff --git a/package/network/services/hostapd/patches/100-daemonize_fix.patch b/package/network/services/hostapd/patches/100-daemonize_fix.patch
deleted file mode 100644
index 687bd4082d..0000000000
--- a/package/network/services/hostapd/patches/100-daemonize_fix.patch
+++ /dev/null
@@ -1,97 +0,0 @@
---- a/src/utils/os_unix.c
-+++ b/src/utils/os_unix.c
-@@ -10,6 +10,7 @@
- 
- #include <time.h>
- #include <sys/wait.h>
-+#include <fcntl.h>
- 
- #ifdef ANDROID
- #include <sys/capability.h>
-@@ -188,59 +189,46 @@ int os_gmtime(os_time_t t, struct os_tm
- 	return 0;
- }
- 
--
--#ifdef __APPLE__
--#include <fcntl.h>
--static int os_daemon(int nochdir, int noclose)
-+int os_daemonize(const char *pid_file)
- {
--	int devnull;
-+	int pid = 0, i, devnull;
- 
--	if (chdir("/") < 0)
--		return -1;
-+#if defined(__uClinux__) || defined(__sun__)
-+	return -1;
-+#else /* defined(__uClinux__) || defined(__sun__) */
- 
--	devnull = open("/dev/null", O_RDWR);
--	if (devnull < 0)
-+#ifndef __APPLE__
-+	pid = fork();
-+	if (pid < 0)
- 		return -1;
-+#endif
- 
--	if (dup2(devnull, STDIN_FILENO) < 0) {
--		close(devnull);
--		return -1;
-+	if (pid > 0) {
-+		if (pid_file) {
-+			FILE *f = fopen(pid_file, "w");
-+			if (f) {
-+				fprintf(f, "%u\n", pid);
-+				fclose(f);
-+			}
-+		}
-+		_exit(0);
- 	}
- 
--	if (dup2(devnull, STDOUT_FILENO) < 0) {
--		close(devnull);
-+	if (setsid() < 0)
- 		return -1;
--	}
- 
--	if (dup2(devnull, STDERR_FILENO) < 0) {
--		close(devnull);
-+	if (chdir("/") < 0)
- 		return -1;
--	}
--
--	return 0;
--}
--#else /* __APPLE__ */
--#define os_daemon daemon
--#endif /* __APPLE__ */
- 
--
--int os_daemonize(const char *pid_file)
--{
--#if defined(__uClinux__) || defined(__sun__)
--	return -1;
--#else /* defined(__uClinux__) || defined(__sun__) */
--	if (os_daemon(0, 0)) {
--		perror("daemon");
-+	devnull = open("/dev/null", O_RDWR);
-+	if (devnull < 0)
- 		return -1;
--	}
- 
--	if (pid_file) {
--		FILE *f = fopen(pid_file, "w");
--		if (f) {
--			fprintf(f, "%u\n", getpid());
--			fclose(f);
--		}
--	}
-+	for (i = 0; i <= STDERR_FILENO; i++)
-+		dup2(devnull, i);
-+
-+	if (devnull > 2)
-+		close(devnull);
- 
- 	return -0;
- #endif /* defined(__uClinux__) || defined(__sun__) */
diff --git a/package/network/services/hostapd/patches/140-tests-Makefile-make-run-tests-with-CONFIG_TLS.patch b/package/network/services/hostapd/patches/140-tests-Makefile-make-run-tests-with-CONFIG_TLS.patch
index 148c268f9c..e967cff427 100644
--- a/package/network/services/hostapd/patches/140-tests-Makefile-make-run-tests-with-CONFIG_TLS.patch
+++ b/package/network/services/hostapd/patches/140-tests-Makefile-make-run-tests-with-CONFIG_TLS.patch
@@ -903,7 +903,7 @@ Signed-off-by: Glenn Strauss <gstrauss at gluelogic.com>
      for exp, flags in tests:
          hapd.disable()
          hapd.set("tls_flags", flags)
-@@ -7115,6 +7173,7 @@ def test_ap_wpa2_eap_assoc_rsn(dev, apde
+@@ -7138,6 +7196,7 @@ def test_ap_wpa2_eap_assoc_rsn(dev, apde
  def test_eap_tls_ext_cert_check(dev, apdev):
      """EAP-TLS and external server certification validation"""
      # With internal server certificate chain validation
@@ -911,7 +911,7 @@ Signed-off-by: Glenn Strauss <gstrauss at gluelogic.com>
      id = dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", eap="TLS",
                          identity="tls user",
                          ca_cert="auth_serv/ca.pem",
-@@ -7127,6 +7186,7 @@ def test_eap_tls_ext_cert_check(dev, apd
+@@ -7150,6 +7209,7 @@ def test_eap_tls_ext_cert_check(dev, apd
  def test_eap_ttls_ext_cert_check(dev, apdev):
      """EAP-TTLS and external server certification validation"""
      # Without internal server certificate chain validation
@@ -919,7 +919,7 @@ Signed-off-by: Glenn Strauss <gstrauss at gluelogic.com>
      id = dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", eap="TTLS",
                          identity="pap user", anonymous_identity="ttls",
                          password="password", phase2="auth=PAP",
-@@ -7137,6 +7197,7 @@ def test_eap_ttls_ext_cert_check(dev, ap
+@@ -7160,6 +7220,7 @@ def test_eap_ttls_ext_cert_check(dev, ap
  def test_eap_peap_ext_cert_check(dev, apdev):
      """EAP-PEAP and external server certification validation"""
      # With internal server certificate chain validation
@@ -927,7 +927,7 @@ Signed-off-by: Glenn Strauss <gstrauss at gluelogic.com>
      id = dev[0].connect("test-wpa2-eap", key_mgmt="WPA-EAP", eap="PEAP",
                          identity="user", anonymous_identity="peap",
                          ca_cert="auth_serv/ca.pem",
-@@ -7147,6 +7208,7 @@ def test_eap_peap_ext_cert_check(dev, ap
+@@ -7170,6 +7231,7 @@ def test_eap_peap_ext_cert_check(dev, ap
  
  def test_eap_fast_ext_cert_check(dev, apdev):
      """EAP-FAST and external server certification validation"""
@@ -935,7 +935,7 @@ Signed-off-by: Glenn Strauss <gstrauss at gluelogic.com>
      check_eap_capa(dev[0], "FAST")
      # With internal server certificate chain validation
      dev[0].request("SET blob fast_pac_auth_ext ")
-@@ -7161,10 +7223,6 @@ def test_eap_fast_ext_cert_check(dev, ap
+@@ -7184,10 +7246,6 @@ def test_eap_fast_ext_cert_check(dev, ap
      run_ext_cert_check(dev, apdev, id)
  
  def run_ext_cert_check(dev, apdev, net_id):
@@ -948,7 +948,7 @@ Signed-off-by: Glenn Strauss <gstrauss at gluelogic.com>
  
 --- a/tests/hwsim/test_ap_ft.py
 +++ b/tests/hwsim/test_ap_ft.py
-@@ -2471,11 +2471,11 @@ def test_ap_ft_ap_oom5(dev, apdev):
+@@ -2474,11 +2474,11 @@ def test_ap_ft_ap_oom5(dev, apdev):
          # This will fail to roam
          dev[0].roam(bssid1, check_bssid=False)
  
@@ -1138,7 +1138,7 @@ Signed-off-by: Glenn Strauss <gstrauss at gluelogic.com>
      heavy_groups = [14, 15, 16]
      suitable_groups = [15, 16, 17, 18, 19, 20, 21]
      groups = [str(g) for g in sae_groups]
-@@ -2188,6 +2193,8 @@ def run_sae_pwe_group(dev, apdev, group)
+@@ -2193,6 +2198,8 @@ def run_sae_pwe_group(dev, apdev, group)
              logger.info("Add Brainpool EC groups since OpenSSL is new enough")
          elif tls.startswith("wolfSSL"):
              logger.info("Make sure Brainpool EC groups were enabled when compiling wolfSSL")
diff --git a/package/network/services/hostapd/patches/170-hostapd-update-cfs0-and-cfs1-for-160MHz.patch b/package/network/services/hostapd/patches/170-hostapd-update-cfs0-and-cfs1-for-160MHz.patch
index 710a3c851e..b0151b071f 100644
--- a/package/network/services/hostapd/patches/170-hostapd-update-cfs0-and-cfs1-for-160MHz.patch
+++ b/package/network/services/hostapd/patches/170-hostapd-update-cfs0-and-cfs1-for-160MHz.patch
@@ -120,7 +120,7 @@ Signed-off-by: P Praneesh <ppranees at codeaurora.org>
  		 * Convert 80+80 MHz channel width to new style as interop
 --- a/src/common/hw_features_common.c
 +++ b/src/common/hw_features_common.c
-@@ -808,6 +808,7 @@ int ieee80211ac_cap_check(u32 hw, u32 co
+@@ -811,6 +811,7 @@ int ieee80211ac_cap_check(u32 hw, u32 co
  	VHT_CAP_CHECK(VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB);
  	VHT_CAP_CHECK(VHT_CAP_RX_ANTENNA_PATTERN);
  	VHT_CAP_CHECK(VHT_CAP_TX_ANTENNA_PATTERN);
@@ -130,7 +130,7 @@ Signed-off-by: P Praneesh <ppranees at codeaurora.org>
  #undef VHT_CAP_CHECK_MAX
 --- a/src/common/ieee802_11_defs.h
 +++ b/src/common/ieee802_11_defs.h
-@@ -1348,6 +1348,8 @@ struct ieee80211_ampe_ie {
+@@ -1349,6 +1349,8 @@ struct ieee80211_ampe_ie {
  #define VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB     ((u32) BIT(26) | BIT(27))
  #define VHT_CAP_RX_ANTENNA_PATTERN                  ((u32) BIT(28))
  #define VHT_CAP_TX_ANTENNA_PATTERN                  ((u32) BIT(29))
diff --git a/package/network/services/hostapd/patches/180-driver_nl80211-fix-setting-QoS-map-on-secondary-BSSs.patch b/package/network/services/hostapd/patches/180-driver_nl80211-fix-setting-QoS-map-on-secondary-BSSs.patch
new file mode 100644
index 0000000000..4929c581ce
--- /dev/null
+++ b/package/network/services/hostapd/patches/180-driver_nl80211-fix-setting-QoS-map-on-secondary-BSSs.patch
@@ -0,0 +1,20 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Thu, 14 Sep 2023 10:53:50 +0200
+Subject: [PATCH] driver_nl80211: fix setting QoS map on secondary BSSs
+
+The setting is per-BSS, not per PHY
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -11341,7 +11341,7 @@ static int nl80211_set_qos_map(void *pri
+ 	wpa_hexdump(MSG_DEBUG, "nl80211: Setting QoS Map",
+ 		    qos_map_set, qos_map_set_len);
+ 
+-	if (!(msg = nl80211_drv_msg(drv, 0, NL80211_CMD_SET_QOS_MAP)) ||
++	if (!(msg = nl80211_bss_msg(bss, 0, NL80211_CMD_SET_QOS_MAP)) ||
+ 	    nla_put(msg, NL80211_ATTR_QOS_MAP, qos_map_set_len, qos_map_set)) {
+ 		nlmsg_free(msg);
+ 		return -ENOBUFS;
diff --git a/package/network/services/hostapd/patches/181-driver_nl80211-update-drv-ifindex-on-removing-the-fi.patch b/package/network/services/hostapd/patches/181-driver_nl80211-update-drv-ifindex-on-removing-the-fi.patch
new file mode 100644
index 0000000000..adfb21fb47
--- /dev/null
+++ b/package/network/services/hostapd/patches/181-driver_nl80211-update-drv-ifindex-on-removing-the-fi.patch
@@ -0,0 +1,18 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Thu, 14 Sep 2023 11:28:03 +0200
+Subject: [PATCH] driver_nl80211: update drv->ifindex on removing the first
+ BSS
+
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -8867,6 +8867,7 @@ static int wpa_driver_nl80211_if_remove(
+ 		if (drv->first_bss->next) {
+ 			drv->first_bss = drv->first_bss->next;
+ 			drv->ctx = drv->first_bss->ctx;
++			drv->ifindex = drv->first_bss->ifindex;
+ 			os_free(bss);
+ 		} else {
+ 			wpa_printf(MSG_DEBUG, "nl80211: No second BSS to reassign context to");
diff --git a/package/network/services/hostapd/patches/182-nl80211-move-nl80211_put_freq_params-call-outside-of.patch b/package/network/services/hostapd/patches/182-nl80211-move-nl80211_put_freq_params-call-outside-of.patch
new file mode 100644
index 0000000000..395c645954
--- /dev/null
+++ b/package/network/services/hostapd/patches/182-nl80211-move-nl80211_put_freq_params-call-outside-of.patch
@@ -0,0 +1,34 @@
+From: Felix Fietkau <nbd at nbd.name>
+Date: Mon, 18 Sep 2023 16:47:41 +0200
+Subject: [PATCH] nl80211: move nl80211_put_freq_params call outside of
+ 802.11ax #ifdef
+
+The relevance of this call is not specific to 802.11ax, so it should be done
+even with CONFIG_IEEE80211AX disabled.
+
+Fixes: b3921db426ea ("nl80211: Add frequency info in start AP command")
+Signed-off-by: Felix Fietkau <nbd at nbd.name>
+---
+
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -5226,6 +5226,9 @@ static int wpa_driver_nl80211_set_ap(voi
+ 		nla_nest_end(msg, ftm);
+ 	}
+ 
++	if (params->freq && nl80211_put_freq_params(msg, params->freq) < 0)
++		goto fail;
++
+ #ifdef CONFIG_IEEE80211AX
+ 	if (params->he_spr_ctrl) {
+ 		struct nlattr *spr;
+@@ -5260,9 +5263,6 @@ static int wpa_driver_nl80211_set_ap(voi
+ 		nla_nest_end(msg, spr);
+ 	}
+ 
+-	if (params->freq && nl80211_put_freq_params(msg, params->freq) < 0)
+-		goto fail;
+-
+ 	if (params->freq && params->freq->he_enabled) {
+ 		struct nlattr *bss_color;
+ 
diff --git a/package/network/services/hostapd/patches/200-multicall.patch b/package/network/services/hostapd/patches/200-multicall.patch
index f7e797a9c8..e3ed00f2de 100644
--- a/package/network/services/hostapd/patches/200-multicall.patch
+++ b/package/network/services/hostapd/patches/200-multicall.patch
@@ -156,7 +156,7 @@
  wpa_cli.exe: wpa_cli
 --- a/src/drivers/driver.h
 +++ b/src/drivers/driver.h
-@@ -6651,8 +6651,8 @@ union wpa_event_data {
+@@ -6667,8 +6667,8 @@ union wpa_event_data {
   * Driver wrapper code should call this function whenever an event is received
   * from the driver.
   */
@@ -167,7 +167,7 @@
  
  /**
   * wpa_supplicant_event_global - Report a driver event for wpa_supplicant
-@@ -6664,7 +6664,7 @@ void wpa_supplicant_event(void *ctx, enu
+@@ -6680,7 +6680,7 @@ void wpa_supplicant_event(void *ctx, enu
   * Same as wpa_supplicant_event(), but we search for the interface in
   * wpa_global.
   */
@@ -178,7 +178,7 @@
  /*
 --- a/src/ap/drv_callbacks.c
 +++ b/src/ap/drv_callbacks.c
-@@ -1994,8 +1994,8 @@ err:
+@@ -2184,8 +2184,8 @@ err:
  #endif /* CONFIG_OWE */
  
  
@@ -189,7 +189,7 @@
  {
  	struct hostapd_data *hapd = ctx;
  #ifndef CONFIG_NO_STDOUT_DEBUG
-@@ -2271,7 +2271,7 @@ void wpa_supplicant_event(void *ctx, enu
+@@ -2489,7 +2489,7 @@ void wpa_supplicant_event(void *ctx, enu
  }
  
  
@@ -231,7 +231,7 @@
  	os_memset(&global, 0, sizeof(global));
 --- a/wpa_supplicant/events.c
 +++ b/wpa_supplicant/events.c
-@@ -5345,8 +5345,8 @@ static void wpas_link_reconfig(struct wp
+@@ -5353,8 +5353,8 @@ static void wpas_link_reconfig(struct wp
  }
  
  
@@ -242,7 +242,7 @@
  {
  	struct wpa_supplicant *wpa_s = ctx;
  	int resched;
-@@ -6264,7 +6264,7 @@ void wpa_supplicant_event(void *ctx, enu
+@@ -6272,7 +6272,7 @@ void wpa_supplicant_event(void *ctx, enu
  }
  
  
@@ -253,7 +253,7 @@
  	struct wpa_supplicant *wpa_s;
 --- a/wpa_supplicant/wpa_supplicant.c
 +++ b/wpa_supplicant/wpa_supplicant.c
-@@ -7435,7 +7435,6 @@ struct wpa_interface * wpa_supplicant_ma
+@@ -7462,7 +7462,6 @@ struct wpa_interface * wpa_supplicant_ma
  	return NULL;
  }
  
@@ -261,7 +261,7 @@
  /**
   * wpa_supplicant_match_existing - Match existing interfaces
   * @global: Pointer to global data from wpa_supplicant_init()
-@@ -7470,6 +7469,11 @@ static int wpa_supplicant_match_existing
+@@ -7497,6 +7496,11 @@ static int wpa_supplicant_match_existing
  
  #endif /* CONFIG_MATCH_IFACE */
  
@@ -273,7 +273,7 @@
  
  /**
   * wpa_supplicant_add_iface - Add a new network interface
-@@ -7726,6 +7730,8 @@ struct wpa_global * wpa_supplicant_init(
+@@ -7753,6 +7757,8 @@ struct wpa_global * wpa_supplicant_init(
  #ifndef CONFIG_NO_WPA_MSG
  	wpa_msg_register_ifname_cb(wpa_supplicant_msg_ifname_cb);
  #endif /* CONFIG_NO_WPA_MSG */
@@ -284,7 +284,7 @@
  		wpa_debug_open_file(params->wpa_debug_file_path);
 --- a/hostapd/main.c
 +++ b/hostapd/main.c
-@@ -685,6 +685,11 @@ fail:
+@@ -698,6 +698,11 @@ fail:
  	return -1;
  }
  
@@ -296,7 +296,7 @@
  
  #ifdef CONFIG_WPS
  static int gen_uuid(const char *txt_addr)
-@@ -778,6 +783,8 @@ int main(int argc, char *argv[])
+@@ -791,6 +796,8 @@ int main(int argc, char *argv[])
  		return -1;
  #endif /* CONFIG_DPP */
  
diff --git a/package/network/services/hostapd/patches/300-noscan.patch b/package/network/services/hostapd/patches/300-noscan.patch
index 1ea89043e8..3b5f4325de 100644
--- a/package/network/services/hostapd/patches/300-noscan.patch
+++ b/package/network/services/hostapd/patches/300-noscan.patch
@@ -13,7 +13,7 @@
  	} else if (os_strcmp(buf, "ht_capab") == 0) {
 --- a/src/ap/ap_config.h
 +++ b/src/ap/ap_config.h
-@@ -1072,6 +1072,8 @@ struct hostapd_config {
+@@ -1075,6 +1075,8 @@ struct hostapd_config {
  
  	int ht_op_mode_fixed;
  	u16 ht_capab;
@@ -24,7 +24,7 @@
  	int no_pri_sec_switch;
 --- a/src/ap/hw_features.c
 +++ b/src/ap/hw_features.c
-@@ -517,7 +517,8 @@ static int ieee80211n_check_40mhz(struct
+@@ -546,7 +546,8 @@ static int ieee80211n_check_40mhz(struct
  	int ret;
  
  	/* Check that HT40 is used and PRI / SEC switch is allowed */
diff --git a/package/network/services/hostapd/patches/310-rescan_immediately.patch b/package/network/services/hostapd/patches/310-rescan_immediately.patch
index a47546d38f..e12b2059b3 100644
--- a/package/network/services/hostapd/patches/310-rescan_immediately.patch
+++ b/package/network/services/hostapd/patches/310-rescan_immediately.patch
@@ -1,6 +1,6 @@
 --- a/wpa_supplicant/wpa_supplicant.c
 +++ b/wpa_supplicant/wpa_supplicant.c
-@@ -5740,7 +5740,7 @@ wpa_supplicant_alloc(struct wpa_supplica
+@@ -5767,7 +5767,7 @@ wpa_supplicant_alloc(struct wpa_supplica
  	if (wpa_s == NULL)
  		return NULL;
  	wpa_s->scan_req = INITIAL_SCAN_REQ;
diff --git a/package/network/services/hostapd/patches/340-reload_freq_change.patch b/package/network/services/hostapd/patches/340-reload_freq_change.patch
deleted file mode 100644
index ae6cd81ea4..0000000000
--- a/package/network/services/hostapd/patches/340-reload_freq_change.patch
+++ /dev/null
@@ -1,80 +0,0 @@
---- a/src/ap/hostapd.c
-+++ b/src/ap/hostapd.c
-@@ -143,6 +143,29 @@ static void hostapd_reload_bss(struct ho
- #endif /* CONFIG_NO_RADIUS */
- 
- 	ssid = &hapd->conf->ssid;
-+
-+	hostapd_set_freq(hapd, hapd->iconf->hw_mode, hapd->iface->freq,
-+			 hapd->iconf->channel,
-+			 hapd->iconf->enable_edmg,
-+			 hapd->iconf->edmg_channel,
-+			 hapd->iconf->ieee80211n,
-+			 hapd->iconf->ieee80211ac,
-+			 hapd->iconf->ieee80211ax,
-+			 hapd->iconf->ieee80211be,
-+			 hapd->iconf->secondary_channel,
-+			 hostapd_get_oper_chwidth(hapd->iconf),
-+			 hostapd_get_oper_centr_freq_seg0_idx(hapd->iconf),
-+			 hostapd_get_oper_centr_freq_seg1_idx(hapd->iconf));
-+
-+	if (hapd->iface->current_mode) {
-+		if (hostapd_prepare_rates(hapd->iface, hapd->iface->current_mode)) {
-+			wpa_printf(MSG_ERROR, "Failed to prepare rates table.");
-+			hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
-+				       HOSTAPD_LEVEL_WARNING,
-+				       "Failed to prepare rates table.");
-+		}
-+	}
-+
- 	if (!ssid->wpa_psk_set && ssid->wpa_psk && !ssid->wpa_psk->next &&
- 	    ssid->wpa_passphrase_set && ssid->wpa_passphrase) {
- 		/*
-@@ -251,6 +274,7 @@ int hostapd_reload_config(struct hostapd
- 	struct hostapd_data *hapd = iface->bss[0];
- 	struct hostapd_config *newconf, *oldconf;
- 	size_t j;
-+	int i;
- 
- 	if (iface->config_fname == NULL) {
- 		/* Only in-memory config in use - assume it has been updated */
-@@ -301,6 +325,17 @@ int hostapd_reload_config(struct hostapd
- 	}
- 	iface->conf = newconf;
- 
-+	for (i = 0; i < iface->num_hw_features; i++) {
-+		struct hostapd_hw_modes *mode = &iface->hw_features[i];
-+		if (mode->mode == iface->conf->hw_mode) {
-+			iface->current_mode = mode;
-+			break;
-+		}
-+	}
-+
-+	if (iface->conf->channel)
-+		iface->freq = hostapd_hw_get_freq(hapd, iface->conf->channel);
-+
- 	for (j = 0; j < iface->num_bss; j++) {
- 		hapd = iface->bss[j];
- 		if (!hapd->conf->config_id || !newconf->bss[j]->config_id ||
-@@ -308,21 +343,6 @@ int hostapd_reload_config(struct hostapd
- 			      newconf->bss[j]->config_id) != 0)
- 			hostapd_clear_old_bss(hapd);
- 		hapd->iconf = newconf;
--		hapd->iconf->channel = oldconf->channel;
--		hapd->iconf->acs = oldconf->acs;
--		hapd->iconf->secondary_channel = oldconf->secondary_channel;
--		hapd->iconf->ieee80211n = oldconf->ieee80211n;
--		hapd->iconf->ieee80211ac = oldconf->ieee80211ac;
--		hapd->iconf->ht_capab = oldconf->ht_capab;
--		hapd->iconf->vht_capab = oldconf->vht_capab;
--		hostapd_set_oper_chwidth(hapd->iconf,
--					 hostapd_get_oper_chwidth(oldconf));
--		hostapd_set_oper_centr_freq_seg0_idx(
--			hapd->iconf,
--			hostapd_get_oper_centr_freq_seg0_idx(oldconf));
--		hostapd_set_oper_centr_freq_seg1_idx(
--			hapd->iconf,
--			hostapd_get_oper_centr_freq_seg1_idx(oldconf));
- 		hapd->conf = newconf->bss[j];
- 		hostapd_reload_bss(hapd);
- 	}
diff --git a/package/network/services/hostapd/patches/360-ctrl_iface_reload.patch b/package/network/services/hostapd/patches/360-ctrl_iface_reload.patch
deleted file mode 100644
index 4d85ea11f9..0000000000
--- a/package/network/services/hostapd/patches/360-ctrl_iface_reload.patch
+++ /dev/null
@@ -1,106 +0,0 @@
---- a/hostapd/ctrl_iface.c
-+++ b/hostapd/ctrl_iface.c
-@@ -68,6 +68,7 @@
- #include "fst/fst_ctrl_iface.h"
- #include "config_file.h"
- #include "ctrl_iface.h"
-+#include "config_file.h"
- 
- 
- #define HOSTAPD_CLI_DUP_VALUE_MAX_LEN 256
-@@ -83,6 +84,7 @@ static void hostapd_ctrl_iface_send(stru
- 				    enum wpa_msg_type type,
- 				    const char *buf, size_t len);
- 
-+static char *reload_opts = NULL;
- 
- static int hostapd_ctrl_iface_attach(struct hostapd_data *hapd,
- 				     struct sockaddr_storage *from,
-@@ -134,6 +136,61 @@ static int hostapd_ctrl_iface_new_sta(st
- 	return 0;
- }
- 
-+static char *get_option(char *opt, char *str)
-+{
-+	int len = strlen(str);
-+
-+	if (!strncmp(opt, str, len))
-+		return opt + len;
-+	else
-+		return NULL;
-+}
-+
-+static struct hostapd_config *hostapd_ctrl_iface_config_read(const char *fname)
-+{
-+	struct hostapd_config *conf;
-+	char *opt, *val;
-+
-+	conf = hostapd_config_read(fname);
-+	if (!conf)
-+		return NULL;
-+
-+	for (opt = strtok(reload_opts, " ");
-+	     opt;
-+		 opt = strtok(NULL, " ")) {
-+
-+		if ((val = get_option(opt, "channel=")))
-+			conf->channel = atoi(val);
-+		else if ((val = get_option(opt, "ht_capab=")))
-+			conf->ht_capab = atoi(val);
-+		else if ((val = get_option(opt, "ht_capab_mask=")))
-+			conf->ht_capab &= atoi(val);
-+		else if ((val = get_option(opt, "sec_chan=")))
-+			conf->secondary_channel = atoi(val);
-+		else if ((val = get_option(opt, "hw_mode=")))
-+			conf->hw_mode = atoi(val);
-+		else if ((val = get_option(opt, "ieee80211n=")))
-+			conf->ieee80211n = atoi(val);
-+		else
-+			break;
-+	}
-+
-+	return conf;
-+}
-+
-+static int hostapd_ctrl_iface_update(struct hostapd_data *hapd, char *txt)
-+{
-+	struct hostapd_config * (*config_read_cb)(const char *config_fname);
-+	struct hostapd_iface *iface = hapd->iface;
-+
-+	config_read_cb = iface->interfaces->config_read_cb;
-+	iface->interfaces->config_read_cb = hostapd_ctrl_iface_config_read;
-+	reload_opts = txt;
-+
-+	hostapd_reload_config(iface);
-+
-+	iface->interfaces->config_read_cb = config_read_cb;
-+}
- 
- #ifdef NEED_AP_MLME
- static int hostapd_ctrl_iface_sa_query(struct hostapd_data *hapd,
-@@ -3564,6 +3621,8 @@ static int hostapd_ctrl_iface_receive_pr
- 	} else if (os_strncmp(buf, "VENDOR ", 7) == 0) {
- 		reply_len = hostapd_ctrl_iface_vendor(hapd, buf + 7, reply,
- 						      reply_size);
-+	} else if (os_strncmp(buf, "UPDATE ", 7) == 0) {
-+		hostapd_ctrl_iface_update(hapd, buf + 7);
- 	} else if (os_strcmp(buf, "ERP_FLUSH") == 0) {
- 		ieee802_1x_erp_flush(hapd);
- #ifdef RADIUS_SERVER
---- a/src/ap/ctrl_iface_ap.c
-+++ b/src/ap/ctrl_iface_ap.c
-@@ -1023,7 +1023,13 @@ int hostapd_parse_csa_settings(const cha
- 
- int hostapd_ctrl_iface_stop_ap(struct hostapd_data *hapd)
- {
--	return hostapd_drv_stop_ap(hapd);
-+	struct hostapd_iface *iface = hapd->iface;
-+	int i;
-+
-+	for (i = 0; i < iface->num_bss; i++)
-+		hostapd_drv_stop_ap(iface->bss[i]);
-+
-+	return 0;
- }
- 
- 
diff --git a/package/network/services/hostapd/patches/370-ap_sta_support.patch b/package/network/services/hostapd/patches/370-ap_sta_support.patch
deleted file mode 100644
index 3baad2a52e..0000000000
--- a/package/network/services/hostapd/patches/370-ap_sta_support.patch
+++ /dev/null
@@ -1,392 +0,0 @@
---- a/wpa_supplicant/Makefile
-+++ b/wpa_supplicant/Makefile
-@@ -126,6 +126,8 @@ OBJS_c += ../src/utils/common.o
- OBJS_c += ../src/common/cli.o
- OBJS += wmm_ac.o
- 
-+OBJS += ../src/common/wpa_ctrl.o
-+
- ifndef CONFIG_OS
- ifdef CONFIG_NATIVE_WINDOWS
- CONFIG_OS=win32
---- a/wpa_supplicant/bss.c
-+++ b/wpa_supplicant/bss.c
-@@ -11,6 +11,7 @@
- #include "utils/common.h"
- #include "utils/eloop.h"
- #include "common/ieee802_11_defs.h"
-+#include "common/ieee802_11_common.h"
- #include "drivers/driver.h"
- #include "eap_peer/eap.h"
- #include "wpa_supplicant_i.h"
-@@ -283,6 +284,10 @@ void calculate_update_time(const struct
- static void wpa_bss_copy_res(struct wpa_bss *dst, struct wpa_scan_res *src,
- 			     struct os_reltime *fetch_time)
- {
-+	struct ieee80211_ht_capabilities *capab;
-+	struct ieee80211_ht_operation *oper;
-+	struct ieee802_11_elems elems;
-+
- 	dst->flags = src->flags;
- 	os_memcpy(dst->bssid, src->bssid, ETH_ALEN);
- 	dst->freq = src->freq;
-@@ -296,6 +301,15 @@ static void wpa_bss_copy_res(struct wpa_
- 	dst->est_throughput = src->est_throughput;
- 	dst->snr = src->snr;
- 
-+	memset(&elems, 0, sizeof(elems));
-+	ieee802_11_parse_elems((u8 *) (src + 1), src->ie_len, &elems, 0);
-+	capab = (struct ieee80211_ht_capabilities *) elems.ht_capabilities;
-+	oper = (struct ieee80211_ht_operation *) elems.ht_operation;
-+	if (capab)
-+		dst->ht_capab = le_to_host16(capab->ht_capabilities_info);
-+	if (oper)
-+		dst->ht_param = oper->ht_param;
-+
- 	calculate_update_time(fetch_time, src->age, &dst->last_update);
- }
- 
---- a/wpa_supplicant/bss.h
-+++ b/wpa_supplicant/bss.h
-@@ -94,6 +94,10 @@ struct wpa_bss {
- 	u8 ssid[SSID_MAX_LEN];
- 	/** Length of SSID */
- 	size_t ssid_len;
-+	/** HT capabilities */
-+	u16 ht_capab;
-+	/* Five octets of HT Operation Information */
-+	u8 ht_param;
- 	/** Frequency of the channel in MHz (e.g., 2412 = channel 1) */
- 	int freq;
- 	/** Beacon interval in TUs (host byte order) */
---- a/wpa_supplicant/main.c
-+++ b/wpa_supplicant/main.c
-@@ -35,7 +35,7 @@ static void usage(void)
- 	       "vW] [-P<pid file>] "
- 	       "[-g<global ctrl>] \\\n"
- 	       "        [-G<group>] \\\n"
--	       "        -i<ifname> -c<config file> [-C<ctrl>] [-D<driver>] "
-+	       "        -i<ifname> -c<config file> [-C<ctrl>] [-D<driver>] [-H<hostapd path>] "
- 	       "[-p<driver_param>] \\\n"
- 	       "        [-b<br_ifname>] [-e<entropy file>]"
- #ifdef CONFIG_DEBUG_FILE
-@@ -75,6 +75,7 @@ static void usage(void)
- 	       "  -g = global ctrl_interface\n"
- 	       "  -G = global ctrl_interface group\n"
- 	       "  -h = show this help text\n"
-+	       "  -H = connect to a hostapd instance to manage state changes\n"
- 	       "  -i = interface name\n"
- 	       "  -I = additional configuration file\n"
- 	       "  -K = include keys (passwords, etc.) in debug output\n"
-@@ -202,7 +203,7 @@ int main(int argc, char *argv[])
- 
- 	for (;;) {
- 		c = getopt(argc, argv,
--			   "b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuvW");
-+			   "b:Bc:C:D:de:f:g:G:hH:i:I:KLMm:No:O:p:P:qsTtuvW");
- 		if (c < 0)
- 			break;
- 		switch (c) {
-@@ -249,6 +250,9 @@ int main(int argc, char *argv[])
- 			usage();
- 			exitcode = 0;
- 			goto out;
-+		case 'H':
-+			iface->hostapd_ctrl = optarg;
-+			break;
- 		case 'i':
- 			iface->ifname = optarg;
- 			break;
---- a/wpa_supplicant/wpa_supplicant.c
-+++ b/wpa_supplicant/wpa_supplicant.c
-@@ -131,6 +131,54 @@ static void wpas_update_fils_connect_par
- static void wpas_update_owe_connect_params(struct wpa_supplicant *wpa_s);
- #endif /* CONFIG_OWE */
- 
-+static int hostapd_stop(struct wpa_supplicant *wpa_s)
-+{
-+	const char *cmd = "STOP_AP";
-+	char buf[256];
-+	size_t len = sizeof(buf);
-+
-+	if (wpa_ctrl_request(wpa_s->hostapd, cmd, os_strlen(cmd), buf, &len, NULL) < 0) {
-+		wpa_printf(MSG_ERROR, "\nFailed to stop hostapd AP interfaces\n");
-+		return -1;
-+	}
-+	return 0;
-+}
-+
-+static int hostapd_reload(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
-+{
-+	char *cmd = NULL;
-+	char buf[256];
-+	size_t len = sizeof(buf);
-+	enum hostapd_hw_mode hw_mode;
-+	u8 channel;
-+	int sec_chan = 0;
-+	int ret;
-+
-+	if (!bss)
-+		return -1;
-+
-+	if (bss->ht_param & HT_INFO_HT_PARAM_STA_CHNL_WIDTH) {
-+		int sec = bss->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
-+		if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
-+			sec_chan = 1;
-+		else if (sec ==  HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
-+			sec_chan = -1;
-+	}
-+
-+	hw_mode = ieee80211_freq_to_chan(bss->freq, &channel);
-+	if (asprintf(&cmd, "UPDATE channel=%d sec_chan=%d hw_mode=%d",
-+		     channel, sec_chan, hw_mode) < 0)
-+		return -1;
-+
-+	ret = wpa_ctrl_request(wpa_s->hostapd, cmd, os_strlen(cmd), buf, &len, NULL);
-+	free(cmd);
-+
-+	if (ret < 0) {
-+		wpa_printf(MSG_ERROR, "\nFailed to reload hostapd AP interfaces\n");
-+		return -1;
-+	}
-+	return 0;
-+}
- 
- #ifdef CONFIG_WEP
- /* Configure default/group WEP keys for static WEP */
-@@ -1026,6 +1074,8 @@ void wpa_supplicant_set_state(struct wpa
- 
- 		sme_sched_obss_scan(wpa_s, 1);
- 
-+		if (wpa_s->hostapd)
-+			hostapd_reload(wpa_s, wpa_s->current_bss);
- #if defined(CONFIG_FILS) && defined(IEEE8021X_EAPOL)
- 		if (!fils_hlp_sent && ssid && ssid->eap.erp)
- 			update_fils_connect_params = true;
-@@ -1036,6 +1086,8 @@ void wpa_supplicant_set_state(struct wpa
- #endif /* CONFIG_OWE */
- 	} else if (state == WPA_DISCONNECTED || state == WPA_ASSOCIATING ||
- 		   state == WPA_ASSOCIATED) {
-+		if (wpa_s->hostapd)
-+			hostapd_stop(wpa_s);
- 		wpa_s->new_connection = 1;
- 		wpa_drv_set_operstate(wpa_s, 0);
- #ifndef IEEE8021X_EAPOL
-@@ -2537,6 +2589,8 @@ void wpa_supplicant_associate(struct wpa
- 			return;
- 		}
- 		wpa_s->current_bss = bss;
-+		if (wpa_s->hostapd)
-+			hostapd_reload(wpa_s, wpa_s->current_bss);
- #else /* CONFIG_MESH */
- 		wpa_msg(wpa_s, MSG_ERROR,
- 			"mesh mode support not included in the build");
-@@ -7037,6 +7091,16 @@ static int wpa_supplicant_init_iface(str
- 			   sizeof(wpa_s->bridge_ifname));
- 	}
- 
-+	if (iface->hostapd_ctrl) {
-+		wpa_s->hostapd = wpa_ctrl_open(iface->hostapd_ctrl);
-+		if (!wpa_s->hostapd) {
-+			wpa_printf(MSG_ERROR, "\nFailed to connect to hostapd\n");
-+			return -1;
-+		}
-+		if (hostapd_stop(wpa_s) < 0)
-+			return -1;
-+	}
-+
- 	/* RSNA Supplicant Key Management - INITIALIZE */
- 	eapol_sm_notify_portEnabled(wpa_s->eapol, false);
- 	eapol_sm_notify_portValid(wpa_s->eapol, false);
-@@ -7379,6 +7443,11 @@ static void wpa_supplicant_deinit_iface(
- 	if (terminate)
- 		wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_TERMINATING);
- 
-+	if (wpa_s->hostapd) {
-+		wpa_ctrl_close(wpa_s->hostapd);
-+		wpa_s->hostapd = NULL;
-+	}
-+
- 	wpa_supplicant_ctrl_iface_deinit(wpa_s, wpa_s->ctrl_iface);
- 	wpa_s->ctrl_iface = NULL;
- 
---- a/wpa_supplicant/wpa_supplicant_i.h
-+++ b/wpa_supplicant/wpa_supplicant_i.h
-@@ -106,6 +106,11 @@ struct wpa_interface {
- 	const char *ifname;
- 
- 	/**
-+	 * hostapd_ctrl - path to hostapd control socket for notification
-+	 */
-+	const char *hostapd_ctrl;
-+
-+	/**
- 	 * bridge_ifname - Optional bridge interface name
- 	 *
- 	 * If the driver interface (ifname) is included in a Linux bridge
-@@ -665,6 +670,8 @@ struct wpa_supplicant {
- #endif /* CONFIG_CTRL_IFACE_BINDER */
- 	char bridge_ifname[16];
- 
-+	struct wpa_ctrl *hostapd;
-+
- 	char *confname;
- 	char *confanother;
- 
---- a/hostapd/ctrl_iface.c
-+++ b/hostapd/ctrl_iface.c
-@@ -2751,6 +2751,12 @@ static int hostapd_ctrl_iface_chan_switc
- 		return 0;
- 	}
- 
-+	if (os_strstr(pos, " auto-ht")) {
-+		settings.freq_params.ht_enabled = iface->conf->ieee80211n;
-+		settings.freq_params.vht_enabled = iface->conf->ieee80211ac;
-+		settings.freq_params.he_enabled = iface->conf->ieee80211ax;
-+	}
-+
- 	for (i = 0; i < iface->num_bss; i++) {
- 
- 		/* Save CHAN_SWITCH VHT, HE, and EHT config */
---- a/src/ap/beacon.c
-+++ b/src/ap/beacon.c
-@@ -2108,11 +2108,6 @@ static int __ieee802_11_set_beacon(struc
- 		return -1;
- 	}
- 
--	if (hapd->csa_in_progress) {
--		wpa_printf(MSG_ERROR, "Cannot set beacons during CSA period");
--		return -1;
--	}
--
- 	hapd->beacon_set_done = 1;
- 
- 	if (ieee802_11_build_ap_params(hapd, &params) < 0)
---- a/wpa_supplicant/events.c
-+++ b/wpa_supplicant/events.c
-@@ -5345,6 +5345,60 @@ static void wpas_link_reconfig(struct wp
- }
- 
- 
-+static void
-+supplicant_ch_switch_started(struct wpa_supplicant *wpa_s,
-+			    union wpa_event_data *data)
-+{
-+	char buf[256];
-+	size_t len = sizeof(buf);
-+	char *cmd = NULL;
-+	int width = 20;
-+	int ret;
-+
-+	if (!wpa_s->hostapd)
-+		return;
-+
-+	wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_CHANNEL_SWITCH
-+		"count=%d freq=%d ht_enabled=%d ch_offset=%d ch_width=%s cf1=%d cf2=%d",
-+		data->ch_switch.count,
-+		data->ch_switch.freq,
-+		data->ch_switch.ht_enabled,
-+		data->ch_switch.ch_offset,
-+		channel_width_to_string(data->ch_switch.ch_width),
-+		data->ch_switch.cf1,
-+		data->ch_switch.cf2);
-+
-+	switch (data->ch_switch.ch_width) {
-+	case CHAN_WIDTH_20_NOHT:
-+	case CHAN_WIDTH_20:
-+		width = 20;
-+		break;
-+	case CHAN_WIDTH_40:
-+		width = 40;
-+		break;
-+	case CHAN_WIDTH_80:
-+		width = 80;
-+		break;
-+	case CHAN_WIDTH_160:
-+	case CHAN_WIDTH_80P80:
-+		width = 160;
-+		break;
-+	}
-+
-+	asprintf(&cmd, "CHAN_SWITCH %d %d sec_channel_offset=%d center_freq1=%d center_freq2=%d, bandwidth=%d auto-ht\n",
-+		data->ch_switch.count - 1,
-+		data->ch_switch.freq,
-+		data->ch_switch.ch_offset,
-+		data->ch_switch.cf1,
-+		data->ch_switch.cf2,
-+		width);
-+	ret = wpa_ctrl_request(wpa_s->hostapd, cmd, os_strlen(cmd), buf, &len, NULL);
-+	free(cmd);
-+
-+	if (ret < 0)
-+		wpa_printf(MSG_ERROR, "\nFailed to reload hostapd AP interfaces\n");
-+}
-+
- void supplicant_event(void *ctx, enum wpa_event_type event,
- 		      union wpa_event_data *data)
- {
-@@ -5697,8 +5751,10 @@ void supplicant_event(void *ctx, enum wp
- 			channel_width_to_string(data->ch_switch.ch_width),
- 			data->ch_switch.cf1,
- 			data->ch_switch.cf2);
--		if (event == EVENT_CH_SWITCH_STARTED)
-+		if (event == EVENT_CH_SWITCH_STARTED) {
-+			supplicant_ch_switch_started(wpa_s, data);
- 			break;
-+		}
- 
- 		wpa_s->assoc_freq = data->ch_switch.freq;
- 		wpa_s->current_ssid->frequency = data->ch_switch.freq;
---- a/src/drivers/driver.h
-+++ b/src/drivers/driver.h
-@@ -6421,6 +6421,7 @@ union wpa_event_data {
- 
- 	/**
- 	 * struct ch_switch
-+	 * @count: Count until channel switch activates
- 	 * @freq: Frequency of new channel in MHz
- 	 * @ht_enabled: Whether this is an HT channel
- 	 * @ch_offset: Secondary channel offset
-@@ -6431,6 +6432,7 @@ union wpa_event_data {
- 	 * @punct_bitmap: Puncturing bitmap
- 	 */
- 	struct ch_switch {
-+		int count;
- 		int freq;
- 		int ht_enabled;
- 		int ch_offset;
---- a/src/drivers/driver_nl80211_event.c
-+++ b/src/drivers/driver_nl80211_event.c
-@@ -1202,6 +1202,7 @@ static void mlme_event_ch_switch(struct
- 				 struct nlattr *bw, struct nlattr *cf1,
- 				 struct nlattr *cf2,
- 				 struct nlattr *punct_bitmap,
-+				 struct nlattr *count,
- 				 int finished)
- {
- 	struct i802_bss *bss;
-@@ -1265,6 +1266,8 @@ static void mlme_event_ch_switch(struct
- 		data.ch_switch.cf1 = nla_get_u32(cf1);
- 	if (cf2)
- 		data.ch_switch.cf2 = nla_get_u32(cf2);
-+	if (count)
-+		data.ch_switch.count = nla_get_u32(count);
- 
- 	if (finished)
- 		bss->flink->freq = data.ch_switch.freq;
-@@ -3848,6 +3851,7 @@ static void do_process_drv_event(struct
- 				     tb[NL80211_ATTR_CENTER_FREQ1],
- 				     tb[NL80211_ATTR_CENTER_FREQ2],
- 				     tb[NL80211_ATTR_PUNCT_BITMAP],
-+				     tb[NL80211_ATTR_CH_SWITCH_COUNT],
- 				     0);
- 		break;
- 	case NL80211_CMD_CH_SWITCH_NOTIFY:
-@@ -3860,6 +3864,7 @@ static void do_process_drv_event(struct
- 				     tb[NL80211_ATTR_CENTER_FREQ1],
- 				     tb[NL80211_ATTR_CENTER_FREQ2],
- 				     tb[NL80211_ATTR_PUNCT_BITMAP],
-+				     NULL,
- 				     1);
- 		break;
- 	case NL80211_CMD_DISCONNECT:
diff --git a/package/network/services/hostapd/patches/380-disable_ctrl_iface_mib.patch b/package/network/services/hostapd/patches/380-disable_ctrl_iface_mib.patch
index 456599db09..f7720fce2f 100644
--- a/package/network/services/hostapd/patches/380-disable_ctrl_iface_mib.patch
+++ b/package/network/services/hostapd/patches/380-disable_ctrl_iface_mib.patch
@@ -12,7 +12,7 @@
  else
 --- a/hostapd/ctrl_iface.c
 +++ b/hostapd/ctrl_iface.c
-@@ -3377,6 +3377,7 @@ static int hostapd_ctrl_iface_receive_pr
+@@ -3314,6 +3314,7 @@ static int hostapd_ctrl_iface_receive_pr
  						      reply_size);
  	} else if (os_strcmp(buf, "STATUS-DRIVER") == 0) {
  		reply_len = hostapd_drv_status(hapd, reply, reply_size);
@@ -20,7 +20,7 @@
  	} else if (os_strcmp(buf, "MIB") == 0) {
  		reply_len = ieee802_11_get_mib(hapd, reply, reply_size);
  		if (reply_len >= 0) {
-@@ -3418,6 +3419,7 @@ static int hostapd_ctrl_iface_receive_pr
+@@ -3355,6 +3356,7 @@ static int hostapd_ctrl_iface_receive_pr
  	} else if (os_strncmp(buf, "STA-NEXT ", 9) == 0) {
  		reply_len = hostapd_ctrl_iface_sta_next(hapd, buf + 9, reply,
  							reply_size);
@@ -30,7 +30,7 @@
  			reply_len = -1;
 --- a/wpa_supplicant/Makefile
 +++ b/wpa_supplicant/Makefile
-@@ -985,6 +985,9 @@ ifdef CONFIG_FILS
+@@ -983,6 +983,9 @@ ifdef CONFIG_FILS
  OBJS += ../src/ap/fils_hlp.o
  endif
  ifdef CONFIG_CTRL_IFACE
@@ -51,7 +51,7 @@
  		if (wpa_s->ap_iface) {
  			pos += ap_ctrl_iface_wpa_get_status(wpa_s, pos,
  							    end - pos,
-@@ -11964,6 +11964,7 @@ char * wpa_supplicant_ctrl_iface_process
+@@ -12087,6 +12087,7 @@ char * wpa_supplicant_ctrl_iface_process
  			reply_len = -1;
  	} else if (os_strncmp(buf, "NOTE ", 5) == 0) {
  		wpa_printf(MSG_INFO, "NOTE: %s", buf + 5);
@@ -59,7 +59,7 @@
  	} else if (os_strcmp(buf, "MIB") == 0) {
  		reply_len = wpa_sm_get_mib(wpa_s->wpa, reply, reply_size);
  		if (reply_len >= 0) {
-@@ -11976,6 +11977,7 @@ char * wpa_supplicant_ctrl_iface_process
+@@ -12099,6 +12100,7 @@ char * wpa_supplicant_ctrl_iface_process
  				reply_size - reply_len);
  #endif /* CONFIG_MACSEC */
  		}
@@ -67,7 +67,7 @@
  	} else if (os_strncmp(buf, "STATUS", 6) == 0) {
  		reply_len = wpa_supplicant_ctrl_iface_status(
  			wpa_s, buf + 6, reply, reply_size);
-@@ -12464,6 +12466,7 @@ char * wpa_supplicant_ctrl_iface_process
+@@ -12587,6 +12589,7 @@ char * wpa_supplicant_ctrl_iface_process
  		reply_len = wpa_supplicant_ctrl_iface_bss(
  			wpa_s, buf + 4, reply, reply_size);
  #ifdef CONFIG_AP
@@ -75,7 +75,7 @@
  	} else if (os_strcmp(buf, "STA-FIRST") == 0) {
  		reply_len = ap_ctrl_iface_sta_first(wpa_s, reply, reply_size);
  	} else if (os_strncmp(buf, "STA ", 4) == 0) {
-@@ -12472,12 +12475,15 @@ char * wpa_supplicant_ctrl_iface_process
+@@ -12595,12 +12598,15 @@ char * wpa_supplicant_ctrl_iface_process
  	} else if (os_strncmp(buf, "STA-NEXT ", 9) == 0) {
  		reply_len = ap_ctrl_iface_sta_next(wpa_s, buf + 9, reply,
  						   reply_size);
diff --git a/package/network/services/hostapd/patches/390-wpa_ie_cap_workaround.patch b/package/network/services/hostapd/patches/390-wpa_ie_cap_workaround.patch
index 40c39ff29c..4592c34127 100644
--- a/package/network/services/hostapd/patches/390-wpa_ie_cap_workaround.patch
+++ b/package/network/services/hostapd/patches/390-wpa_ie_cap_workaround.patch
@@ -1,6 +1,6 @@
 --- a/src/common/wpa_common.c
 +++ b/src/common/wpa_common.c
-@@ -2719,6 +2719,31 @@ u32 wpa_akm_to_suite(int akm)
+@@ -2841,6 +2841,31 @@ u32 wpa_akm_to_suite(int akm)
  }
  
  
@@ -32,7 +32,7 @@
  int wpa_compare_rsn_ie(int ft_initial_assoc,
  		       const u8 *ie1, size_t ie1len,
  		       const u8 *ie2, size_t ie2len)
-@@ -2726,8 +2751,19 @@ int wpa_compare_rsn_ie(int ft_initial_as
+@@ -2848,8 +2873,19 @@ int wpa_compare_rsn_ie(int ft_initial_as
  	if (ie1 == NULL || ie2 == NULL)
  		return -1;
  
diff --git a/package/network/services/hostapd/patches/420-indicate-features.patch b/package/network/services/hostapd/patches/420-indicate-features.patch
index 786b83d315..07df8e5a9a 100644
--- a/package/network/services/hostapd/patches/420-indicate-features.patch
+++ b/package/network/services/hostapd/patches/420-indicate-features.patch
@@ -9,7 +9,7 @@
  
  struct hapd_global {
  	void **drv_priv;
-@@ -786,7 +786,7 @@ int main(int argc, char *argv[])
+@@ -799,7 +799,7 @@ int main(int argc, char *argv[])
  	wpa_supplicant_event = hostapd_wpa_event;
  	wpa_supplicant_event_global = hostapd_wpa_event_global;
  	for (;;) {
@@ -18,7 +18,7 @@
  		if (c < 0)
  			break;
  		switch (c) {
-@@ -823,6 +823,8 @@ int main(int argc, char *argv[])
+@@ -836,6 +836,8 @@ int main(int argc, char *argv[])
  			break;
  #endif /* CONFIG_DEBUG_LINUX_TRACING */
  		case 'v':
@@ -37,16 +37,16 @@
  #include "crypto/crypto.h"
  #include "fst/fst.h"
  #include "wpa_supplicant_i.h"
-@@ -203,7 +204,7 @@ int main(int argc, char *argv[])
+@@ -202,7 +203,7 @@ int main(int argc, char *argv[])
  
  	for (;;) {
  		c = getopt(argc, argv,
--			   "b:Bc:C:D:de:f:g:G:hH:i:I:KLMm:No:O:p:P:qsTtuvW");
-+			   "b:Bc:C:D:de:f:g:G:hH:i:I:KLMm:No:O:p:P:qsTtuv::W");
+-			   "b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuvW");
++			   "b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuv::W");
  		if (c < 0)
  			break;
  		switch (c) {
-@@ -306,8 +307,12 @@ int main(int argc, char *argv[])
+@@ -302,8 +303,12 @@ int main(int argc, char *argv[])
  			break;
  #endif /* CONFIG_CTRL_IFACE_DBUS_NEW */
  		case 'v':
diff --git a/package/network/services/hostapd/patches/432-missing-typedef.patch b/package/network/services/hostapd/patches/432-missing-typedef.patch
deleted file mode 100644
index 7a100f1a0d..0000000000
--- a/package/network/services/hostapd/patches/432-missing-typedef.patch
+++ /dev/null
@@ -1,10 +0,0 @@
---- a/src/drivers/linux_wext.h
-+++ b/src/drivers/linux_wext.h
-@@ -26,6 +26,7 @@ typedef int32_t __s32;
- typedef uint16_t __u16;
- typedef int16_t __s16;
- typedef uint8_t __u8;
-+typedef int8_t __s8;
- #ifndef __user
- #define __user
- #endif /* __user */
diff --git a/package/network/services/hostapd/patches/450-scan_wait.patch b/package/network/services/hostapd/patches/450-scan_wait.patch
deleted file mode 100644
index 45886896ee..0000000000
--- a/package/network/services/hostapd/patches/450-scan_wait.patch
+++ /dev/null
@@ -1,73 +0,0 @@
---- a/hostapd/main.c
-+++ b/hostapd/main.c
-@@ -39,6 +39,8 @@ struct hapd_global {
- };
- 
- static struct hapd_global global;
-+static int daemonize = 0;
-+static char *pid_file = NULL;
- 
- 
- #ifndef CONFIG_NO_HOSTAPD_LOGGER
-@@ -146,6 +148,14 @@ static void hostapd_logger_cb(void *ctx,
- }
- #endif /* CONFIG_NO_HOSTAPD_LOGGER */
- 
-+static void hostapd_setup_complete_cb(void *ctx)
-+{
-+	if (daemonize && os_daemonize(pid_file)) {
-+		perror("daemon");
-+		return;
-+	}
-+	daemonize = 0;
-+}
- 
- /**
-  * hostapd_driver_init - Preparate driver interface
-@@ -217,6 +227,8 @@ static int hostapd_driver_init(struct ho
- 	}
- #endif /* CONFIG_IEEE80211BE */
- 
-+	hapd->setup_complete_cb = hostapd_setup_complete_cb;
-+
- 	/* Initialize the driver interface */
- 	if (!(b[0] | b[1] | b[2] | b[3] | b[4] | b[5]))
- 		b = NULL;
-@@ -497,8 +509,6 @@ static void hostapd_global_deinit(const
- #endif /* CONFIG_NATIVE_WINDOWS */
- 
- 	eap_server_unregister_methods();
--
--	os_daemonize_terminate(pid_file);
- }
- 
- 
-@@ -524,18 +534,6 @@ static int hostapd_global_run(struct hap
- 	}
- #endif /* EAP_SERVER_TNC */
- 
--	if (daemonize) {
--		if (os_daemonize(pid_file)) {
--			wpa_printf(MSG_ERROR, "daemon: %s", strerror(errno));
--			return -1;
--		}
--		if (eloop_sock_requeue()) {
--			wpa_printf(MSG_ERROR, "eloop_sock_requeue: %s",
--				   strerror(errno));
--			return -1;
--		}
--	}
--
- 	eloop_run();
- 
- 	return 0;
-@@ -739,8 +737,7 @@ int main(int argc, char *argv[])
- 	struct hapd_interfaces interfaces;
- 	int ret = 1;
- 	size_t i, j;
--	int c, debug = 0, daemonize = 0;
--	char *pid_file = NULL;
-+	int c, debug = 0;
- 	const char *log_file = NULL;
- 	const char *entropy_file = NULL;
- 	char **bss_config = NULL, **tmp_bss;
diff --git a/package/network/services/hostapd/patches/460-wpa_supplicant-add-new-config-params-to-be-used-with.patch b/package/network/services/hostapd/patches/460-wpa_supplicant-add-new-config-params-to-be-used-with.patch
index 4c72868139..c6fe54efed 100644
--- a/package/network/services/hostapd/patches/460-wpa_supplicant-add-new-config-params-to-be-used-with.patch
+++ b/package/network/services/hostapd/patches/460-wpa_supplicant-add-new-config-params-to-be-used-with.patch
@@ -174,7 +174,7 @@ Signed-hostap: Antonio Quartulli <ordex at autistici.org>
  	 * macsec_policy - Determines the policy for MACsec secure session
 --- a/wpa_supplicant/wpa_supplicant.c
 +++ b/wpa_supplicant/wpa_supplicant.c
-@@ -4203,6 +4203,12 @@ static void wpas_start_assoc_cb(struct w
+@@ -4175,6 +4175,12 @@ static void wpas_start_assoc_cb(struct w
  			params.beacon_int = ssid->beacon_int;
  		else
  			params.beacon_int = wpa_s->conf->beacon_int;
diff --git a/package/network/services/hostapd/patches/463-add-mcast_rate-to-11s.patch b/package/network/services/hostapd/patches/463-add-mcast_rate-to-11s.patch
index be9e0507d6..daa36c2f35 100644
--- a/package/network/services/hostapd/patches/463-add-mcast_rate-to-11s.patch
+++ b/package/network/services/hostapd/patches/463-add-mcast_rate-to-11s.patch
@@ -29,7 +29,7 @@ Tested-by: Simon Wunderlich <simon.wunderlich at openmesh.com>
  struct wpa_driver_set_key_params {
 --- a/src/drivers/driver_nl80211.c
 +++ b/src/drivers/driver_nl80211.c
-@@ -11626,6 +11626,18 @@ static int nl80211_put_mesh_id(struct nl
+@@ -11667,6 +11667,18 @@ static int nl80211_put_mesh_id(struct nl
  }
  
  
@@ -48,7 +48,7 @@ Tested-by: Simon Wunderlich <simon.wunderlich at openmesh.com>
  static int nl80211_put_mesh_config(struct nl_msg *msg,
  				   struct wpa_driver_mesh_bss_params *params)
  {
-@@ -11687,6 +11699,7 @@ static int nl80211_join_mesh(struct i802
+@@ -11728,6 +11740,7 @@ static int nl80211_join_mesh(struct i802
  	    nl80211_put_basic_rates(msg, params->basic_rates) ||
  	    nl80211_put_mesh_id(msg, params->meshid, params->meshid_len) ||
  	    nl80211_put_beacon_int(msg, params->beacon_int) ||
diff --git a/package/network/services/hostapd/patches/464-fix-mesh-obss-check.patch b/package/network/services/hostapd/patches/464-fix-mesh-obss-check.patch
index c7e8cf25ce..4d7d85f4ab 100644
--- a/package/network/services/hostapd/patches/464-fix-mesh-obss-check.patch
+++ b/package/network/services/hostapd/patches/464-fix-mesh-obss-check.patch
@@ -1,6 +1,6 @@
 --- a/wpa_supplicant/wpa_supplicant.c
 +++ b/wpa_supplicant/wpa_supplicant.c
-@@ -3094,6 +3094,10 @@ void ibss_mesh_setup_freq(struct wpa_sup
+@@ -3040,6 +3040,10 @@ void ibss_mesh_setup_freq(struct wpa_sup
  
  	freq->freq = ssid->frequency;
  
diff --git a/package/network/services/hostapd/patches/500-lto-jobserver-support.patch b/package/network/services/hostapd/patches/500-lto-jobserver-support.patch
index 046da42ab8..67312c5004 100644
--- a/package/network/services/hostapd/patches/500-lto-jobserver-support.patch
+++ b/package/network/services/hostapd/patches/500-lto-jobserver-support.patch
@@ -20,7 +20,7 @@
  NOBJS = nt_password_hash.o ../src/crypto/ms_funcs.o $(SHA1OBJS)
 --- a/wpa_supplicant/Makefile
 +++ b/wpa_supplicant/Makefile
-@@ -2039,31 +2039,31 @@ wpa_supplicant_multi.a: .config $(BCHECK
+@@ -2037,31 +2037,31 @@ wpa_supplicant_multi.a: .config $(BCHECK
  	@$(AR) cr $@ wpa_supplicant_multi.o $(OBJS)
  
  wpa_supplicant: $(BCHECK) $(OBJS) $(EXTRA_progs)
diff --git a/package/network/services/hostapd/patches/600-ubus_support.patch b/package/network/services/hostapd/patches/600-ubus_support.patch
index f3936342a5..bc80ef0e81 100644
--- a/package/network/services/hostapd/patches/600-ubus_support.patch
+++ b/package/network/services/hostapd/patches/600-ubus_support.patch
@@ -1,11 +1,12 @@
 --- a/hostapd/Makefile
 +++ b/hostapd/Makefile
-@@ -166,6 +166,11 @@ OBJS += ../src/common/hw_features_common
+@@ -166,6 +166,12 @@ OBJS += ../src/common/hw_features_common
  
  OBJS += ../src/eapol_auth/eapol_auth_sm.o
  
 +ifdef CONFIG_UBUS
 +CFLAGS += -DUBUS_SUPPORT
++OBJS += ../src/utils/uloop.o
 +OBJS += ../src/ap/ubus.o
 +LIBS += -lubox -lubus
 +endif
@@ -22,15 +23,6 @@
  
  #define OCE_STA_CFON_ENABLED(hapd) \
  	((hapd->conf->oce & OCE_STA_CFON) && \
-@@ -92,7 +93,7 @@ struct hapd_interfaces {
- #ifdef CONFIG_CTRL_IFACE_UDP
-        unsigned char ctrl_iface_cookie[CTRL_IFACE_COOKIE_LEN];
- #endif /* CONFIG_CTRL_IFACE_UDP */
--
-+	struct ubus_object ubus;
- };
- 
- enum hostapd_chan_status {
 @@ -184,6 +185,7 @@ struct hostapd_data {
  	struct hostapd_iface *iface;
  	struct hostapd_config *iconf;
@@ -49,7 +41,7 @@
  struct hostapd_iface * hostapd_alloc_iface(void);
 --- a/src/ap/hostapd.c
 +++ b/src/ap/hostapd.c
-@@ -455,6 +455,7 @@ void hostapd_free_hapd_data(struct hosta
+@@ -435,6 +435,7 @@ void hostapd_free_hapd_data(struct hosta
  	hapd->beacon_set_done = 0;
  
  	wpa_printf(MSG_DEBUG, "%s(%s)", __func__, hapd->conf->iface);
@@ -57,7 +49,7 @@
  	accounting_deinit(hapd);
  	hostapd_deinit_wpa(hapd);
  	vlan_deinit(hapd);
-@@ -1207,6 +1208,8 @@ static int hostapd_start_beacon(struct h
+@@ -1187,6 +1188,8 @@ static int hostapd_start_beacon(struct h
  	if (hapd->driver && hapd->driver->set_operstate)
  		hapd->driver->set_operstate(hapd->drv_priv, 1);
  
@@ -66,7 +58,7 @@
  	return 0;
  }
  
-@@ -2295,6 +2298,7 @@ static int hostapd_setup_interface_compl
+@@ -2275,6 +2278,7 @@ static int hostapd_setup_interface_compl
  	if (err)
  		goto fail;
  
@@ -74,7 +66,7 @@
  	wpa_printf(MSG_DEBUG, "Completing interface initialization");
  	if (iface->freq) {
  #ifdef NEED_AP_MLME
-@@ -2514,6 +2518,7 @@ dfs_offload:
+@@ -2494,6 +2498,7 @@ dfs_offload:
  
  fail:
  	wpa_printf(MSG_ERROR, "Interface initialization failed");
@@ -82,7 +74,7 @@
  
  	if (iface->is_no_ir) {
  		hostapd_set_state(iface, HAPD_IFACE_NO_IR);
-@@ -3004,6 +3009,7 @@ void hostapd_interface_deinit_free(struc
+@@ -2984,6 +2989,7 @@ void hostapd_interface_deinit_free(struc
  		   (unsigned int) iface->conf->num_bss);
  	driver = iface->bss[0]->driver;
  	drv_priv = iface->bss[0]->drv_priv;
@@ -92,7 +84,7 @@
  		   __func__, driver, drv_priv);
 --- a/src/ap/ieee802_11.c
 +++ b/src/ap/ieee802_11.c
-@@ -2778,7 +2778,7 @@ static void handle_auth(struct hostapd_d
+@@ -2786,7 +2786,7 @@ static void handle_auth(struct hostapd_d
  	u16 auth_alg, auth_transaction, status_code;
  	u16 resp = WLAN_STATUS_SUCCESS;
  	struct sta_info *sta = NULL;
@@ -101,7 +93,7 @@
  	u16 fc;
  	const u8 *challenge = NULL;
  	u8 resp_ies[2 + WLAN_AUTH_CHALLENGE_LEN];
-@@ -2787,6 +2787,11 @@ static void handle_auth(struct hostapd_d
+@@ -2795,6 +2795,11 @@ static void handle_auth(struct hostapd_d
  	struct radius_sta rad_info;
  	const u8 *dst, *sa, *bssid;
  	bool mld_sta = false;
@@ -113,7 +105,7 @@
  
  	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
  		wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)",
-@@ -2978,6 +2983,13 @@ static void handle_auth(struct hostapd_d
+@@ -2986,6 +2991,13 @@ static void handle_auth(struct hostapd_d
  		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
  		goto fail;
  	}
@@ -127,7 +119,7 @@
  	if (res == HOSTAPD_ACL_PENDING)
  		return;
  
-@@ -5141,7 +5153,7 @@ static void handle_assoc(struct hostapd_
+@@ -5161,7 +5173,7 @@ static void handle_assoc(struct hostapd_
  	int resp = WLAN_STATUS_SUCCESS;
  	u16 reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE;
  	const u8 *pos;
@@ -136,7 +128,7 @@
  	struct sta_info *sta;
  	u8 *tmp = NULL;
  #ifdef CONFIG_FILS
-@@ -5354,6 +5366,11 @@ static void handle_assoc(struct hostapd_
+@@ -5374,6 +5386,11 @@ static void handle_assoc(struct hostapd_
  		left = res;
  	}
  #endif /* CONFIG_FILS */
@@ -148,7 +140,7 @@
  
  	/* followed by SSID and Supported rates; and HT capabilities if 802.11n
  	 * is used */
-@@ -5452,6 +5469,13 @@ static void handle_assoc(struct hostapd_
+@@ -5472,6 +5489,13 @@ static void handle_assoc(struct hostapd_
  	}
  #endif /* CONFIG_FILS */
  
@@ -162,7 +154,7 @@
   fail:
  
  	/*
-@@ -5733,6 +5757,7 @@ static void handle_disassoc(struct hosta
+@@ -5753,6 +5777,7 @@ static void handle_disassoc(struct hosta
  			   (unsigned long) len);
  		return;
  	}
@@ -170,7 +162,7 @@
  
  	sta = ap_get_sta(hapd, mgmt->sa);
  	if (!sta) {
-@@ -5764,6 +5789,8 @@ static void handle_deauth(struct hostapd
+@@ -5784,6 +5809,8 @@ static void handle_deauth(struct hostapd
  	/* Clear the PTKSA cache entries for PASN */
  	ptksa_cache_flush(hapd->ptksa, mgmt->sa, WPA_CIPHER_NONE);
  
@@ -209,7 +201,7 @@
  
 --- a/src/ap/drv_callbacks.c
 +++ b/src/ap/drv_callbacks.c
-@@ -145,6 +145,10 @@ int hostapd_notif_assoc(struct hostapd_d
+@@ -260,6 +260,10 @@ int hostapd_notif_assoc(struct hostapd_d
  	u16 reason = WLAN_REASON_UNSPECIFIED;
  	int status = WLAN_STATUS_SUCCESS;
  	const u8 *p2p_dev_addr = NULL;
@@ -220,7 +212,7 @@
  
  	if (addr == NULL) {
  		/*
-@@ -237,6 +241,12 @@ int hostapd_notif_assoc(struct hostapd_d
+@@ -396,6 +400,12 @@ int hostapd_notif_assoc(struct hostapd_d
  		goto fail;
  	}
  
@@ -330,20 +322,21 @@
  
 --- a/wpa_supplicant/Makefile
 +++ b/wpa_supplicant/Makefile
-@@ -194,6 +194,12 @@ ifdef CONFIG_EAPOL_TEST
+@@ -192,6 +192,13 @@ ifdef CONFIG_EAPOL_TEST
  CFLAGS += -Werror -DEAPOL_TEST
  endif
  
 +ifdef CONFIG_UBUS
 +CFLAGS += -DUBUS_SUPPORT
 +OBJS += ubus.o
++OBJS += ../src/utils/uloop.o
 +LIBS += -lubox -lubus
 +endif
 +
  ifdef CONFIG_CODE_COVERAGE
  CFLAGS += -O0 -fprofile-arcs -ftest-coverage
  LIBS += -lgcov
-@@ -989,6 +995,9 @@ ifdef CONFIG_CTRL_IFACE_MIB
+@@ -987,6 +994,9 @@ ifdef CONFIG_CTRL_IFACE_MIB
  CFLAGS += -DCONFIG_CTRL_IFACE_MIB
  endif
  OBJS += ../src/ap/ctrl_iface_ap.o
@@ -355,7 +348,7 @@
  CFLAGS += -DEAP_SERVER -DEAP_SERVER_IDENTITY
 --- a/wpa_supplicant/wpa_supplicant.c
 +++ b/wpa_supplicant/wpa_supplicant.c
-@@ -7635,6 +7635,8 @@ struct wpa_supplicant * wpa_supplicant_a
+@@ -7593,6 +7593,8 @@ struct wpa_supplicant * wpa_supplicant_a
  	}
  #endif /* CONFIG_P2P */
  
@@ -364,7 +357,7 @@
  	return wpa_s;
  }
  
-@@ -7661,6 +7663,8 @@ int wpa_supplicant_remove_iface(struct w
+@@ -7619,6 +7621,8 @@ int wpa_supplicant_remove_iface(struct w
  	struct wpa_supplicant *parent = wpa_s->parent;
  #endif /* CONFIG_MESH */
  
@@ -373,7 +366,7 @@
  	/* Remove interface from the global list of interfaces */
  	prev = global->ifaces;
  	if (prev == wpa_s) {
-@@ -8007,8 +8011,12 @@ int wpa_supplicant_run(struct wpa_global
+@@ -7965,8 +7969,12 @@ int wpa_supplicant_run(struct wpa_global
  	eloop_register_signal_terminate(wpa_supplicant_terminate, global);
  	eloop_register_signal_reconfig(wpa_supplicant_reconfig, global);
  
@@ -396,7 +389,7 @@
  
  extern const char *const wpa_supplicant_version;
  extern const char *const wpa_supplicant_license;
-@@ -324,6 +325,8 @@ struct wpa_global {
+@@ -319,6 +320,8 @@ struct wpa_global {
  #endif /* CONFIG_WIFI_DISPLAY */
  
  	struct psk_list_entry *add_psk; /* From group formation */
@@ -405,7 +398,7 @@
  };
  
  
-@@ -655,6 +658,7 @@ struct wpa_supplicant {
+@@ -685,6 +688,7 @@ struct wpa_supplicant {
  	unsigned char own_addr[ETH_ALEN];
  	unsigned char perm_addr[ETH_ALEN];
  	char ifname[100];
@@ -432,36 +425,18 @@
  	if (wpa_s->conf->wps_cred_processing == 1)
  		return 0;
  
---- a/hostapd/main.c
-+++ b/hostapd/main.c
-@@ -991,6 +991,7 @@ int main(int argc, char *argv[])
- 	}
- 
- 	hostapd_global_ctrl_iface_init(&interfaces);
-+	hostapd_ubus_add(&interfaces);
- 
- 	if (hostapd_global_run(&interfaces, daemonize, pid_file)) {
- 		wpa_printf(MSG_ERROR, "Failed to start eloop");
-@@ -1000,6 +1001,7 @@ int main(int argc, char *argv[])
- 	ret = 0;
- 
-  out:
-+	hostapd_ubus_free(&interfaces);
- 	hostapd_global_ctrl_iface_deinit(&interfaces);
- 	/* Deinitialize all interfaces */
- 	for (i = 0; i < interfaces.count; i++) {
 --- a/wpa_supplicant/main.c
 +++ b/wpa_supplicant/main.c
-@@ -204,7 +204,7 @@ int main(int argc, char *argv[])
+@@ -203,7 +203,7 @@ int main(int argc, char *argv[])
  
  	for (;;) {
  		c = getopt(argc, argv,
--			   "b:Bc:C:D:de:f:g:G:hH:i:I:KLMm:No:O:p:P:qsTtuv::W");
-+			   "b:Bc:C:D:de:f:g:G:hH:i:I:KLMm:nNo:O:p:P:qsTtuv::W");
+-			   "b:Bc:C:D:de:f:g:G:hi:I:KLMm:No:O:p:P:qsTtuv::W");
++			   "b:Bc:C:D:de:f:g:G:hi:I:KLMm:nNo:O:p:P:qsTtuv::W");
  		if (c < 0)
  			break;
  		switch (c) {
-@@ -272,6 +272,9 @@ int main(int argc, char *argv[])
+@@ -268,6 +268,9 @@ int main(int argc, char *argv[])
  			params.conf_p2p_dev = optarg;
  			break;
  #endif /* CONFIG_P2P */
@@ -533,7 +508,7 @@
  
 --- a/src/ap/dfs.c
 +++ b/src/ap/dfs.c
-@@ -1211,6 +1211,8 @@ int hostapd_dfs_pre_cac_expired(struct h
+@@ -1216,6 +1216,8 @@ int hostapd_dfs_pre_cac_expired(struct h
  		"freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d",
  		freq, ht_enabled, chan_offset, chan_width, cf1, cf2);
  
@@ -623,3 +598,151 @@
  	wpa_hexdump(MSG_DEBUG, "WNM: BSS Transition Candidate List Entries",
  		    pos, end - pos);
  }
+--- a/src/utils/eloop.c
++++ b/src/utils/eloop.c
+@@ -77,6 +77,9 @@ struct eloop_sock_table {
+ struct eloop_data {
+ 	int max_sock;
+ 
++	eloop_timeout_poll_handler timeout_poll_cb;
++	eloop_poll_handler poll_cb;
++
+ 	size_t count; /* sum of all table counts */
+ #ifdef CONFIG_ELOOP_POLL
+ 	size_t max_pollfd_map; /* number of pollfds_map currently allocated */
+@@ -1121,6 +1124,12 @@ void eloop_run(void)
+ 				os_reltime_sub(&timeout->time, &now, &tv);
+ 			else
+ 				tv.sec = tv.usec = 0;
++		}
++
++		if (eloop.timeout_poll_cb && eloop.timeout_poll_cb(&tv, !!timeout))
++			timeout = (void *)1;
++
++		if (timeout) {
+ #if defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL)
+ 			timeout_ms = tv.sec * 1000 + tv.usec / 1000;
+ #endif /* defined(CONFIG_ELOOP_POLL) || defined(CONFIG_ELOOP_EPOLL) */
+@@ -1190,7 +1199,8 @@ void eloop_run(void)
+ 		eloop.exceptions.changed = 0;
+ 
+ 		eloop_process_pending_signals();
+-
++		if (eloop.poll_cb)
++			eloop.poll_cb();
+ 
+ 		/* check if some registered timeouts have occurred */
+ 		timeout = dl_list_first(&eloop.timeout, struct eloop_timeout,
+@@ -1252,6 +1262,14 @@ out:
+ 	return;
+ }
+ 
++int eloop_register_cb(eloop_poll_handler poll_cb,
++		      eloop_timeout_poll_handler timeout_cb)
++{
++	eloop.poll_cb = poll_cb;
++	eloop.timeout_poll_cb = timeout_cb;
++
++	return 0;
++}
+ 
+ void eloop_terminate(void)
+ {
+--- a/src/utils/eloop.h
++++ b/src/utils/eloop.h
+@@ -65,6 +65,9 @@ typedef void (*eloop_timeout_handler)(vo
+  */
+ typedef void (*eloop_signal_handler)(int sig, void *signal_ctx);
+ 
++typedef bool (*eloop_timeout_poll_handler)(struct os_reltime *tv, bool tv_set);
++typedef void (*eloop_poll_handler)(void);
++
+ /**
+  * eloop_init() - Initialize global event loop data
+  * Returns: 0 on success, -1 on failure
+@@ -73,6 +76,9 @@ typedef void (*eloop_signal_handler)(int
+  */
+ int eloop_init(void);
+ 
++int eloop_register_cb(eloop_poll_handler poll_cb,
++		      eloop_timeout_poll_handler timeout_cb);
++
+ /**
+  * eloop_register_read_sock - Register handler for read events
+  * @sock: File descriptor number for the socket
+@@ -320,6 +326,8 @@ int eloop_register_signal_reconfig(eloop
+  */
+ int eloop_sock_requeue(void);
+ 
++void eloop_add_uloop(void);
++
+ /**
+  * eloop_run - Start the event loop
+  *
+--- /dev/null
++++ b/src/utils/uloop.c
+@@ -0,0 +1,64 @@
++#include <libubox/uloop.h>
++#include "includes.h"
++#include "common.h"
++#include "eloop.h"
++
++static void eloop_uloop_event_cb(int sock, void *eloop_ctx, void *sock_ctx)
++{
++}
++
++static void eloop_uloop_fd_cb(struct uloop_fd *fd, unsigned int events)
++{
++	unsigned int changed = events ^ fd->flags;
++
++	if (changed & ULOOP_READ) {
++		if (events & ULOOP_READ)
++			eloop_register_sock(fd->fd, EVENT_TYPE_READ, eloop_uloop_event_cb, fd, fd);
++		else
++			eloop_unregister_sock(fd->fd, EVENT_TYPE_READ);
++	}
++
++	if (changed & ULOOP_WRITE) {
++		if (events & ULOOP_WRITE)
++			eloop_register_sock(fd->fd, EVENT_TYPE_WRITE, eloop_uloop_event_cb, fd, fd);
++		else
++			eloop_unregister_sock(fd->fd, EVENT_TYPE_WRITE);
++	}
++}
++
++static bool uloop_timeout_poll_handler(struct os_reltime *tv, bool tv_set)
++{
++	struct os_reltime tv_uloop;
++	int timeout_ms = uloop_get_next_timeout();
++
++	if (timeout_ms < 0)
++		return false;
++
++	tv_uloop.sec = timeout_ms / 1000;
++	tv_uloop.usec = (timeout_ms % 1000) * 1000;
++
++	if (!tv_set || os_reltime_before(&tv_uloop, tv)) {
++		*tv = tv_uloop;
++		return true;
++	}
++
++	return false;
++}
++
++static void uloop_poll_handler(void)
++{
++	uloop_run_timeout(0);
++}
++
++void eloop_add_uloop(void)
++{
++	static bool init_done = false;
++
++	if (!init_done) {
++		uloop_init();
++		uloop_fd_set_cb = eloop_uloop_fd_cb;
++		init_done = true;
++	}
++
++	eloop_register_cb(uloop_poll_handler, uloop_timeout_poll_handler);
++}
diff --git a/package/network/services/hostapd/patches/601-ucode_support.patch b/package/network/services/hostapd/patches/601-ucode_support.patch
new file mode 100644
index 0000000000..c8bbfd43d8
--- /dev/null
+++ b/package/network/services/hostapd/patches/601-ucode_support.patch
@@ -0,0 +1,543 @@
+--- a/hostapd/Makefile
++++ b/hostapd/Makefile
+@@ -168,9 +168,21 @@ OBJS += ../src/eapol_auth/eapol_auth_sm.
+ 
+ ifdef CONFIG_UBUS
+ CFLAGS += -DUBUS_SUPPORT
+-OBJS += ../src/utils/uloop.o
+ OBJS += ../src/ap/ubus.o
+-LIBS += -lubox -lubus
++LIBS += -lubus
++NEED_ULOOP:=y
++endif
++
++ifdef CONFIG_UCODE
++CFLAGS += -DUCODE_SUPPORT
++OBJS += ../src/utils/ucode.o
++OBJS += ../src/ap/ucode.o
++NEED_ULOOP:=y
++endif
++
++ifdef NEED_ULOOP
++OBJS += ../src/utils/uloop.o
++LIBS += -lubox
+ endif
+ 
+ ifdef CONFIG_CODE_COVERAGE
+--- a/hostapd/main.c
++++ b/hostapd/main.c
+@@ -1007,6 +1007,7 @@ int main(int argc, char *argv[])
+ 	}
+ 
+ 	hostapd_global_ctrl_iface_init(&interfaces);
++	hostapd_ucode_init(&interfaces);
+ 
+ 	if (hostapd_global_run(&interfaces, daemonize, pid_file)) {
+ 		wpa_printf(MSG_ERROR, "Failed to start eloop");
+@@ -1016,6 +1017,7 @@ int main(int argc, char *argv[])
+ 	ret = 0;
+ 
+  out:
++	hostapd_ucode_free();
+ 	hostapd_global_ctrl_iface_deinit(&interfaces);
+ 	/* Deinitialize all interfaces */
+ 	for (i = 0; i < interfaces.count; i++) {
+--- a/src/ap/hostapd.h
++++ b/src/ap/hostapd.h
+@@ -19,6 +19,7 @@
+ #include "ap_config.h"
+ #include "drivers/driver.h"
+ #include "ubus.h"
++#include "ucode.h"
+ 
+ #define OCE_STA_CFON_ENABLED(hapd) \
+ 	((hapd->conf->oce & OCE_STA_CFON) && \
+@@ -51,6 +52,10 @@ struct hapd_interfaces {
+ 	struct hostapd_config * (*config_read_cb)(const char *config_fname);
+ 	int (*ctrl_iface_init)(struct hostapd_data *hapd);
+ 	void (*ctrl_iface_deinit)(struct hostapd_data *hapd);
++	int (*ctrl_iface_recv)(struct hostapd_data *hapd,
++			       char *buf, char *reply, int reply_size,
++			       struct sockaddr_storage *from,
++			       socklen_t fromlen);
+ 	int (*for_each_interface)(struct hapd_interfaces *interfaces,
+ 				  int (*cb)(struct hostapd_iface *iface,
+ 					    void *ctx), void *ctx);
+@@ -186,6 +191,7 @@ struct hostapd_data {
+ 	struct hostapd_config *iconf;
+ 	struct hostapd_bss_config *conf;
+ 	struct hostapd_ubus_bss ubus;
++	struct hostapd_ucode_bss ucode;
+ 	int interface_added; /* virtual interface added for this BSS */
+ 	unsigned int started:1;
+ 	unsigned int disabled:1;
+@@ -506,6 +512,7 @@ struct hostapd_sta_info {
+  */
+ struct hostapd_iface {
+ 	struct hapd_interfaces *interfaces;
++	struct hostapd_ucode_iface ucode;
+ 	void *owner;
+ 	char *config_fname;
+ 	struct hostapd_config *conf;
+@@ -706,6 +713,8 @@ struct hostapd_iface * hostapd_init(stru
+ struct hostapd_iface *
+ hostapd_interface_init_bss(struct hapd_interfaces *interfaces, const char *phy,
+ 			   const char *config_fname, int debug);
++int hostapd_setup_bss(struct hostapd_data *hapd, int first, bool start_beacon);
++void hostapd_bss_deinit(struct hostapd_data *hapd);
+ void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
+ 			   int reassoc);
+ void hostapd_interface_deinit_free(struct hostapd_iface *iface);
+--- a/src/ap/hostapd.c
++++ b/src/ap/hostapd.c
+@@ -252,6 +252,8 @@ int hostapd_reload_config(struct hostapd
+ 	struct hostapd_config *newconf, *oldconf;
+ 	size_t j;
+ 
++	hostapd_ucode_reload_bss(hapd);
++
+ 	if (iface->config_fname == NULL) {
+ 		/* Only in-memory config in use - assume it has been updated */
+ 		hostapd_clear_old(iface);
+@@ -435,6 +437,7 @@ void hostapd_free_hapd_data(struct hosta
+ 	hapd->beacon_set_done = 0;
+ 
+ 	wpa_printf(MSG_DEBUG, "%s(%s)", __func__, hapd->conf->iface);
++	hostapd_ucode_free_bss(hapd);
+ 	hostapd_ubus_free_bss(hapd);
+ 	accounting_deinit(hapd);
+ 	hostapd_deinit_wpa(hapd);
+@@ -599,6 +602,7 @@ void hostapd_cleanup_iface_partial(struc
+ static void hostapd_cleanup_iface(struct hostapd_iface *iface)
+ {
+ 	wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface);
++	hostapd_ucode_free_iface(iface);
+ 	eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
+ 	eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,
+ 			     NULL);
+@@ -1189,6 +1193,7 @@ static int hostapd_start_beacon(struct h
+ 		hapd->driver->set_operstate(hapd->drv_priv, 1);
+ 
+ 	hostapd_ubus_add_bss(hapd);
++	hostapd_ucode_add_bss(hapd);
+ 
+ 	return 0;
+ }
+@@ -1211,8 +1216,7 @@ static int hostapd_start_beacon(struct h
+  * initialized. Most of the modules that are initialized here will be
+  * deinitialized in hostapd_cleanup().
+  */
+-static int hostapd_setup_bss(struct hostapd_data *hapd, int first,
+-			     bool start_beacon)
++int hostapd_setup_bss(struct hostapd_data *hapd, int first, bool start_beacon)
+ {
+ 	struct hostapd_bss_config *conf = hapd->conf;
+ 	u8 ssid[SSID_MAX_LEN + 1];
+@@ -2698,7 +2702,7 @@ hostapd_alloc_bss_data(struct hostapd_if
+ }
+ 
+ 
+-static void hostapd_bss_deinit(struct hostapd_data *hapd)
++void hostapd_bss_deinit(struct hostapd_data *hapd)
+ {
+ 	if (!hapd)
+ 		return;
+--- a/wpa_supplicant/Makefile
++++ b/wpa_supplicant/Makefile
+@@ -195,8 +195,20 @@ endif
+ ifdef CONFIG_UBUS
+ CFLAGS += -DUBUS_SUPPORT
+ OBJS += ubus.o
++LIBS += -lubus
++NEED_ULOOP:=y
++endif
++
++ifdef CONFIG_UCODE
++CFLAGS += -DUCODE_SUPPORT
++OBJS += ../src/utils/ucode.o
++OBJS += ucode.o
++NEED_ULOOP:=y
++endif
++
++ifdef NEED_ULOOP
+ OBJS += ../src/utils/uloop.o
+-LIBS += -lubox -lubus
++LIBS += -lubox
+ endif
+ 
+ ifdef CONFIG_CODE_COVERAGE
+@@ -997,6 +1009,9 @@ OBJS += ../src/ap/ctrl_iface_ap.o
+ ifdef CONFIG_UBUS
+ OBJS += ../src/ap/ubus.o
+ endif
++ifdef CONFIG_UCODE
++OBJS += ../src/ap/ucode.o
++endif
+ endif
+ 
+ CFLAGS += -DEAP_SERVER -DEAP_SERVER_IDENTITY
+--- a/wpa_supplicant/wpa_supplicant.c
++++ b/wpa_supplicant/wpa_supplicant.c
+@@ -1044,6 +1044,7 @@ void wpa_supplicant_set_state(struct wpa
+ 		sme_sched_obss_scan(wpa_s, 0);
+ 	}
+ 	wpa_s->wpa_state = state;
++	wpas_ucode_update_state(wpa_s);
+ 
+ #ifdef CONFIG_BGSCAN
+ 	if (state == WPA_COMPLETED && wpa_s->current_ssid != wpa_s->bgscan_ssid)
+@@ -7594,6 +7595,7 @@ struct wpa_supplicant * wpa_supplicant_a
+ #endif /* CONFIG_P2P */
+ 
+ 	wpas_ubus_add_bss(wpa_s);
++	wpas_ucode_add_bss(wpa_s);
+ 
+ 	return wpa_s;
+ }
+@@ -7621,6 +7623,7 @@ int wpa_supplicant_remove_iface(struct w
+ 	struct wpa_supplicant *parent = wpa_s->parent;
+ #endif /* CONFIG_MESH */
+ 
++	wpas_ucode_free_bss(wpa_s);
+ 	wpas_ubus_free_bss(wpa_s);
+ 
+ 	/* Remove interface from the global list of interfaces */
+@@ -7931,6 +7934,7 @@ struct wpa_global * wpa_supplicant_init(
+ 
+ 	eloop_register_timeout(WPA_SUPPLICANT_CLEANUP_INTERVAL, 0,
+ 			       wpas_periodic, global, NULL);
++	wpas_ucode_init(global);
+ 
+ 	return global;
+ }
+@@ -7969,12 +7973,8 @@ int wpa_supplicant_run(struct wpa_global
+ 	eloop_register_signal_terminate(wpa_supplicant_terminate, global);
+ 	eloop_register_signal_reconfig(wpa_supplicant_reconfig, global);
+ 
+-	wpas_ubus_add(global);
+-
+ 	eloop_run();
+ 
+-	wpas_ubus_free(global);
+-
+ 	return 0;
+ }
+ 
+@@ -8007,6 +8007,8 @@ void wpa_supplicant_deinit(struct wpa_gl
+ 
+ 	wpas_notify_supplicant_deinitialized(global);
+ 
++	wpas_ucode_free();
++
+ 	eap_peer_unregister_methods();
+ #ifdef CONFIG_AP
+ 	eap_server_unregister_methods();
+--- a/wpa_supplicant/wpa_supplicant_i.h
++++ b/wpa_supplicant/wpa_supplicant_i.h
+@@ -22,6 +22,7 @@
+ #include "wmm_ac.h"
+ #include "pasn/pasn_common.h"
+ #include "ubus.h"
++#include "ucode.h"
+ 
+ extern const char *const wpa_supplicant_version;
+ extern const char *const wpa_supplicant_license;
+@@ -689,6 +690,7 @@ struct wpa_supplicant {
+ 	unsigned char perm_addr[ETH_ALEN];
+ 	char ifname[100];
+ 	struct wpas_ubus_bss ubus;
++	struct wpas_ucode_bss ucode;
+ #ifdef CONFIG_MATCH_IFACE
+ 	int matched;
+ #endif /* CONFIG_MATCH_IFACE */
+--- a/hostapd/ctrl_iface.c
++++ b/hostapd/ctrl_iface.c
+@@ -4856,6 +4856,7 @@ try_again:
+ 		return -1;
+ 	}
+ 
++	interface->ctrl_iface_recv = hostapd_ctrl_iface_receive_process;
+ 	wpa_msg_register_cb(hostapd_ctrl_iface_msg_cb);
+ 
+ 	return 0;
+@@ -4957,6 +4958,7 @@ fail:
+ 	os_free(fname);
+ 
+ 	interface->global_ctrl_sock = s;
++	interface->ctrl_iface_recv = hostapd_ctrl_iface_receive_process;
+ 	eloop_register_read_sock(s, hostapd_global_ctrl_iface_receive,
+ 				 interface, NULL);
+ 
+--- a/src/drivers/driver.h
++++ b/src/drivers/driver.h
+@@ -3787,6 +3787,25 @@ struct wpa_driver_ops {
+ 			 const char *ifname);
+ 
+ 	/**
++	 * if_rename - Rename a virtual interface
++	 * @priv: Private driver interface data
++	 * @type: Interface type
++	 * @ifname: Interface name of the virtual interface to be renamed
++	 *	    (NULL when renaming the AP BSS interface)
++	 * @new_name: New interface name of the virtual interface
++	 * Returns: 0 on success, -1 on failure
++	 */
++	int (*if_rename)(void *priv, enum wpa_driver_if_type type,
++			 const char *ifname, const char *new_name);
++
++	/**
++	 * set_first_bss - Make a virtual interface the first (primary) bss
++	 * @priv: Private driver interface data
++	 * Returns: 0 on success, -1 on failure
++	 */
++	int (*set_first_bss)(void *priv);
++
++	/**
+ 	 * set_sta_vlan - Bind a station into a specific interface (AP only)
+ 	 * @priv: Private driver interface data
+ 	 * @ifname: Interface (main or virtual BSS or VLAN)
+@@ -6440,6 +6459,7 @@ union wpa_event_data {
+ 
+ 	/**
+ 	 * struct ch_switch
++	 * @count: Count until channel switch activates
+ 	 * @freq: Frequency of new channel in MHz
+ 	 * @ht_enabled: Whether this is an HT channel
+ 	 * @ch_offset: Secondary channel offset
+@@ -6450,6 +6470,7 @@ union wpa_event_data {
+ 	 * @punct_bitmap: Puncturing bitmap
+ 	 */
+ 	struct ch_switch {
++		int count;
+ 		int freq;
+ 		int ht_enabled;
+ 		int ch_offset;
+--- a/src/drivers/driver_nl80211_event.c
++++ b/src/drivers/driver_nl80211_event.c
+@@ -1202,6 +1202,7 @@ static void mlme_event_ch_switch(struct
+ 				 struct nlattr *bw, struct nlattr *cf1,
+ 				 struct nlattr *cf2,
+ 				 struct nlattr *punct_bitmap,
++				 struct nlattr *count,
+ 				 int finished)
+ {
+ 	struct i802_bss *bss;
+@@ -1265,6 +1266,8 @@ static void mlme_event_ch_switch(struct
+ 		data.ch_switch.cf1 = nla_get_u32(cf1);
+ 	if (cf2)
+ 		data.ch_switch.cf2 = nla_get_u32(cf2);
++	if (count)
++		data.ch_switch.count = nla_get_u32(count);
+ 
+ 	if (finished)
+ 		bss->flink->freq = data.ch_switch.freq;
+@@ -3912,6 +3915,7 @@ static void do_process_drv_event(struct
+ 				     tb[NL80211_ATTR_CENTER_FREQ1],
+ 				     tb[NL80211_ATTR_CENTER_FREQ2],
+ 				     tb[NL80211_ATTR_PUNCT_BITMAP],
++				     tb[NL80211_ATTR_CH_SWITCH_COUNT],
+ 				     0);
+ 		break;
+ 	case NL80211_CMD_CH_SWITCH_NOTIFY:
+@@ -3924,6 +3928,7 @@ static void do_process_drv_event(struct
+ 				     tb[NL80211_ATTR_CENTER_FREQ1],
+ 				     tb[NL80211_ATTR_CENTER_FREQ2],
+ 				     tb[NL80211_ATTR_PUNCT_BITMAP],
++				     NULL,
+ 				     1);
+ 		break;
+ 	case NL80211_CMD_DISCONNECT:
+--- a/wpa_supplicant/events.c
++++ b/wpa_supplicant/events.c
+@@ -5389,6 +5389,7 @@ void supplicant_event(void *ctx, enum wp
+ 		event_to_string(event), event);
+ #endif /* CONFIG_NO_STDOUT_DEBUG */
+ 
++	wpas_ucode_event(wpa_s, event, data);
+ 	switch (event) {
+ 	case EVENT_AUTH:
+ #ifdef CONFIG_FST
+--- a/src/ap/ap_drv_ops.h
++++ b/src/ap/ap_drv_ops.h
+@@ -393,6 +393,23 @@ static inline int hostapd_drv_stop_ap(st
+ 	return hapd->driver->stop_ap(hapd->drv_priv);
+ }
+ 
++static inline int hostapd_drv_if_rename(struct hostapd_data *hapd,
++					enum wpa_driver_if_type type,
++					const char *ifname,
++					const char *new_name)
++{
++	if (!hapd->driver || !hapd->driver->if_rename || !hapd->drv_priv)
++		return -1;
++	return hapd->driver->if_rename(hapd->drv_priv, type, ifname, new_name);
++}
++
++static inline int hostapd_drv_set_first_bss(struct hostapd_data *hapd)
++{
++	if (!hapd->driver || !hapd->driver->set_first_bss || !hapd->drv_priv)
++		return 0;
++	return hapd->driver->set_first_bss(hapd->drv_priv);
++}
++
+ static inline int hostapd_drv_channel_info(struct hostapd_data *hapd,
+ 					   struct wpa_channel_info *ci)
+ {
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -1333,7 +1333,7 @@ static void wpa_driver_nl80211_event_rtm
+ 		}
+ 		wpa_printf(MSG_DEBUG, "nl80211: Interface down (%s/%s)",
+ 			   namebuf, ifname);
+-		if (os_strcmp(drv->first_bss->ifname, ifname) != 0) {
++		if (drv->first_bss->ifindex != ifi->ifi_index) {
+ 			wpa_printf(MSG_DEBUG,
+ 				   "nl80211: Not the main interface (%s) - do not indicate interface down",
+ 				   drv->first_bss->ifname);
+@@ -1369,7 +1369,7 @@ static void wpa_driver_nl80211_event_rtm
+ 		}
+ 		wpa_printf(MSG_DEBUG, "nl80211: Interface up (%s/%s)",
+ 			   namebuf, ifname);
+-		if (os_strcmp(drv->first_bss->ifname, ifname) != 0) {
++		if (drv->first_bss->ifindex != ifi->ifi_index) {
+ 			wpa_printf(MSG_DEBUG,
+ 				   "nl80211: Not the main interface (%s) - do not indicate interface up",
+ 				   drv->first_bss->ifname);
+@@ -8432,6 +8432,7 @@ static void *i802_init(struct hostapd_da
+ 	char master_ifname[IFNAMSIZ];
+ 	int ifindex, br_ifindex = 0;
+ 	int br_added = 0;
++	int err;
+ 
+ 	bss = wpa_driver_nl80211_drv_init(hapd, params->ifname,
+ 					  params->global_priv, 1,
+@@ -8491,21 +8492,17 @@ static void *i802_init(struct hostapd_da
+ 	    (params->num_bridge == 0 || !params->bridge[0]))
+ 		add_ifidx(drv, br_ifindex, drv->ifindex);
+ 
+-	if (bss->added_if_into_bridge || bss->already_in_bridge) {
+-		int err;
+-
+-		drv->rtnl_sk = nl_socket_alloc();
+-		if (drv->rtnl_sk == NULL) {
+-			wpa_printf(MSG_ERROR, "nl80211: Failed to allocate nl_sock");
+-			goto failed;
+-		}
++	drv->rtnl_sk = nl_socket_alloc();
++	if (drv->rtnl_sk == NULL) {
++		wpa_printf(MSG_ERROR, "nl80211: Failed to allocate nl_sock");
++		goto failed;
++	}
+ 
+-		err = nl_connect(drv->rtnl_sk, NETLINK_ROUTE);
+-		if (err) {
+-			wpa_printf(MSG_ERROR, "nl80211: Failed to connect nl_sock to NETLINK_ROUTE: %s",
+-				   nl_geterror(err));
+-			goto failed;
+-		}
++	err = nl_connect(drv->rtnl_sk, NETLINK_ROUTE);
++	if (err) {
++		wpa_printf(MSG_ERROR, "nl80211: Failed to connect nl_sock to NETLINK_ROUTE: %s",
++			   nl_geterror(err));
++		goto failed;
+ 	}
+ 
+ 	if (drv->capa.flags2 & WPA_DRIVER_FLAGS2_CONTROL_PORT_RX) {
+@@ -8875,6 +8872,50 @@ static int wpa_driver_nl80211_if_remove(
+ 	return 0;
+ }
+ 
++static int wpa_driver_nl80211_if_rename(struct i802_bss *bss,
++					enum wpa_driver_if_type type,
++					const char *ifname, const char *new_name)
++{
++	struct wpa_driver_nl80211_data *drv = bss->drv;
++	struct ifinfomsg ifi = {
++		.ifi_family = AF_UNSPEC,
++		.ifi_index = bss->ifindex,
++	};
++	struct nl_msg *msg;
++	int res = -ENOMEM;
++
++	if (ifname)
++		ifi.ifi_index = if_nametoindex(ifname);
++
++	msg = nlmsg_alloc_simple(RTM_SETLINK, 0);
++	if (!msg)
++		return res;
++
++	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
++		goto out;
++
++	if (nla_put_string(msg, IFLA_IFNAME, new_name))
++		goto out;
++
++	res = nl_send_auto_complete(drv->rtnl_sk, msg);
++	if (res < 0)
++		goto out;
++
++	res = nl_wait_for_ack(drv->rtnl_sk);
++	if (res) {
++		wpa_printf(MSG_INFO,
++			   "nl80211: Renaming device %s to %s failed: %s",
++			   ifname ? ifname : bss->ifname, new_name, nl_geterror(res));
++		goto out;
++	}
++
++	if (type == WPA_IF_AP_BSS && !ifname)
++		os_strlcpy(bss->ifname, new_name, sizeof(bss->ifname));
++
++out:
++	nlmsg_free(msg);
++	return res;
++}
+ 
+ static int cookie_handler(struct nl_msg *msg, void *arg)
+ {
+@@ -10513,6 +10554,37 @@ static int driver_nl80211_if_remove(void
+ }
+ 
+ 
++static int driver_nl80211_if_rename(void *priv, enum wpa_driver_if_type type,
++				    const char *ifname, const char *new_name)
++{
++	struct i802_bss *bss = priv;
++	return wpa_driver_nl80211_if_rename(bss, type, ifname, new_name);
++}
++
++
++static int driver_nl80211_set_first_bss(void *priv)
++{
++	struct i802_bss *bss = priv, *tbss;
++	struct wpa_driver_nl80211_data *drv = bss->drv;
++
++	if (drv->first_bss == bss)
++		return 0;
++
++	for (tbss = drv->first_bss; tbss; tbss = tbss->next) {
++		if (tbss->next != bss)
++			continue;
++
++		tbss->next = bss->next;
++		bss->next = drv->first_bss;
++		drv->first_bss = bss;
++		drv->ctx = bss->ctx;
++		return 0;
++	}
++
++	return -1;
++}
++
++
+ static int driver_nl80211_send_mlme(void *priv, const u8 *data,
+ 				    size_t data_len, int noack,
+ 				    unsigned int freq,
+@@ -13697,6 +13769,8 @@ const struct wpa_driver_ops wpa_driver_n
+ 	.set_acl = wpa_driver_nl80211_set_acl,
+ 	.if_add = wpa_driver_nl80211_if_add,
+ 	.if_remove = driver_nl80211_if_remove,
++	.if_rename = driver_nl80211_if_rename,
++	.set_first_bss = driver_nl80211_set_first_bss,
+ 	.send_mlme = driver_nl80211_send_mlme,
+ 	.get_hw_feature_data = nl80211_get_hw_feature_data,
+ 	.sta_add = wpa_driver_nl80211_sta_add,
diff --git a/package/network/services/hostapd/patches/700-wifi-reload.patch b/package/network/services/hostapd/patches/700-wifi-reload.patch
deleted file mode 100644
index 0c7627645f..0000000000
--- a/package/network/services/hostapd/patches/700-wifi-reload.patch
+++ /dev/null
@@ -1,194 +0,0 @@
---- a/hostapd/config_file.c
-+++ b/hostapd/config_file.c
-@@ -2420,6 +2420,8 @@ static int hostapd_config_fill(struct ho
- 		bss->isolate = atoi(pos);
- 	} else if (os_strcmp(buf, "ap_max_inactivity") == 0) {
- 		bss->ap_max_inactivity = atoi(pos);
-+	} else if (os_strcmp(buf, "config_id") == 0) {
-+		bss->config_id = os_strdup(pos);
- 	} else if (os_strcmp(buf, "skip_inactivity_poll") == 0) {
- 		bss->skip_inactivity_poll = atoi(pos);
- 	} else if (os_strcmp(buf, "config_id") == 0) {
-@@ -3130,6 +3132,8 @@ static int hostapd_config_fill(struct ho
- 		}
- 	} else if (os_strcmp(buf, "acs_exclude_dfs") == 0) {
- 		conf->acs_exclude_dfs = atoi(pos);
-+	} else if (os_strcmp(buf, "radio_config_id") == 0) {
-+			conf->config_id = os_strdup(pos);
- 	} else if (os_strcmp(buf, "op_class") == 0) {
- 		conf->op_class = atoi(pos);
- 	} else if (os_strcmp(buf, "channel") == 0) {
---- a/src/ap/ap_config.c
-+++ b/src/ap/ap_config.c
-@@ -998,6 +998,7 @@ void hostapd_config_free(struct hostapd_
- 
- 	for (i = 0; i < conf->num_bss; i++)
- 		hostapd_config_free_bss(conf->bss[i]);
-+	os_free(conf->config_id);
- 	os_free(conf->bss);
- 	os_free(conf->supported_rates);
- 	os_free(conf->basic_rates);
---- a/src/ap/ap_config.h
-+++ b/src/ap/ap_config.h
-@@ -998,6 +998,7 @@ struct eht_phy_capabilities_info {
- struct hostapd_config {
- 	struct hostapd_bss_config **bss, *last_bss;
- 	size_t num_bss;
-+	char *config_id;
- 
- 	u16 beacon_int;
- 	int rts_threshold;
---- a/src/ap/hostapd.c
-+++ b/src/ap/hostapd.c
-@@ -255,6 +255,10 @@ static int hostapd_iface_conf_changed(st
- {
- 	size_t i;
- 
-+	if (newconf->config_id != oldconf->config_id)
-+		if (strcmp(newconf->config_id, oldconf->config_id))
-+			return 1;
-+
- 	if (newconf->num_bss != oldconf->num_bss)
- 		return 1;
- 
-@@ -268,7 +272,7 @@ static int hostapd_iface_conf_changed(st
- }
- 
- 
--int hostapd_reload_config(struct hostapd_iface *iface)
-+int hostapd_reload_config(struct hostapd_iface *iface, int reconf)
- {
- 	struct hapd_interfaces *interfaces = iface->interfaces;
- 	struct hostapd_data *hapd = iface->bss[0];
-@@ -296,6 +300,9 @@ int hostapd_reload_config(struct hostapd
- 		char *fname;
- 		int res;
- 
-+		if (reconf)
-+			return -1;
-+
- 		hostapd_clear_old(iface);
- 
- 		wpa_printf(MSG_DEBUG,
-@@ -322,6 +329,24 @@ int hostapd_reload_config(struct hostapd
- 			wpa_printf(MSG_ERROR,
- 				   "Failed to enable interface on config reload");
- 		return res;
-+	} else {
-+		for (j = 0; j < iface->num_bss; j++) {
-+			hapd = iface->bss[j];
-+			if (!hapd->config_id || strcmp(hapd->config_id, newconf->bss[j]->config_id)) {
-+				hostapd_flush_old_stations(iface->bss[j],
-+							   WLAN_REASON_PREV_AUTH_NOT_VALID);
-+#ifdef CONFIG_WEP
-+				hostapd_broadcast_wep_clear(iface->bss[j]);
-+#endif
-+
-+#ifndef CONFIG_NO_RADIUS
-+				/* TODO: update dynamic data based on changed configuration
-+				 * items (e.g., open/close sockets, etc.) */
-+				radius_client_flush(iface->bss[j]->radius, 0);
-+#endif /* CONFIG_NO_RADIUS */
-+				wpa_printf(MSG_INFO, "bss %zu changed", j);
-+			}
-+		}
- 	}
- 	iface->conf = newconf;
- 
-@@ -338,6 +363,12 @@ int hostapd_reload_config(struct hostapd
- 
- 	for (j = 0; j < iface->num_bss; j++) {
- 		hapd = iface->bss[j];
-+		if (hapd->config_id) {
-+			os_free(hapd->config_id);
-+			hapd->config_id = NULL;
-+		}
-+		if (newconf->bss[j]->config_id)
-+			hapd->config_id = strdup(newconf->bss[j]->config_id);
- 		if (!hapd->conf->config_id || !newconf->bss[j]->config_id ||
- 		    os_strcmp(hapd->conf->config_id,
- 			      newconf->bss[j]->config_id) != 0)
-@@ -2700,6 +2731,10 @@ hostapd_alloc_bss_data(struct hostapd_if
- 	hapd->iconf = conf;
- 	hapd->conf = bss;
- 	hapd->iface = hapd_iface;
-+	if (bss && bss->config_id)
-+		hapd->config_id = strdup(bss->config_id);
-+	else
-+		hapd->config_id = NULL;
- 	if (conf)
- 		hapd->driver = conf->driver;
- 	hapd->ctrl_sock = -1;
---- a/src/ap/hostapd.h
-+++ b/src/ap/hostapd.h
-@@ -47,7 +47,7 @@ struct mesh_conf;
- struct hostapd_iface;
- 
- struct hapd_interfaces {
--	int (*reload_config)(struct hostapd_iface *iface);
-+	int (*reload_config)(struct hostapd_iface *iface, int reconf);
- 	struct hostapd_config * (*config_read_cb)(const char *config_fname);
- 	int (*ctrl_iface_init)(struct hostapd_data *hapd);
- 	void (*ctrl_iface_deinit)(struct hostapd_data *hapd);
-@@ -186,6 +186,7 @@ struct hostapd_data {
- 	struct hostapd_config *iconf;
- 	struct hostapd_bss_config *conf;
- 	struct hostapd_ubus_bss ubus;
-+	char *config_id;
- 	int interface_added; /* virtual interface added for this BSS */
- 	unsigned int started:1;
- 	unsigned int disabled:1;
-@@ -689,7 +690,7 @@ struct hostapd_iface {
- int hostapd_for_each_interface(struct hapd_interfaces *interfaces,
- 			       int (*cb)(struct hostapd_iface *iface,
- 					 void *ctx), void *ctx);
--int hostapd_reload_config(struct hostapd_iface *iface);
-+int hostapd_reload_config(struct hostapd_iface *iface, int reconf);
- void hostapd_reconfig_encryption(struct hostapd_data *hapd);
- struct hostapd_data *
- hostapd_alloc_bss_data(struct hostapd_iface *hapd_iface,
---- a/src/drivers/driver_nl80211.c
-+++ b/src/drivers/driver_nl80211.c
-@@ -5322,6 +5322,9 @@ static int wpa_driver_nl80211_set_ap(voi
- 	if (ret) {
- 		wpa_printf(MSG_DEBUG, "nl80211: Beacon set failed: %d (%s)",
- 			   ret, strerror(-ret));
-+		if (!bss->flink->beacon_set)
-+			ret = 0;
-+		bss->flink->beacon_set = 0;
- 	} else {
- 		link->beacon_set = 1;
- 		nl80211_set_bss(bss, params->cts_protect, params->preamble,
---- a/hostapd/ctrl_iface.c
-+++ b/hostapd/ctrl_iface.c
-@@ -187,7 +187,7 @@ static int hostapd_ctrl_iface_update(str
- 	iface->interfaces->config_read_cb = hostapd_ctrl_iface_config_read;
- 	reload_opts = txt;
- 
--	hostapd_reload_config(iface);
-+	hostapd_reload_config(iface, 0);
- 
- 	iface->interfaces->config_read_cb = config_read_cb;
- }
---- a/hostapd/main.c
-+++ b/hostapd/main.c
-@@ -410,7 +410,7 @@ static void handle_term(int sig, void *s
- 
- static int handle_reload_iface(struct hostapd_iface *iface, void *ctx)
- {
--	if (hostapd_reload_config(iface) < 0) {
-+	if (hostapd_reload_config(iface, 0) < 0) {
- 		wpa_printf(MSG_WARNING, "Failed to read new configuration "
- 			   "file - continuing with old.");
- 	}
---- a/src/ap/wps_hostapd.c
-+++ b/src/ap/wps_hostapd.c
-@@ -315,7 +315,7 @@ static void wps_reload_config(void *eloo
- 
- 	wpa_printf(MSG_DEBUG, "WPS: Reload configuration data");
- 	if (iface->interfaces == NULL ||
--	    iface->interfaces->reload_config(iface) < 0) {
-+	    iface->interfaces->reload_config(iface, 1) < 0) {
- 		wpa_printf(MSG_WARNING, "WPS: Failed to reload the updated "
- 			   "configuration");
- 	}
diff --git a/package/network/services/hostapd/patches/701-reload_config_inline.patch b/package/network/services/hostapd/patches/701-reload_config_inline.patch
new file mode 100644
index 0000000000..3c62bf670f
--- /dev/null
+++ b/package/network/services/hostapd/patches/701-reload_config_inline.patch
@@ -0,0 +1,33 @@
+--- a/hostapd/config_file.c
++++ b/hostapd/config_file.c
+@@ -4816,7 +4816,12 @@ struct hostapd_config * hostapd_config_r
+ 	int errors = 0;
+ 	size_t i;
+ 
+-	f = fopen(fname, "r");
++	if (!strncmp(fname, "data:", 5)) {
++		f = fmemopen((void *)(fname + 5), strlen(fname + 5), "r");
++		fname = "<inline>";
++	} else {
++		f = fopen(fname, "r");
++	}
+ 	if (f == NULL) {
+ 		wpa_printf(MSG_ERROR, "Could not open configuration file '%s' "
+ 			   "for reading.", fname);
+--- a/wpa_supplicant/config_file.c
++++ b/wpa_supplicant/config_file.c
+@@ -326,8 +326,13 @@ struct wpa_config * wpa_config_read(cons
+ 	while (cred_tail && cred_tail->next)
+ 		cred_tail = cred_tail->next;
+ 
++	if (!strncmp(name, "data:", 5)) {
++		f = fmemopen((void *)(name + 5), strlen(name + 5), "r");
++		name = "<inline>";
++	} else {
++		f = fopen(name, "r");
++	}
+ 	wpa_printf(MSG_DEBUG, "Reading configuration file '%s'", name);
+-	f = fopen(name, "r");
+ 	if (f == NULL) {
+ 		wpa_printf(MSG_ERROR, "Failed to open config file '%s', "
+ 			   "error: %s", name, strerror(errno));
diff --git a/package/network/services/hostapd/patches/710-vlan_no_bridge.patch b/package/network/services/hostapd/patches/710-vlan_no_bridge.patch
index 61f33acb6e..63d1b8a3b8 100644
--- a/package/network/services/hostapd/patches/710-vlan_no_bridge.patch
+++ b/package/network/services/hostapd/patches/710-vlan_no_bridge.patch
@@ -30,7 +30,7 @@
  
 --- a/hostapd/config_file.c
 +++ b/hostapd/config_file.c
-@@ -3355,6 +3355,8 @@ static int hostapd_config_fill(struct ho
+@@ -3351,6 +3351,8 @@ static int hostapd_config_fill(struct ho
  #ifndef CONFIG_NO_VLAN
  	} else if (os_strcmp(buf, "dynamic_vlan") == 0) {
  		bss->ssid.dynamic_vlan = atoi(pos);
diff --git a/package/network/services/hostapd/patches/720-iface_max_num_sta.patch b/package/network/services/hostapd/patches/720-iface_max_num_sta.patch
index 0bb00f9555..089c1ddc24 100644
--- a/package/network/services/hostapd/patches/720-iface_max_num_sta.patch
+++ b/package/network/services/hostapd/patches/720-iface_max_num_sta.patch
@@ -1,6 +1,6 @@
 --- a/hostapd/config_file.c
 +++ b/hostapd/config_file.c
-@@ -2850,6 +2850,14 @@ static int hostapd_config_fill(struct ho
+@@ -2848,6 +2848,14 @@ static int hostapd_config_fill(struct ho
  				   line, bss->max_num_sta, MAX_STA_COUNT);
  			return 1;
  		}
@@ -17,7 +17,7 @@
  	} else if (os_strcmp(buf, "extended_key_id") == 0) {
 --- a/src/ap/hostapd.h
 +++ b/src/ap/hostapd.h
-@@ -734,6 +734,7 @@ void hostapd_cleanup_cs_params(struct ho
+@@ -742,6 +742,7 @@ void hostapd_cleanup_cs_params(struct ho
  void hostapd_periodic_iface(struct hostapd_iface *iface);
  int hostapd_owe_trans_get_info(struct hostapd_data *hapd);
  void hostapd_ocv_check_csa_sa_query(void *eloop_ctx, void *timeout_ctx);
@@ -27,10 +27,10 @@
  void hostapd_cleanup_cca_params(struct hostapd_data *hapd);
 --- a/src/ap/hostapd.c
 +++ b/src/ap/hostapd.c
-@@ -272,6 +272,30 @@ static int hostapd_iface_conf_changed(st
+@@ -244,6 +244,29 @@ static int hostapd_iface_conf_changed(st
+ 	return 0;
  }
  
- 
 +static inline int hostapd_iface_num_sta(struct hostapd_iface *iface)
 +{
 +	int num_sta = 0;
@@ -54,10 +54,9 @@
 +
 +	return 0;
 +}
-+
- int hostapd_reload_config(struct hostapd_iface *iface, int reconf)
+ 
+ int hostapd_reload_config(struct hostapd_iface *iface)
  {
- 	struct hapd_interfaces *interfaces = iface->interfaces;
 --- a/src/ap/beacon.c
 +++ b/src/ap/beacon.c
 @@ -1252,7 +1252,7 @@ void handle_probe_req(struct hostapd_dat
@@ -71,7 +70,7 @@
  			   " since no room for additional STA",
 --- a/src/ap/ap_config.h
 +++ b/src/ap/ap_config.h
-@@ -1037,6 +1037,8 @@ struct hostapd_config {
+@@ -1039,6 +1039,8 @@ struct hostapd_config {
  	unsigned int track_sta_max_num;
  	unsigned int track_sta_max_age;
  
diff --git a/package/network/services/hostapd/patches/730-ft_iface.patch b/package/network/services/hostapd/patches/730-ft_iface.patch
index 563fe5b5fb..0795ed15a1 100644
--- a/package/network/services/hostapd/patches/730-ft_iface.patch
+++ b/package/network/services/hostapd/patches/730-ft_iface.patch
@@ -1,6 +1,6 @@
 --- a/hostapd/config_file.c
 +++ b/hostapd/config_file.c
-@@ -3009,6 +3009,8 @@ static int hostapd_config_fill(struct ho
+@@ -3007,6 +3007,8 @@ static int hostapd_config_fill(struct ho
  		wpa_printf(MSG_INFO,
  			   "Line %d: Obsolete peerkey parameter ignored", line);
  #ifdef CONFIG_IEEE80211R_AP
diff --git a/package/network/services/hostapd/patches/740-snoop_iface.patch b/package/network/services/hostapd/patches/740-snoop_iface.patch
index 6b6cc0fad7..ce64513a42 100644
--- a/package/network/services/hostapd/patches/740-snoop_iface.patch
+++ b/package/network/services/hostapd/patches/740-snoop_iface.patch
@@ -10,7 +10,7 @@
  	int bridge_hairpin; /* hairpin_mode on bridge members */
 --- a/src/ap/x_snoop.c
 +++ b/src/ap/x_snoop.c
-@@ -33,14 +33,16 @@ int x_snoop_init(struct hostapd_data *ha
+@@ -33,28 +33,31 @@ int x_snoop_init(struct hostapd_data *ha
  
  	hapd->x_snoop_initialized = true;
  
@@ -29,13 +29,20 @@
  		wpa_printf(MSG_DEBUG,
  			   "x_snoop: Failed to enable proxyarp on the bridge port");
  		return -1;
-@@ -54,7 +56,8 @@ int x_snoop_init(struct hostapd_data *ha
+ 	}
+ 
+ 	if (hostapd_drv_br_set_net_param(hapd, DRV_BR_NET_PARAM_GARP_ACCEPT,
+-					 1)) {
++					 conf->snoop_iface[0] ? conf->snoop_iface : NULL, 1)) {
+ 		wpa_printf(MSG_DEBUG,
+ 			   "x_snoop: Failed to enable accepting gratuitous ARP on the bridge");
+ 		return -1;
  	}
  
  #ifdef CONFIG_IPV6
 -	if (hostapd_drv_br_set_net_param(hapd, DRV_BR_MULTICAST_SNOOPING, 1)) {
 +	if (!conf->snoop_iface[0] &&
-+	    hostapd_drv_br_set_net_param(hapd, DRV_BR_MULTICAST_SNOOPING, 1)) {
++	    hostapd_drv_br_set_net_param(hapd, DRV_BR_MULTICAST_SNOOPING, NULL, 1)) {
  		wpa_printf(MSG_DEBUG,
  			   "x_snoop: Failed to enable multicast snooping on the bridge");
  		return -1;
@@ -44,15 +51,29 @@
  	struct hostapd_bss_config *conf = hapd->conf;
  	struct l2_packet_data *l2;
 +	const char *ifname = conf->bridge;
- 
--	l2 = l2_packet_init(conf->bridge, NULL, ETH_P_ALL, handler, hapd, 1);
++
 +	if (conf->snoop_iface[0])
 +		ifname = conf->snoop_iface;
-+
+ 
+-	l2 = l2_packet_init(conf->bridge, NULL, ETH_P_ALL, handler, hapd, 1);
 +	l2 = l2_packet_init(ifname, NULL, ETH_P_ALL, handler, hapd, 1);
  	if (l2 == NULL) {
  		wpa_printf(MSG_DEBUG,
  			   "x_snoop: Failed to initialize L2 packet processing %s",
+@@ -127,9 +134,12 @@ void x_snoop_mcast_to_ucast_convert_send
+ 
+ void x_snoop_deinit(struct hostapd_data *hapd)
+ {
++	struct hostapd_bss_config *conf = hapd->conf;
++
+ 	if (!hapd->x_snoop_initialized)
+ 		return;
+-	hostapd_drv_br_set_net_param(hapd, DRV_BR_NET_PARAM_GARP_ACCEPT, 0);
++	hostapd_drv_br_set_net_param(hapd, DRV_BR_NET_PARAM_GARP_ACCEPT,
++				     conf->snoop_iface[0] ? conf->snoop_iface : NULL, 0);
+ 	hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_PROXYARP, 0);
+ 	hostapd_drv_br_port_set_attr(hapd, DRV_BR_PORT_ATTR_HAIRPIN_MODE, 0);
+ 	hapd->x_snoop_initialized = false;
 --- a/hostapd/config_file.c
 +++ b/hostapd/config_file.c
 @@ -2322,6 +2322,8 @@ static int hostapd_config_fill(struct ho
@@ -64,3 +85,55 @@
  	} else if (os_strcmp(buf, "vlan_bridge") == 0) {
  		os_strlcpy(bss->vlan_bridge, pos, sizeof(bss->vlan_bridge));
  	} else if (os_strcmp(buf, "wds_bridge") == 0) {
+--- a/src/ap/ap_drv_ops.h
++++ b/src/ap/ap_drv_ops.h
+@@ -366,12 +366,12 @@ static inline int hostapd_drv_br_port_se
+ 
+ static inline int hostapd_drv_br_set_net_param(struct hostapd_data *hapd,
+ 					       enum drv_br_net_param param,
+-					       unsigned int val)
++					       const char *ifname, unsigned int val)
+ {
+ 	if (hapd->driver == NULL || hapd->drv_priv == NULL ||
+ 	    hapd->driver->br_set_net_param == NULL)
+ 		return -1;
+-	return hapd->driver->br_set_net_param(hapd->drv_priv, param, val);
++	return hapd->driver->br_set_net_param(hapd->drv_priv, param, ifname, val);
+ }
+ 
+ static inline int hostapd_drv_vendor_cmd(struct hostapd_data *hapd,
+--- a/src/drivers/driver.h
++++ b/src/drivers/driver.h
+@@ -4209,7 +4209,7 @@ struct wpa_driver_ops {
+ 	 * Returns: 0 on success, negative (<0) on failure
+ 	 */
+ 	int (*br_set_net_param)(void *priv, enum drv_br_net_param param,
+-				unsigned int val);
++				const char *ifname, unsigned int val);
+ 
+ 	/**
+ 	 * get_wowlan - Get wake-on-wireless status
+--- a/src/drivers/driver_nl80211.c
++++ b/src/drivers/driver_nl80211.c
+@@ -12168,7 +12168,7 @@ static const char * drv_br_net_param_str
+ 
+ 
+ static int wpa_driver_br_set_net_param(void *priv, enum drv_br_net_param param,
+-				       unsigned int val)
++				       const char *ifname, unsigned int val)
+ {
+ 	struct i802_bss *bss = priv;
+ 	char path[128];
+@@ -12194,8 +12194,11 @@ static int wpa_driver_br_set_net_param(v
+ 			return -EINVAL;
+ 	}
+ 
++	if (!ifname)
++		ifname = bss->brname;
++
+ 	os_snprintf(path, sizeof(path), "/proc/sys/net/ipv%d/conf/%s/%s",
+-		    ip_version, bss->brname, param_txt);
++		    ip_version, ifname, param_txt);
+ 
+ set_val:
+ 	if (linux_write_system_file(path, val))
diff --git a/package/network/services/hostapd/patches/750-qos_map_set_without_interworking.patch b/package/network/services/hostapd/patches/750-qos_map_set_without_interworking.patch
index 124f5ea6ba..97c32df704 100644
--- a/package/network/services/hostapd/patches/750-qos_map_set_without_interworking.patch
+++ b/package/network/services/hostapd/patches/750-qos_map_set_without_interworking.patch
@@ -18,7 +18,7 @@
  
  #ifdef CONFIG_HS20
  static int hs20_parse_conn_capab(struct hostapd_bss_config *bss, char *buf,
-@@ -4066,10 +4066,10 @@ static int hostapd_config_fill(struct ho
+@@ -4062,10 +4062,10 @@ static int hostapd_config_fill(struct ho
  		bss->gas_frag_limit = val;
  	} else if (os_strcmp(buf, "gas_comeback_delay") == 0) {
  		bss->gas_comeback_delay = atoi(pos);
@@ -32,7 +32,7 @@
  		os_free(bss->dump_msk_file);
 --- a/src/ap/hostapd.c
 +++ b/src/ap/hostapd.c
-@@ -1534,6 +1534,7 @@ static int hostapd_setup_bss(struct host
+@@ -1486,6 +1486,7 @@ int hostapd_setup_bss(struct hostapd_dat
  		wpa_printf(MSG_ERROR, "GAS server initialization failed");
  		return -1;
  	}
@@ -40,7 +40,7 @@
  
  	if (conf->qos_map_set_len &&
  	    hostapd_drv_set_qos_map(hapd, conf->qos_map_set,
-@@ -1541,7 +1542,6 @@ static int hostapd_setup_bss(struct host
+@@ -1493,7 +1494,6 @@ int hostapd_setup_bss(struct hostapd_dat
  		wpa_printf(MSG_ERROR, "Failed to initialize QoS Map");
  		return -1;
  	}
diff --git a/package/network/services/hostapd/patches/760-dynamic_own_ip.patch b/package/network/services/hostapd/patches/760-dynamic_own_ip.patch
index 946b4533bf..2c705a68cf 100644
--- a/package/network/services/hostapd/patches/760-dynamic_own_ip.patch
+++ b/package/network/services/hostapd/patches/760-dynamic_own_ip.patch
@@ -98,7 +98,7 @@
  	    hapd->conf->own_ip_addr.af == AF_INET &&
 --- a/hostapd/config_file.c
 +++ b/hostapd/config_file.c
-@@ -2690,6 +2690,8 @@ static int hostapd_config_fill(struct ho
+@@ -2688,6 +2688,8 @@ static int hostapd_config_fill(struct ho
  	} else if (os_strcmp(buf, "iapp_interface") == 0) {
  		wpa_printf(MSG_INFO, "DEPRECATED: iapp_interface not used");
  #endif /* CONFIG_IAPP */
diff --git a/package/network/services/hostapd/patches/761-shared_das_port.patch b/package/network/services/hostapd/patches/761-shared_das_port.patch
index dad7afddf1..cbb2a1be3c 100644
--- a/package/network/services/hostapd/patches/761-shared_das_port.patch
+++ b/package/network/services/hostapd/patches/761-shared_das_port.patch
@@ -10,7 +10,7 @@
  	unsigned int time_window;
 --- a/src/ap/hostapd.c
 +++ b/src/ap/hostapd.c
-@@ -1471,6 +1471,7 @@ static int hostapd_setup_bss(struct host
+@@ -1423,6 +1423,7 @@ int hostapd_setup_bss(struct hostapd_dat
  
  			os_memset(&das_conf, 0, sizeof(das_conf));
  			das_conf.port = conf->radius_das_port;
diff --git a/package/network/services/hostapd/patches/770-radius_server.patch b/package/network/services/hostapd/patches/770-radius_server.patch
new file mode 100644
index 0000000000..8837a26257
--- /dev/null
+++ b/package/network/services/hostapd/patches/770-radius_server.patch
@@ -0,0 +1,154 @@
+--- a/hostapd/Makefile
++++ b/hostapd/Makefile
+@@ -63,6 +63,10 @@ endif
+ OBJS += main.o
+ OBJS += config_file.o
+ 
++ifdef CONFIG_RADIUS_SERVER
++OBJS += radius.o
++endif
++
+ OBJS += ../src/ap/hostapd.o
+ OBJS += ../src/ap/wpa_auth_glue.o
+ OBJS += ../src/ap/drv_callbacks.o
+--- a/hostapd/main.c
++++ b/hostapd/main.c
+@@ -40,6 +40,7 @@ struct hapd_global {
+ 
+ static struct hapd_global global;
+ 
++extern int radius_main(int argc, char **argv);
+ 
+ #ifndef CONFIG_NO_HOSTAPD_LOGGER
+ static void hostapd_logger_cb(void *ctx, const u8 *addr, unsigned int module,
+@@ -771,6 +772,11 @@ int main(int argc, char *argv[])
+ 	if (os_program_init())
+ 		return -1;
+ 
++#ifdef RADIUS_SERVER
++	if (strstr(argv[0], "radius"))
++		return radius_main(argc, argv);
++#endif
++
+ 	os_memset(&interfaces, 0, sizeof(interfaces));
+ 	interfaces.reload_config = hostapd_reload_config;
+ 	interfaces.config_read_cb = hostapd_config_read;
+--- a/src/radius/radius_server.c
++++ b/src/radius/radius_server.c
+@@ -63,6 +63,12 @@ struct radius_server_counters {
+ 	u32 unknown_acct_types;
+ };
+ 
++struct radius_accept_attr {
++	u8 type;
++	u16 len;
++	void *data;
++};
++
+ /**
+  * struct radius_session - Internal RADIUS server data for a session
+  */
+@@ -90,7 +96,7 @@ struct radius_session {
+ 	unsigned int macacl:1;
+ 	unsigned int t_c_filtering:1;
+ 
+-	struct hostapd_radius_attr *accept_attr;
++	struct radius_accept_attr *accept_attr;
+ 
+ 	u32 t_c_timestamp; /* Last read T&C timestamp from user DB */
+ };
+@@ -394,6 +400,7 @@ static void radius_server_session_free(s
+ 	radius_msg_free(sess->last_reply);
+ 	os_free(sess->username);
+ 	os_free(sess->nas_ip);
++	os_free(sess->accept_attr);
+ 	os_free(sess);
+ 	data->num_sess--;
+ }
+@@ -554,6 +561,36 @@ radius_server_erp_find_key(struct radius
+ }
+ #endif /* CONFIG_ERP */
+ 
++static struct radius_accept_attr *
++radius_server_copy_attr(const struct hostapd_radius_attr *data)
++{
++	const struct hostapd_radius_attr *attr;
++	struct radius_accept_attr *attr_new;
++	size_t data_size = 0;
++	void *data_buf;
++	int n_attr = 1;
++
++	for (attr = data; attr; attr = attr->next) {
++		n_attr++;
++		data_size += wpabuf_len(attr->val);
++	}
++
++	attr_new = os_zalloc(n_attr * sizeof(*attr) + data_size);
++	if (!attr_new)
++		return NULL;
++
++	data_buf = &attr_new[n_attr];
++	for (n_attr = 0, attr = data; attr; attr = attr->next) {
++		struct radius_accept_attr *cur = &attr_new[n_attr++];
++
++		cur->type = attr->type;
++		cur->len = wpabuf_len(attr->val);
++		cur->data = memcpy(data_buf, wpabuf_head(attr->val), cur->len);
++		data_buf += cur->len;
++	}
++
++	return attr_new;
++}
+ 
+ static struct radius_session *
+ radius_server_get_new_session(struct radius_server_data *data,
+@@ -607,7 +644,7 @@ radius_server_get_new_session(struct rad
+ 		eap_user_free(tmp);
+ 		return NULL;
+ 	}
+-	sess->accept_attr = tmp->accept_attr;
++	sess->accept_attr = radius_server_copy_attr(tmp->accept_attr);
+ 	sess->macacl = tmp->macacl;
+ 	eap_user_free(tmp);
+ 
+@@ -1118,11 +1155,10 @@ radius_server_encapsulate_eap(struct rad
+ 	}
+ 
+ 	if (code == RADIUS_CODE_ACCESS_ACCEPT) {
+-		struct hostapd_radius_attr *attr;
+-		for (attr = sess->accept_attr; attr; attr = attr->next) {
+-			if (!radius_msg_add_attr(msg, attr->type,
+-						 wpabuf_head(attr->val),
+-						 wpabuf_len(attr->val))) {
++		struct radius_accept_attr *attr;
++		for (attr = sess->accept_attr; attr->data; attr++) {
++			if (!radius_msg_add_attr(msg, attr->type, attr->data,
++						 attr->len)) {
+ 				wpa_printf(MSG_ERROR, "Could not add RADIUS attribute");
+ 				radius_msg_free(msg);
+ 				return NULL;
+@@ -1211,11 +1247,10 @@ radius_server_macacl(struct radius_serve
+ 	}
+ 
+ 	if (code == RADIUS_CODE_ACCESS_ACCEPT) {
+-		struct hostapd_radius_attr *attr;
+-		for (attr = sess->accept_attr; attr; attr = attr->next) {
+-			if (!radius_msg_add_attr(msg, attr->type,
+-						 wpabuf_head(attr->val),
+-						 wpabuf_len(attr->val))) {
++		struct radius_accept_attr *attr;
++		for (attr = sess->accept_attr; attr->data; attr++) {
++			if (!radius_msg_add_attr(msg, attr->type, attr->data,
++						 attr->len)) {
+ 				wpa_printf(MSG_ERROR, "Could not add RADIUS attribute");
+ 				radius_msg_free(msg);
+ 				return NULL;
+@@ -2512,7 +2547,7 @@ static int radius_server_get_eap_user(vo
+ 	ret = data->get_eap_user(data->conf_ctx, identity, identity_len,
+ 				 phase2, user);
+ 	if (ret == 0 && user) {
+-		sess->accept_attr = user->accept_attr;
++		sess->accept_attr = radius_server_copy_attr(user->accept_attr);
+ 		sess->remediation = user->remediation;
+ 		sess->macacl = user->macacl;
+ 		sess->t_c_timestamp = user->t_c_timestamp;
diff --git a/package/network/services/hostapd/patches/990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch b/package/network/services/hostapd/patches/990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch
index 51690def09..5809a3b7e8 100644
--- a/package/network/services/hostapd/patches/990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch
+++ b/package/network/services/hostapd/patches/990-ctrl-make-WNM_AP-functions-dependant-on-CONFIG_AP.patch
@@ -13,7 +13,7 @@ Signed-off-by: David Bauer <mail at david-bauer.net>
 
 --- a/wpa_supplicant/ctrl_iface.c
 +++ b/wpa_supplicant/ctrl_iface.c
-@@ -12640,7 +12640,7 @@ char * wpa_supplicant_ctrl_iface_process
+@@ -12763,7 +12763,7 @@ char * wpa_supplicant_ctrl_iface_process
  		if (wpas_ctrl_iface_coloc_intf_report(wpa_s, buf + 18))
  			reply_len = -1;
  #endif /* CONFIG_WNM */
@@ -22,7 +22,7 @@ Signed-off-by: David Bauer <mail at david-bauer.net>
  	} else if (os_strncmp(buf, "DISASSOC_IMMINENT ", 18) == 0) {
  		if (ap_ctrl_iface_disassoc_imminent(wpa_s, buf + 18))
  			reply_len = -1;
-@@ -12650,7 +12650,7 @@ char * wpa_supplicant_ctrl_iface_process
+@@ -12773,7 +12773,7 @@ char * wpa_supplicant_ctrl_iface_process
  	} else if (os_strncmp(buf, "BSS_TM_REQ ", 11) == 0) {
  		if (ap_ctrl_iface_bss_tm_req(wpa_s, buf + 11))
  			reply_len = -1;
diff --git a/package/network/services/hostapd/patches/991-Fix-OpenWrt-13156.patch b/package/network/services/hostapd/patches/991-Fix-OpenWrt-13156.patch
index 99d800858f..3f10fb1eef 100644
--- a/package/network/services/hostapd/patches/991-Fix-OpenWrt-13156.patch
+++ b/package/network/services/hostapd/patches/991-Fix-OpenWrt-13156.patch
@@ -20,7 +20,7 @@ Signed-off-by: Stijn Tintel <stijn at linux-ipv6.be>
 
 --- a/src/ap/hostapd.c
 +++ b/src/ap/hostapd.c
-@@ -3615,6 +3615,8 @@ int hostapd_remove_iface(struct hapd_int
+@@ -3563,6 +3563,8 @@ int hostapd_remove_iface(struct hapd_int
  void hostapd_new_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
  			   int reassoc)
  {
@@ -29,7 +29,7 @@ Signed-off-by: Stijn Tintel <stijn at linux-ipv6.be>
  	if (hapd->tkip_countermeasures) {
  		hostapd_drv_sta_deauth(hapd, sta->addr,
  				       WLAN_REASON_MICHAEL_MIC_FAILURE);
-@@ -3622,10 +3624,16 @@ void hostapd_new_assoc_sta(struct hostap
+@@ -3570,10 +3572,16 @@ void hostapd_new_assoc_sta(struct hostap
  	}
  
  #ifdef CONFIG_IEEE80211BE
diff --git a/package/network/services/hostapd/src/hostapd/radius.c b/package/network/services/hostapd/src/hostapd/radius.c
new file mode 100644
index 0000000000..362a22c276
--- /dev/null
+++ b/package/network/services/hostapd/src/hostapd/radius.c
@@ -0,0 +1,715 @@
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "crypto/crypto.h"
+#include "crypto/tls.h"
+
+#include "ap/ap_config.h"
+#include "eap_server/eap.h"
+#include "radius/radius.h"
+#include "radius/radius_server.h"
+#include "eap_register.h"
+
+#include <libubox/blobmsg_json.h>
+#include <libubox/blobmsg.h>
+#include <libubox/avl.h>
+#include <libubox/avl-cmp.h>
+#include <libubox/kvlist.h>
+
+#include <sys/stat.h>
+#include <fnmatch.h>
+
+#define VENDOR_ID_WISPR 14122
+#define VENDOR_ATTR_SIZE 6
+
+struct radius_parse_attr_data {
+	unsigned int vendor;
+	u8 type;
+	int size;
+	char format;
+	const char *data;
+};
+
+struct radius_parse_attr_state {
+	struct hostapd_radius_attr *prev;
+	struct hostapd_radius_attr *attr;
+	struct wpabuf *buf;
+	void *attrdata;
+};
+
+struct radius_user_state {
+	struct avl_node node;
+	struct eap_user data;
+};
+
+struct radius_user_data {
+	struct kvlist users;
+	struct avl_tree user_state;
+	struct blob_attr *wildcard;
+};
+
+struct radius_state {
+	struct radius_server_data *radius;
+	struct eap_config eap;
+
+	struct radius_user_data phase1, phase2;
+	const char *user_file;
+	time_t user_file_ts;
+
+	int n_attrs;
+	struct hostapd_radius_attr *attrs;
+};
+
+struct radius_config {
+	struct tls_connection_params tls;
+	struct radius_server_conf radius;
+};
+
+enum {
+	USER_ATTR_PASSWORD,
+	USER_ATTR_HASH,
+	USER_ATTR_SALT,
+	USER_ATTR_METHODS,
+	USER_ATTR_RADIUS,
+	USER_ATTR_VLAN,
+	USER_ATTR_MAX_RATE_UP,
+	USER_ATTR_MAX_RATE_DOWN,
+	__USER_ATTR_MAX
+};
+
+static void radius_tls_event(void *ctx, enum tls_event ev,
+			      union tls_event_data *data)
+{
+	switch (ev) {
+	case TLS_CERT_CHAIN_SUCCESS:
+		wpa_printf(MSG_DEBUG, "radius: remote certificate verification success");
+		break;
+	case TLS_CERT_CHAIN_FAILURE:
+		wpa_printf(MSG_INFO, "radius: certificate chain failure: reason=%d depth=%d subject='%s' err='%s'",
+			   data->cert_fail.reason,
+			   data->cert_fail.depth,
+			   data->cert_fail.subject,
+			   data->cert_fail.reason_txt);
+		break;
+	case TLS_PEER_CERTIFICATE:
+		wpa_printf(MSG_DEBUG, "radius: peer certificate: depth=%d serial_num=%s subject=%s",
+			   data->peer_cert.depth,
+			   data->peer_cert.serial_num ? data->peer_cert.serial_num : "N/A",
+			   data->peer_cert.subject);
+		break;
+	case TLS_ALERT:
+		if (data->alert.is_local)
+			wpa_printf(MSG_DEBUG, "radius: local TLS alert: %s",
+				   data->alert.description);
+		else
+			wpa_printf(MSG_DEBUG, "radius: remote TLS alert: %s",
+				   data->alert.description);
+		break;
+	case TLS_UNSAFE_RENEGOTIATION_DISABLED:
+		/* Not applicable to TLS server */
+		break;
+	}
+}
+
+static void radius_userdata_init(struct radius_user_data *u)
+{
+	kvlist_init(&u->users, kvlist_blob_len);
+	avl_init(&u->user_state, avl_strcmp, false, NULL);
+}
+
+static void radius_userdata_free(struct radius_user_data *u)
+{
+	struct radius_user_state *s, *tmp;
+
+	kvlist_free(&u->users);
+	free(u->wildcard);
+	u->wildcard = NULL;
+	avl_remove_all_elements(&u->user_state, s, node, tmp)
+		free(s);
+}
+
+static void
+radius_userdata_load(struct radius_user_data *u, struct blob_attr *data)
+{
+	enum {
+		USERSTATE_USERS,
+		USERSTATE_WILDCARD,
+		__USERSTATE_MAX,
+	};
+	static const struct blobmsg_policy policy[__USERSTATE_MAX] = {
+		[USERSTATE_USERS] = { "users", BLOBMSG_TYPE_TABLE },
+		[USERSTATE_WILDCARD] = { "wildcard", BLOBMSG_TYPE_ARRAY },
+	};
+	struct blob_attr *tb[__USERSTATE_MAX], *cur;
+	int rem;
+
+	if (!data)
+		return;
+
+	blobmsg_parse(policy, __USERSTATE_MAX, tb, blobmsg_data(data), blobmsg_len(data));
+
+	blobmsg_for_each_attr(cur, tb[USERSTATE_USERS], rem)
+		kvlist_set(&u->users, blobmsg_name(cur), cur);
+
+	if (tb[USERSTATE_WILDCARD])
+		u->wildcard = blob_memdup(tb[USERSTATE_WILDCARD]);
+}
+
+static void
+load_userfile(struct radius_state *s)
+{
+	enum {
+		USERDATA_PHASE1,
+		USERDATA_PHASE2,
+		__USERDATA_MAX
+	};
+	static const struct blobmsg_policy policy[__USERDATA_MAX] = {
+		[USERDATA_PHASE1] = { "phase1", BLOBMSG_TYPE_TABLE },
+		[USERDATA_PHASE2] = { "phase2", BLOBMSG_TYPE_TABLE },
+	};
+	struct blob_attr *tb[__USERDATA_MAX], *cur;
+	static struct blob_buf b;
+	struct stat st;
+	int rem;
+
+	if (stat(s->user_file, &st))
+		return;
+
+	if (s->user_file_ts == st.st_mtime)
+		return;
+
+	s->user_file_ts = st.st_mtime;
+	radius_userdata_free(&s->phase1);
+	radius_userdata_free(&s->phase2);
+
+	blob_buf_init(&b, 0);
+	blobmsg_add_json_from_file(&b, s->user_file);
+	blobmsg_parse(policy, __USERDATA_MAX, tb, blob_data(b.head), blob_len(b.head));
+	radius_userdata_load(&s->phase1, tb[USERDATA_PHASE1]);
+	radius_userdata_load(&s->phase2, tb[USERDATA_PHASE2]);
+
+	blob_buf_free(&b);
+}
+
+static struct blob_attr *
+radius_user_get(struct radius_user_data *s, const char *name)
+{
+	struct blob_attr *cur;
+	int rem;
+
+	cur = kvlist_get(&s->users, name);
+	if (cur)
+		return cur;
+
+	blobmsg_for_each_attr(cur, s->wildcard, rem) {
+		static const struct blobmsg_policy policy = {
+			"name", BLOBMSG_TYPE_STRING
+		};
+		struct blob_attr *pattern;
+
+		if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE)
+			continue;
+
+		blobmsg_parse(&policy, 1, &pattern, blobmsg_data(cur), blobmsg_len(cur));
+		if (!name)
+			continue;
+
+		if (!fnmatch(blobmsg_get_string(pattern), name, 0))
+			return cur;
+	}
+
+	return NULL;
+}
+
+static struct radius_parse_attr_data *
+radius_parse_attr(struct blob_attr *attr)
+{
+	static const struct blobmsg_policy policy[4] = {
+		{ .type = BLOBMSG_TYPE_INT32 },
+		{ .type = BLOBMSG_TYPE_INT32 },
+		{ .type = BLOBMSG_TYPE_STRING },
+		{ .type = BLOBMSG_TYPE_STRING },
+	};
+	static struct radius_parse_attr_data data;
+	struct blob_attr *tb[4];
+	const char *format;
+
+	blobmsg_parse_array(policy, ARRAY_SIZE(policy), tb, blobmsg_data(attr), blobmsg_len(attr));
+
+	if (!tb[0] || !tb[1] || !tb[2] || !tb[3])
+		return NULL;
+
+	format = blobmsg_get_string(tb[2]);
+	if (strlen(format) != 1)
+		return NULL;
+
+	data.vendor = blobmsg_get_u32(tb[0]);
+	data.type = blobmsg_get_u32(tb[1]);
+	data.format = format[0];
+	data.data = blobmsg_get_string(tb[3]);
+	data.size = strlen(data.data);
+
+	switch (data.format) {
+	case 's':
+		break;
+	case 'x':
+		if (data.size & 1)
+			return NULL;
+		data.size /= 2;
+		break;
+	case 'd':
+		data.size = 4;
+		break;
+	default:
+		return NULL;
+	}
+
+	return &data;
+}
+
+static void
+radius_count_attrs(struct blob_attr **tb, int *n_attr, size_t *attr_size)
+{
+	struct blob_attr *data = tb[USER_ATTR_RADIUS];
+	struct blob_attr *cur;
+	int rem;
+
+	blobmsg_for_each_attr(cur, data, rem) {
+		struct radius_parse_attr_data *data;
+		size_t prev = *attr_size;
+
+		data = radius_parse_attr(cur);
+		if (!data)
+			continue;
+
+		*attr_size += data->size;
+		if (data->vendor)
+			*attr_size += VENDOR_ATTR_SIZE;
+
+		(*n_attr)++;
+	}
+
+	*n_attr += !!tb[USER_ATTR_VLAN] * 3 +
+		   !!tb[USER_ATTR_MAX_RATE_UP] +
+		   !!tb[USER_ATTR_MAX_RATE_DOWN];
+	*attr_size += !!tb[USER_ATTR_VLAN] * (4 + 4 + 5) +
+		      !!tb[USER_ATTR_MAX_RATE_UP] * (4 + VENDOR_ATTR_SIZE) +
+		      !!tb[USER_ATTR_MAX_RATE_DOWN] * (4 + VENDOR_ATTR_SIZE);
+}
+
+static void *
+radius_add_attr(struct radius_parse_attr_state *state,
+		u32 vendor, u8 type, u8 len)
+{
+	struct hostapd_radius_attr *attr;
+	struct wpabuf *buf;
+	void *val;
+
+	val = state->attrdata;
+
+	buf = state->buf++;
+	buf->buf = val;
+
+	attr = state->attr++;
+	attr->val = buf;
+	attr->type = type;
+
+	if (state->prev)
+		state->prev->next = attr;
+	state->prev = attr;
+
+	if (vendor) {
+		u8 *vendor_hdr = val + 4;
+
+		WPA_PUT_BE32(val, vendor);
+		vendor_hdr[0] = type;
+		vendor_hdr[1] = len + 2;
+
+		len += VENDOR_ATTR_SIZE;
+		val += VENDOR_ATTR_SIZE;
+		attr->type = RADIUS_ATTR_VENDOR_SPECIFIC;
+	}
+
+	buf->size = buf->used = len;
+	state->attrdata += len;
+
+	return val;
+}
+
+static void
+radius_parse_attrs(struct blob_attr **tb, struct radius_parse_attr_state *state)
+{
+	struct blob_attr *data = tb[USER_ATTR_RADIUS];
+	struct hostapd_radius_attr *prev = NULL;
+	struct blob_attr *cur;
+	int len, rem;
+	void *val;
+
+	if ((cur = tb[USER_ATTR_VLAN]) != NULL && blobmsg_get_u32(cur) < 4096) {
+		char buf[5];
+
+		val = radius_add_attr(state, 0, RADIUS_ATTR_TUNNEL_TYPE, 4);
+		WPA_PUT_BE32(val, RADIUS_TUNNEL_TYPE_VLAN);
+
+		val = radius_add_attr(state, 0, RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, 4);
+		WPA_PUT_BE32(val, RADIUS_TUNNEL_MEDIUM_TYPE_802);
+
+		len = snprintf(buf, sizeof(buf), "%d", blobmsg_get_u32(cur));
+		val = radius_add_attr(state, 0, RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID, len);
+		memcpy(val, buf, len);
+	}
+
+	if ((cur = tb[USER_ATTR_MAX_RATE_UP]) != NULL) {
+		val = radius_add_attr(state, VENDOR_ID_WISPR, 7, 4);
+		WPA_PUT_BE32(val, blobmsg_get_u32(cur));
+	}
+
+	if ((cur = tb[USER_ATTR_MAX_RATE_DOWN]) != NULL) {
+		val = radius_add_attr(state, VENDOR_ID_WISPR, 8, 4);
+		WPA_PUT_BE32(val, blobmsg_get_u32(cur));
+	}
+
+	blobmsg_for_each_attr(cur, data, rem) {
+		struct radius_parse_attr_data *data;
+		void *val;
+		int size;
+
+		data = radius_parse_attr(cur);
+		if (!data)
+			continue;
+
+		val = radius_add_attr(state, data->vendor, data->type, data->size);
+		switch (data->format) {
+		case 's':
+			memcpy(val, data->data, data->size);
+			break;
+		case 'x':
+			hexstr2bin(data->data, val, data->size);
+			break;
+		case 'd':
+			WPA_PUT_BE32(val, atoi(data->data));
+			break;
+		}
+	}
+}
+
+static void
+radius_user_parse_methods(struct eap_user *eap, struct blob_attr *data)
+{
+	struct blob_attr *cur;
+	int rem, n = 0;
+
+	if (!data)
+		return;
+
+	blobmsg_for_each_attr(cur, data, rem) {
+		const char *method;
+
+		if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+			continue;
+
+		if (n == EAP_MAX_METHODS)
+			break;
+
+		method = blobmsg_get_string(cur);
+		eap->methods[n].method = eap_server_get_type(method, &eap->methods[n].vendor);
+		if (eap->methods[n].vendor == EAP_VENDOR_IETF &&
+		    eap->methods[n].method == EAP_TYPE_NONE) {
+			if (!strcmp(method, "TTLS-PAP")) {
+				eap->ttls_auth |= EAP_TTLS_AUTH_PAP;
+				continue;
+			}
+			if (!strcmp(method, "TTLS-CHAP")) {
+				eap->ttls_auth |= EAP_TTLS_AUTH_CHAP;
+				continue;
+			}
+			if (!strcmp(method, "TTLS-MSCHAP")) {
+				eap->ttls_auth |= EAP_TTLS_AUTH_MSCHAP;
+				continue;
+			}
+			if (!strcmp(method, "TTLS-MSCHAPV2")) {
+				eap->ttls_auth |= EAP_TTLS_AUTH_MSCHAPV2;
+				continue;
+			}
+		}
+		n++;
+	}
+}
+
+static struct eap_user *
+radius_user_get_state(struct radius_user_data *u, struct blob_attr *data,
+		      const char *id)
+{
+	static const struct blobmsg_policy policy[__USER_ATTR_MAX] = {
+		[USER_ATTR_PASSWORD] = { "password", BLOBMSG_TYPE_STRING },
+		[USER_ATTR_HASH] = { "hash", BLOBMSG_TYPE_STRING },
+		[USER_ATTR_SALT] = { "salt", BLOBMSG_TYPE_STRING },
+		[USER_ATTR_METHODS] = { "methods", BLOBMSG_TYPE_ARRAY },
+		[USER_ATTR_RADIUS] = { "radius", BLOBMSG_TYPE_ARRAY },
+		[USER_ATTR_VLAN] = { "vlan-id", BLOBMSG_TYPE_INT32 },
+		[USER_ATTR_MAX_RATE_UP] = { "max-rate-up", BLOBMSG_TYPE_INT32 },
+		[USER_ATTR_MAX_RATE_DOWN] = { "max-rate-down", BLOBMSG_TYPE_INT32 },
+	};
+	struct blob_attr *tb[__USER_ATTR_MAX], *cur;
+	char *password_buf, *salt_buf, *name_buf;
+	struct radius_parse_attr_state astate = {};
+	struct hostapd_radius_attr *attr;
+	struct radius_user_state *state;
+	int pw_len = 0, salt_len = 0;
+	struct eap_user *eap;
+	struct wpabuf *val;
+	size_t attrsize = 0;
+	void *attrdata;
+	int n_attr = 0;
+
+	state = avl_find_element(&u->user_state, id, state, node);
+	if (state)
+		return &state->data;
+
+	blobmsg_parse(policy, __USER_ATTR_MAX, tb, blobmsg_data(data), blobmsg_len(data));
+
+	if ((cur = tb[USER_ATTR_SALT]) != NULL)
+		salt_len = strlen(blobmsg_get_string(cur)) / 2;
+	if ((cur = tb[USER_ATTR_HASH]) != NULL)
+		pw_len = strlen(blobmsg_get_string(cur)) / 2;
+	else if ((cur = tb[USER_ATTR_PASSWORD]) != NULL)
+		pw_len = blobmsg_len(cur) - 1;
+	radius_count_attrs(tb, &n_attr, &attrsize);
+
+	state = calloc_a(sizeof(*state), &name_buf, strlen(id) + 1,
+			 &password_buf, pw_len,
+			 &salt_buf, salt_len,
+			 &astate.attr, n_attr * sizeof(*astate.attr),
+			 &astate.buf, n_attr * sizeof(*astate.buf),
+			 &astate.attrdata, attrsize);
+	eap = &state->data;
+	eap->salt = salt_len ? salt_buf : NULL;
+	eap->salt_len = salt_len;
+	eap->password = pw_len ? password_buf : NULL;
+	eap->password_len = pw_len;
+	eap->force_version = -1;
+
+	if ((cur = tb[USER_ATTR_SALT]) != NULL)
+		hexstr2bin(blobmsg_get_string(cur), salt_buf, salt_len);
+	if ((cur = tb[USER_ATTR_PASSWORD]) != NULL)
+		memcpy(password_buf, blobmsg_get_string(cur), pw_len);
+	else if ((cur = tb[USER_ATTR_HASH]) != NULL) {
+		hexstr2bin(blobmsg_get_string(cur), password_buf, pw_len);
+		eap->password_hash = 1;
+	}
+	radius_user_parse_methods(eap, tb[USER_ATTR_METHODS]);
+
+	if (n_attr > 0) {
+		cur = tb[USER_ATTR_RADIUS];
+		eap->accept_attr = astate.attr;
+		radius_parse_attrs(tb, &astate);
+	}
+
+	state->node.key = strcpy(name_buf, id);
+	avl_insert(&u->user_state, &state->node);
+
+	return &state->data;
+
+free:
+	free(state);
+	return NULL;
+}
+
+static int radius_get_eap_user(void *ctx, const u8 *identity,
+			       size_t identity_len, int phase2,
+			       struct eap_user *user)
+{
+	struct radius_state *s = ctx;
+	struct radius_user_data *u = phase2 ? &s->phase2 : &s->phase1;
+	struct blob_attr *entry;
+	struct eap_user *data;
+	char *id;
+
+	if (identity_len > 512)
+		return -1;
+
+	load_userfile(s);
+
+	id = alloca(identity_len + 1);
+	memcpy(id, identity, identity_len);
+	id[identity_len] = 0;
+
+	entry = radius_user_get(u, id);
+	if (!entry)
+		return -1;
+
+	if (!user)
+		return 0;
+
+	data = radius_user_get_state(u, entry, id);
+	if (!data)
+		return -1;
+
+	*user = *data;
+	if (user->password_len > 0)
+		user->password = os_memdup(user->password, user->password_len);
+	if (user->salt_len > 0)
+		user->salt = os_memdup(user->salt, user->salt_len);
+	user->phase2 = phase2;
+
+	return 0;
+}
+
+static int radius_setup(struct radius_state *s, struct radius_config *c)
+{
+	struct eap_config *eap = &s->eap;
+	struct tls_config conf = {
+		.event_cb = radius_tls_event,
+		.tls_flags = TLS_CONN_DISABLE_TLSv1_3,
+		.cb_ctx = s,
+	};
+
+	eap->eap_server = 1;
+	eap->max_auth_rounds = 100;
+	eap->max_auth_rounds_short = 50;
+	eap->ssl_ctx = tls_init(&conf);
+	if (!eap->ssl_ctx) {
+		wpa_printf(MSG_INFO, "TLS init failed\n");
+		return 1;
+	}
+
+	if (tls_global_set_params(eap->ssl_ctx, &c->tls)) {
+		wpa_printf(MSG_INFO, "failed to set TLS parameters\n");
+		return 1;
+	}
+
+	c->radius.eap_cfg = eap;
+	c->radius.conf_ctx = s;
+	c->radius.get_eap_user = radius_get_eap_user;
+	s->radius = radius_server_init(&c->radius);
+	if (!s->radius) {
+		wpa_printf(MSG_INFO, "failed to initialize radius server\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int radius_init(struct radius_state *s)
+{
+	memset(s, 0, sizeof(*s));
+	radius_userdata_init(&s->phase1);
+	radius_userdata_init(&s->phase2);
+}
+
+static void radius_deinit(struct radius_state *s)
+{
+	if (s->radius)
+		radius_server_deinit(s->radius);
+
+	if (s->eap.ssl_ctx)
+		tls_deinit(s->eap.ssl_ctx);
+
+	radius_userdata_free(&s->phase1);
+	radius_userdata_free(&s->phase2);
+}
+
+static int usage(const char *progname)
+{
+	fprintf(stderr, "Usage: %s <options>\n",
+		progname);
+}
+
+int radius_main(int argc, char **argv)
+{
+	static struct radius_state state = {};
+	static struct radius_config config = {};
+	const char *progname = argv[0];
+	int ret = 0;
+	int ch;
+
+	wpa_debug_setup_stdout();
+	wpa_debug_level = 0;
+
+	if (eloop_init()) {
+		wpa_printf(MSG_ERROR, "Failed to initialize event loop");
+		return 1;
+	}
+
+	eap_server_register_methods();
+	radius_init(&state);
+
+	while ((ch = getopt(argc, argv, "6C:c:d:i:k:K:p:P:s:u:")) != -1) {
+		switch (ch) {
+		case '6':
+			config.radius.ipv6 = 1;
+			break;
+		case 'C':
+			config.tls.ca_cert = optarg;
+			break;
+		case 'c':
+			if (config.tls.client_cert2)
+				return usage(progname);
+
+			if (config.tls.client_cert)
+				config.tls.client_cert2 = optarg;
+			else
+				config.tls.client_cert = optarg;
+			break;
+		case 'd':
+			config.tls.dh_file = optarg;
+			break;
+		case 'i':
+			state.eap.server_id = optarg;
+			state.eap.server_id_len = strlen(optarg);
+			break;
+		case 'k':
+			if (config.tls.private_key2)
+				return usage(progname);
+
+			if (config.tls.private_key)
+				config.tls.private_key2 = optarg;
+			else
+				config.tls.private_key = optarg;
+			break;
+		case 'K':
+			if (config.tls.private_key_passwd2)
+				return usage(progname);
+
+			if (config.tls.private_key_passwd)
+				config.tls.private_key_passwd2 = optarg;
+			else
+				config.tls.private_key_passwd = optarg;
+			break;
+		case 'p':
+			config.radius.auth_port = atoi(optarg);
+			break;
+		case 'P':
+			config.radius.acct_port = atoi(optarg);
+			break;
+		case 's':
+			config.radius.client_file = optarg;
+			break;
+		case 'u':
+			state.user_file = optarg;
+			break;
+		default:
+			return usage(progname);
+		}
+	}
+
+	if (!config.tls.client_cert || !config.tls.private_key ||
+	    !config.radius.client_file || !state.eap.server_id ||
+	    !state.user_file) {
+		wpa_printf(MSG_INFO, "missing options\n");
+		goto out;
+	}
+
+	ret = radius_setup(&state, &config);
+	if (ret)
+		goto out;
+
+	load_userfile(&state);
+	eloop_run();
+
+out:
+	radius_deinit(&state);
+	os_program_deinit();
+
+	return ret;
+}
diff --git a/package/network/services/hostapd/src/src/ap/ubus.c b/package/network/services/hostapd/src/src/ap/ubus.c
index ddd86447eb..6ff2257c32 100644
--- a/package/network/services/hostapd/src/src/ap/ubus.c
+++ b/package/network/services/hostapd/src/src/ap/ubus.c
@@ -29,11 +29,6 @@ static struct ubus_context *ctx;
 static struct blob_buf b;
 static int ctx_ref;
 
-static inline struct hapd_interfaces *get_hapd_interfaces_from_object(struct ubus_object *obj)
-{
-	return container_of(obj, struct hapd_interfaces, ubus);
-}
-
 static inline struct hostapd_data *get_hapd_from_object(struct ubus_object *obj)
 {
 	return container_of(obj, struct hostapd_data, ubus.obj);
@@ -44,12 +39,6 @@ struct ubus_banned_client {
 	u8 addr[ETH_ALEN];
 };
 
-static void ubus_receive(int sock, void *eloop_ctx, void *sock_ctx)
-{
-	struct ubus_context *ctx = eloop_ctx;
-	ubus_handle_event(ctx);
-}
-
 static void ubus_reconnect_timeout(void *eloop_data, void *user_ctx)
 {
 	if (ubus_reconnect(ctx, NULL)) {
@@ -57,12 +46,12 @@ static void ubus_reconnect_timeout(void *eloop_data, void *user_ctx)
 		return;
 	}
 
-	eloop_register_read_sock(ctx->sock.fd, ubus_receive, ctx, NULL);
+	ubus_add_uloop(ctx);
 }
 
 static void hostapd_ubus_connection_lost(struct ubus_context *ctx)
 {
-	eloop_unregister_read_sock(ctx->sock.fd);
+	uloop_fd_delete(&ctx->sock);
 	eloop_register_timeout(1, 0, ubus_reconnect_timeout, ctx, NULL);
 }
 
@@ -71,12 +60,14 @@ static bool hostapd_ubus_init(void)
 	if (ctx)
 		return true;
 
+	eloop_add_uloop();
 	ctx = ubus_connect(NULL);
 	if (!ctx)
 		return false;
 
 	ctx->connection_lost = hostapd_ubus_connection_lost;
-	eloop_register_read_sock(ctx->sock.fd, ubus_receive, ctx, NULL);
+	ubus_add_uloop(ctx);
+
 	return true;
 }
 
@@ -94,7 +85,7 @@ static void hostapd_ubus_ref_dec(void)
 	if (ctx_ref)
 		return;
 
-	eloop_unregister_read_sock(ctx->sock.fd);
+	uloop_fd_delete(&ctx->sock);
 	ubus_free(ctx);
 	ctx = NULL;
 }
@@ -127,38 +118,6 @@ static void hostapd_notify_ubus(struct ubus_object *obj, char *bssname, char *ev
 	free(event_type);
 }
 
-static void hostapd_send_procd_event(char *bssname, char *event)
-{
-	char *name, *s;
-	uint32_t id;
-	void *v;
-
-	if (!ctx || ubus_lookup_id(ctx, "service", &id))
-		return;
-
-	if (asprintf(&name, "hostapd.%s.%s", bssname, event) < 0)
-		return;
-
-	blob_buf_init(&b, 0);
-
-	s = blobmsg_alloc_string_buffer(&b, "type", strlen(name) + 1);
-	sprintf(s, "%s", name);
-	blobmsg_add_string_buffer(&b);
-
-	v = blobmsg_open_table(&b, "data");
-	blobmsg_close_table(&b, v);
-
-	ubus_invoke(ctx, id, "event", b.head, NULL, NULL, 1000);
-
-	free(name);
-}
-
-static void hostapd_send_shared_event(struct ubus_object *obj, char *bssname, char *event)
-{
-	hostapd_send_procd_event(bssname, event);
-	hostapd_notify_ubus(obj, bssname, event);
-}
-
 static void
 hostapd_bss_del_ban(void *eloop_data, void *user_ctx)
 {
@@ -203,10 +162,8 @@ hostapd_bss_reload(struct ubus_context *ctx, struct ubus_object *obj,
 		   struct blob_attr *msg)
 {
 	struct hostapd_data *hapd = container_of(obj, struct hostapd_data, ubus.obj);
-	int ret = hostapd_reload_config(hapd->iface, 1);
 
-	hostapd_send_shared_event(&hapd->iface->interfaces->ubus, hapd->conf->iface, "reload");
-	return ret;
+	return hostapd_reload_config(hapd->iface);
 }
 
 
@@ -687,68 +644,6 @@ enum {
 	__CONFIG_MAX
 };
 
-static const struct blobmsg_policy config_add_policy[__CONFIG_MAX] = {
-	[CONFIG_IFACE] = { "iface", BLOBMSG_TYPE_STRING },
-	[CONFIG_FILE] = { "config", BLOBMSG_TYPE_STRING },
-};
-
-static int
-hostapd_config_add(struct ubus_context *ctx, struct ubus_object *obj,
-		   struct ubus_request_data *req, const char *method,
-		   struct blob_attr *msg)
-{
-	struct blob_attr *tb[__CONFIG_MAX];
-	struct hapd_interfaces *interfaces = get_hapd_interfaces_from_object(obj);
-	char buf[128];
-
-	blobmsg_parse(config_add_policy, __CONFIG_MAX, tb, blob_data(msg), blob_len(msg));
-
-	if (!tb[CONFIG_FILE] || !tb[CONFIG_IFACE])
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	snprintf(buf, sizeof(buf), "bss_config=%s:%s",
-		blobmsg_get_string(tb[CONFIG_IFACE]),
-		blobmsg_get_string(tb[CONFIG_FILE]));
-
-	if (hostapd_add_iface(interfaces, buf))
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	blob_buf_init(&b, 0);
-	blobmsg_add_u32(&b, "pid", getpid());
-	ubus_send_reply(ctx, req, b.head);
-
-	return UBUS_STATUS_OK;
-}
-
-enum {
-	CONFIG_REM_IFACE,
-	__CONFIG_REM_MAX
-};
-
-static const struct blobmsg_policy config_remove_policy[__CONFIG_REM_MAX] = {
-	[CONFIG_REM_IFACE] = { "iface", BLOBMSG_TYPE_STRING },
-};
-
-static int
-hostapd_config_remove(struct ubus_context *ctx, struct ubus_object *obj,
-		      struct ubus_request_data *req, const char *method,
-		      struct blob_attr *msg)
-{
-	struct blob_attr *tb[__CONFIG_REM_MAX];
-	struct hapd_interfaces *interfaces = get_hapd_interfaces_from_object(obj);
-	char buf[128];
-
-	blobmsg_parse(config_remove_policy, __CONFIG_REM_MAX, tb, blob_data(msg), blob_len(msg));
-
-	if (!tb[CONFIG_REM_IFACE])
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	if (hostapd_remove_iface(interfaces, blobmsg_get_string(tb[CONFIG_REM_IFACE])))
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	return UBUS_STATUS_OK;
-}
-
 enum {
 	CSA_FREQ,
 	CSA_BCN_COUNT,
@@ -1669,10 +1564,61 @@ hostapd_bss_update_airtime(struct ubus_context *ctx, struct ubus_object *obj,
 }
 #endif
 
+#ifdef CONFIG_TAXONOMY
+static const struct blobmsg_policy addr_policy[] = {
+	{ "address", BLOBMSG_TYPE_STRING }
+};
+
+static bool
+hostapd_add_b64_data(const char *name, const struct wpabuf *buf)
+{
+	char *str;
+
+	if (!buf)
+		return false;
+
+	str = blobmsg_alloc_string_buffer(&b, name, B64_ENCODE_LEN(wpabuf_len(buf)));
+	b64_encode(wpabuf_head(buf), wpabuf_len(buf), str, B64_ENCODE_LEN(wpabuf_len(buf)));
+	blobmsg_add_string_buffer(&b);
+
+	return true;
+}
+
+static int
+hostapd_bss_get_sta_ies(struct ubus_context *ctx, struct ubus_object *obj,
+			struct ubus_request_data *req, const char *method,
+			struct blob_attr *msg)
+{
+	struct hostapd_data *hapd = container_of(obj, struct hostapd_data, ubus.obj);
+	struct blob_attr *tb;
+	struct sta_info *sta;
+	u8 addr[ETH_ALEN];
+
+	blobmsg_parse(addr_policy, 1, &tb, blobmsg_data(msg), blobmsg_len(msg));
+
+	if (!tb || hwaddr_aton(blobmsg_data(tb), addr))
+		return UBUS_STATUS_INVALID_ARGUMENT;
+
+	sta = ap_get_sta(hapd, addr);
+	if (!sta || (!sta->probe_ie_taxonomy && !sta->assoc_ie_taxonomy))
+		return UBUS_STATUS_NOT_FOUND;
+
+	blob_buf_init(&b, 0);
+	hostapd_add_b64_data("probe_ie", sta->probe_ie_taxonomy);
+	hostapd_add_b64_data("assoc_ie", sta->assoc_ie_taxonomy);
+	ubus_send_reply(ctx, req, b.head);
+
+	return 0;
+}
+#endif
+
 
 static const struct ubus_method bss_methods[] = {
 	UBUS_METHOD_NOARG("reload", hostapd_bss_reload),
 	UBUS_METHOD_NOARG("get_clients", hostapd_bss_get_clients),
+#ifdef CONFIG_TAXONOMY
+	UBUS_METHOD("get_sta_ies", hostapd_bss_get_sta_ies, addr_policy),
+#endif
 	UBUS_METHOD_NOARG("get_status", hostapd_bss_get_status),
 	UBUS_METHOD("del_client", hostapd_bss_del_client, del_policy),
 #ifdef CONFIG_AIRTIME_POLICY
@@ -1734,8 +1680,6 @@ void hostapd_ubus_add_bss(struct hostapd_data *hapd)
 	obj->n_methods = bss_object_type.n_methods;
 	ret = ubus_add_object(ctx, obj);
 	hostapd_ubus_ref_inc();
-
-	hostapd_send_shared_event(&hapd->iface->interfaces->ubus, hapd->conf->iface, "add");
 }
 
 void hostapd_ubus_free_bss(struct hostapd_data *hapd)
@@ -1751,8 +1695,6 @@ void hostapd_ubus_free_bss(struct hostapd_data *hapd)
 	if (!ctx)
 		return;
 
-	hostapd_send_shared_event(&hapd->iface->interfaces->ubus, hapd->conf->iface, "remove");
-
 	if (obj->id) {
 		ubus_remove_object(ctx, obj);
 		hostapd_ubus_ref_dec();
@@ -1798,47 +1740,6 @@ void hostapd_ubus_remove_vlan(struct hostapd_data *hapd, struct hostapd_vlan *vl
 	hostapd_ubus_vlan_action(hapd, vlan, "vlan_remove");
 }
 
-static const struct ubus_method daemon_methods[] = {
-	UBUS_METHOD("config_add", hostapd_config_add, config_add_policy),
-	UBUS_METHOD("config_remove", hostapd_config_remove, config_remove_policy),
-};
-
-static struct ubus_object_type daemon_object_type =
-	UBUS_OBJECT_TYPE("hostapd", daemon_methods);
-
-void hostapd_ubus_add(struct hapd_interfaces *interfaces)
-{
-	struct ubus_object *obj = &interfaces->ubus;
-	int ret;
-
-	if (!hostapd_ubus_init())
-		return;
-
-	obj->name = strdup("hostapd");
-
-	obj->type = &daemon_object_type;
-	obj->methods = daemon_object_type.methods;
-	obj->n_methods = daemon_object_type.n_methods;
-	ret = ubus_add_object(ctx, obj);
-	hostapd_ubus_ref_inc();
-}
-
-void hostapd_ubus_free(struct hapd_interfaces *interfaces)
-{
-	struct ubus_object *obj = &interfaces->ubus;
-	char *name = (char *) obj->name;
-
-	if (!ctx)
-		return;
-
-	if (obj->id) {
-		ubus_remove_object(ctx, obj);
-		hostapd_ubus_ref_dec();
-	}
-
-	free(name);
-}
-
 struct ubus_event_req {
 	struct ubus_notify_request nreq;
 	int resp;
diff --git a/package/network/services/hostapd/src/src/ap/ucode.c b/package/network/services/hostapd/src/src/ap/ucode.c
new file mode 100644
index 0000000000..e79f2420c0
--- /dev/null
+++ b/package/network/services/hostapd/src/src/ap/ucode.c
@@ -0,0 +1,808 @@
+#include <sys/un.h>
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "utils/ucode.h"
+#include "hostapd.h"
+#include "beacon.h"
+#include "hw_features.h"
+#include "ap_drv_ops.h"
+#include "dfs.h"
+#include "acs.h"
+#include <libubox/uloop.h>
+
+static uc_resource_type_t *global_type, *bss_type, *iface_type;
+static struct hapd_interfaces *interfaces;
+static uc_value_t *global, *bss_registry, *iface_registry;
+static uc_vm_t *vm;
+
+static uc_value_t *
+hostapd_ucode_bss_get_uval(struct hostapd_data *hapd)
+{
+	uc_value_t *val;
+
+	if (hapd->ucode.idx)
+		return wpa_ucode_registry_get(bss_registry, hapd->ucode.idx);
+
+	val = uc_resource_new(bss_type, hapd);
+	hapd->ucode.idx = wpa_ucode_registry_add(bss_registry, val);
+
+	return val;
+}
+
+static uc_value_t *
+hostapd_ucode_iface_get_uval(struct hostapd_iface *hapd)
+{
+	uc_value_t *val;
+
+	if (hapd->ucode.idx)
+		return wpa_ucode_registry_get(iface_registry, hapd->ucode.idx);
+
+	val = uc_resource_new(iface_type, hapd);
+	hapd->ucode.idx = wpa_ucode_registry_add(iface_registry, val);
+
+	return val;
+}
+
+static void
+hostapd_ucode_update_bss_list(struct hostapd_iface *iface, uc_value_t *if_bss, uc_value_t *bss)
+{
+	uc_value_t *list;
+	int i;
+
+	list = ucv_array_new(vm);
+	for (i = 0; i < iface->num_bss; i++) {
+		struct hostapd_data *hapd = iface->bss[i];
+		uc_value_t *val = hostapd_ucode_bss_get_uval(hapd);
+
+		ucv_array_set(list, i, ucv_get(ucv_string_new(hapd->conf->iface)));
+		ucv_object_add(bss, hapd->conf->iface, ucv_get(val));
+	}
+	ucv_object_add(if_bss, iface->phy, ucv_get(list));
+}
+
+static void
+hostapd_ucode_update_interfaces(void)
+{
+	uc_value_t *ifs = ucv_object_new(vm);
+	uc_value_t *if_bss = ucv_array_new(vm);
+	uc_value_t *bss = ucv_object_new(vm);
+	int i;
+
+	for (i = 0; i < interfaces->count; i++) {
+		struct hostapd_iface *iface = interfaces->iface[i];
+
+		ucv_object_add(ifs, iface->phy, ucv_get(hostapd_ucode_iface_get_uval(iface)));
+		hostapd_ucode_update_bss_list(iface, if_bss, bss);
+	}
+
+	ucv_object_add(ucv_prototype_get(global), "interfaces", ucv_get(ifs));
+	ucv_object_add(ucv_prototype_get(global), "interface_bss", ucv_get(if_bss));
+	ucv_object_add(ucv_prototype_get(global), "bss", ucv_get(bss));
+	ucv_gc(vm);
+}
+
+static uc_value_t *
+uc_hostapd_add_iface(uc_vm_t *vm, size_t nargs)
+{
+	uc_value_t *iface = uc_fn_arg(0);
+	int ret;
+
+	if (ucv_type(iface) != UC_STRING)
+		return ucv_int64_new(-1);
+
+	ret = hostapd_add_iface(interfaces, ucv_string_get(iface));
+	hostapd_ucode_update_interfaces();
+
+	return ucv_int64_new(ret);
+}
+
+static uc_value_t *
+uc_hostapd_remove_iface(uc_vm_t *vm, size_t nargs)
+{
+	uc_value_t *iface = uc_fn_arg(0);
+
+	if (ucv_type(iface) != UC_STRING)
+		return NULL;
+
+	hostapd_remove_iface(interfaces, ucv_string_get(iface));
+	hostapd_ucode_update_interfaces();
+
+	return NULL;
+}
+
+static struct hostapd_vlan *
+bss_conf_find_vlan(struct hostapd_bss_config *bss, int id)
+{
+	struct hostapd_vlan *vlan;
+
+	for (vlan = bss->vlan; vlan; vlan = vlan->next)
+		if (vlan->vlan_id == id)
+			return vlan;
+
+	return NULL;
+}
+
+static int
+bss_conf_rename_vlan(struct hostapd_data *hapd, struct hostapd_vlan *vlan,
+		     const char *ifname)
+{
+	if (!strcmp(ifname, vlan->ifname))
+		return 0;
+
+	hostapd_drv_if_rename(hapd, WPA_IF_AP_VLAN, vlan->ifname, ifname);
+	os_strlcpy(vlan->ifname, ifname, sizeof(vlan->ifname));
+
+	return 0;
+}
+
+static int
+bss_reload_vlans(struct hostapd_data *hapd, struct hostapd_bss_config *bss)
+{
+	struct hostapd_bss_config *old_bss = hapd->conf;
+	struct hostapd_vlan *vlan, *vlan_new, *wildcard;
+	char ifname[IFNAMSIZ + 1], vlan_ifname[IFNAMSIZ + 1], *pos;
+	int ret;
+
+	vlan = bss_conf_find_vlan(old_bss, VLAN_ID_WILDCARD);
+	wildcard = bss_conf_find_vlan(bss, VLAN_ID_WILDCARD);
+	if (!!vlan != !!wildcard)
+		return -1;
+
+	if (vlan && wildcard && strcmp(vlan->ifname, wildcard->ifname) != 0)
+		strcpy(vlan->ifname, wildcard->ifname);
+	else
+		wildcard = NULL;
+
+	for (vlan = bss->vlan; vlan; vlan = vlan->next) {
+		if (vlan->vlan_id == VLAN_ID_WILDCARD ||
+		    vlan->dynamic_vlan > 0)
+			continue;
+
+		if (!bss_conf_find_vlan(old_bss, vlan->vlan_id))
+			return -1;
+	}
+
+	for (vlan = old_bss->vlan; vlan; vlan = vlan->next) {
+		if (vlan->vlan_id == VLAN_ID_WILDCARD)
+			continue;
+
+		if (vlan->dynamic_vlan == 0) {
+			vlan_new = bss_conf_find_vlan(bss, vlan->vlan_id);
+			if (!vlan_new)
+				return -1;
+
+			if (bss_conf_rename_vlan(hapd, vlan, vlan_new->ifname))
+				return -1;
+
+			continue;
+		}
+
+		if (!wildcard)
+			continue;
+
+		os_strlcpy(ifname, wildcard->ifname, sizeof(ifname));
+		pos = os_strchr(ifname, '#');
+		if (!pos)
+			return -1;
+
+		*pos++ = '\0';
+		ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s%d%s",
+				  ifname, vlan->vlan_id, pos);
+	        if (os_snprintf_error(sizeof(vlan_ifname), ret))
+			return -1;
+
+		if (bss_conf_rename_vlan(hapd, vlan, vlan_ifname))
+			return -1;
+	}
+
+	return 0;
+}
+
+static uc_value_t *
+uc_hostapd_bss_set_config(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_data *hapd = uc_fn_thisval("hostapd.bss");
+	struct hostapd_bss_config *old_bss;
+	struct hostapd_iface *iface;
+	struct hostapd_config *conf;
+	uc_value_t *file = uc_fn_arg(0);
+	uc_value_t *index = uc_fn_arg(1);
+	uc_value_t *files_only = uc_fn_arg(2);
+	unsigned int i, idx = 0;
+	int ret = -1;
+
+	if (!hapd || ucv_type(file) != UC_STRING)
+		goto out;
+
+	if (ucv_type(index) == UC_INTEGER)
+		idx = ucv_int64_get(index);
+
+	iface = hapd->iface;
+	conf = interfaces->config_read_cb(ucv_string_get(file));
+	if (!conf)
+		goto out;
+
+	if (idx > conf->num_bss || !conf->bss[idx])
+		goto free;
+
+	if (ucv_boolean_get(files_only)) {
+		struct hostapd_bss_config *bss = conf->bss[idx];
+		struct hostapd_bss_config *old_bss = hapd->conf;
+
+#define swap_field(name)				\
+	do {								\
+		void *ptr = old_bss->name;		\
+		old_bss->name = bss->name;		\
+		bss->name = ptr;				\
+	} while (0)
+
+		swap_field(ssid.wpa_psk_file);
+		ret = bss_reload_vlans(hapd, bss);
+		goto done;
+	}
+
+	hostapd_bss_deinit_no_free(hapd);
+	hostapd_drv_stop_ap(hapd);
+	hostapd_free_hapd_data(hapd);
+
+	old_bss = hapd->conf;
+	for (i = 0; i < iface->conf->num_bss; i++)
+		if (iface->conf->bss[i] == hapd->conf)
+			iface->conf->bss[i] = conf->bss[idx];
+	hapd->conf = conf->bss[idx];
+	conf->bss[idx] = old_bss;
+
+	hostapd_setup_bss(hapd, hapd == iface->bss[0], true);
+	hostapd_ucode_update_interfaces();
+
+done:
+	ret = 0;
+free:
+	hostapd_config_free(conf);
+out:
+	return ucv_int64_new(ret);
+}
+
+static void
+hostapd_remove_iface_bss_conf(struct hostapd_config *iconf,
+			      struct hostapd_bss_config *conf)
+{
+	int i;
+
+	for (i = 0; i < iconf->num_bss; i++)
+		if (iconf->bss[i] == conf)
+			break;
+
+	if (i == iconf->num_bss)
+		return;
+
+	for (i++; i < iconf->num_bss; i++)
+		iconf->bss[i - 1] = iconf->bss[i];
+	iconf->num_bss--;
+}
+
+
+static uc_value_t *
+uc_hostapd_bss_delete(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_data *hapd = uc_fn_thisval("hostapd.bss");
+	struct hostapd_iface *iface;
+	int i, idx;
+
+	if (!hapd)
+		return NULL;
+
+	iface = hapd->iface;
+	if (iface->num_bss == 1) {
+		wpa_printf(MSG_ERROR, "trying to delete last bss of an iface: %s\n", hapd->conf->iface);
+		return NULL;
+	}
+
+	for (idx = 0; idx < iface->num_bss; idx++)
+		if (iface->bss[idx] == hapd)
+			break;
+
+	if (idx == iface->num_bss)
+		return NULL;
+
+	for (i = idx + 1; i < iface->num_bss; i++)
+		iface->bss[i - 1] = iface->bss[i];
+
+	iface->num_bss--;
+
+	iface->bss[0]->interface_added = 0;
+	hostapd_drv_set_first_bss(iface->bss[0]);
+	hapd->interface_added = 1;
+
+	hostapd_drv_stop_ap(hapd);
+	hostapd_bss_deinit(hapd);
+	hostapd_remove_iface_bss_conf(iface->conf, hapd->conf);
+	hostapd_config_free_bss(hapd->conf);
+	os_free(hapd);
+
+	hostapd_ucode_update_interfaces();
+	ucv_gc(vm);
+
+	return NULL;
+}
+
+static uc_value_t *
+uc_hostapd_iface_add_bss(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_iface *iface = uc_fn_thisval("hostapd.iface");
+	struct hostapd_bss_config *bss;
+	struct hostapd_config *conf;
+	struct hostapd_data *hapd;
+	uc_value_t *file = uc_fn_arg(0);
+	uc_value_t *index = uc_fn_arg(1);
+	unsigned int idx = 0;
+	uc_value_t *ret = NULL;
+
+	if (!iface || ucv_type(file) != UC_STRING)
+		goto out;
+
+	if (ucv_type(index) == UC_INTEGER)
+		idx = ucv_int64_get(index);
+
+	conf = interfaces->config_read_cb(ucv_string_get(file));
+	if (!conf || idx > conf->num_bss || !conf->bss[idx])
+		goto out;
+
+	bss = conf->bss[idx];
+	hapd = hostapd_alloc_bss_data(iface, iface->conf, bss);
+	if (!hapd)
+		goto out;
+
+	hapd->driver = iface->bss[0]->driver;
+	hapd->drv_priv = iface->bss[0]->drv_priv;
+	if (interfaces->ctrl_iface_init &&
+	    interfaces->ctrl_iface_init(hapd) < 0)
+		goto free_hapd;
+
+	if (iface->state == HAPD_IFACE_ENABLED &&
+	    hostapd_setup_bss(hapd, -1, true))
+		goto deinit_ctrl;
+
+	iface->bss = os_realloc_array(iface->bss, iface->num_bss + 1,
+				      sizeof(*iface->bss));
+	iface->bss[iface->num_bss++] = hapd;
+
+	iface->conf->bss = os_realloc_array(iface->conf->bss,
+					    iface->conf->num_bss + 1,
+					    sizeof(*iface->conf->bss));
+	iface->conf->bss[iface->conf->num_bss] = bss;
+	conf->bss[idx] = NULL;
+	ret = hostapd_ucode_bss_get_uval(hapd);
+	hostapd_ucode_update_interfaces();
+	goto out;
+
+deinit_ctrl:
+	if (interfaces->ctrl_iface_deinit)
+		interfaces->ctrl_iface_deinit(hapd);
+free_hapd:
+	hostapd_free_hapd_data(hapd);
+	os_free(hapd);
+out:
+	hostapd_config_free(conf);
+	return ret;
+}
+
+static uc_value_t *
+uc_hostapd_iface_set_bss_order(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_iface *iface = uc_fn_thisval("hostapd.iface");
+	uc_value_t *bss_list = uc_fn_arg(0);
+	struct hostapd_data **new_bss;
+	struct hostapd_bss_config **new_conf;
+
+	if (!iface)
+		return NULL;
+
+	if (ucv_type(bss_list) != UC_ARRAY ||
+	    ucv_array_length(bss_list) != iface->num_bss)
+		return NULL;
+
+	new_bss = calloc(iface->num_bss, sizeof(*new_bss));
+	new_conf = calloc(iface->num_bss, sizeof(*new_conf));
+	for (size_t i = 0; i < iface->num_bss; i++) {
+		struct hostapd_data *bss;
+
+		bss = ucv_resource_data(ucv_array_get(bss_list, i), "hostapd.bss");
+		if (bss->iface != iface)
+			goto free;
+
+		for (size_t k = 0; k < i; k++)
+			if (new_bss[k] == bss)
+				goto free;
+
+		new_bss[i] = bss;
+		new_conf[i] = bss->conf;
+	}
+
+	new_bss[0]->interface_added = 0;
+	for (size_t i = 1; i < iface->num_bss; i++)
+		new_bss[i]->interface_added = 1;
+
+	free(iface->bss);
+	iface->bss = new_bss;
+
+	free(iface->conf->bss);
+	iface->conf->bss = new_conf;
+	iface->conf->num_bss = iface->num_bss;
+	hostapd_drv_set_first_bss(iface->bss[0]);
+
+	return ucv_boolean_new(true);
+
+free:
+	free(new_bss);
+	free(new_conf);
+	return NULL;
+}
+
+static uc_value_t *
+uc_hostapd_bss_ctrl(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_data *hapd = uc_fn_thisval("hostapd.bss");
+	uc_value_t *arg = uc_fn_arg(0);
+	struct sockaddr_storage from = {};
+	static char reply[4096];
+	int reply_len;
+
+	if (!hapd || !interfaces->ctrl_iface_recv ||
+	    ucv_type(arg) != UC_STRING)
+		return NULL;
+
+	reply_len = interfaces->ctrl_iface_recv(hapd, ucv_string_get(arg),
+						reply, sizeof(reply),
+						&from, sizeof(from));
+	if (reply_len < 0)
+		return NULL;
+
+	if (reply_len && reply[reply_len - 1] == '\n')
+		reply_len--;
+
+	return ucv_string_new_length(reply, reply_len);
+}
+
+static uc_value_t *
+uc_hostapd_iface_stop(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_iface *iface = uc_fn_thisval("hostapd.iface");
+	int i;
+
+	switch (iface->state) {
+	case HAPD_IFACE_ENABLED:
+	case HAPD_IFACE_DISABLED:
+		break;
+#ifdef CONFIG_ACS
+	case HAPD_IFACE_ACS:
+		acs_cleanup(iface);
+		iface->scan_cb = NULL;
+		/* fallthrough */
+#endif
+	default:
+		hostapd_disable_iface(iface);
+		break;
+	}
+
+	if (iface->state != HAPD_IFACE_ENABLED)
+		hostapd_disable_iface(iface);
+
+	for (i = 0; i < iface->num_bss; i++) {
+		struct hostapd_data *hapd = iface->bss[i];
+
+		hostapd_drv_stop_ap(hapd);
+		hapd->beacon_set_done = 0;
+	}
+
+	return NULL;
+}
+
+static uc_value_t *
+uc_hostapd_iface_start(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_iface *iface = uc_fn_thisval("hostapd.iface");
+	uc_value_t *info = uc_fn_arg(0);
+	struct hostapd_config *conf;
+	bool changed = false;
+	uint64_t intval;
+	int i;
+
+	if (!iface)
+		return NULL;
+
+	if (!info) {
+		iface->freq = 0;
+		goto out;
+	}
+
+	if (ucv_type(info) != UC_OBJECT)
+		return NULL;
+
+#define UPDATE_VAL(field, name)							\
+	if ((intval = ucv_int64_get(ucv_object_get(info, name, NULL))) &&	\
+		!errno && intval != conf->field) do {				\
+		conf->field = intval;						\
+		changed = true;							\
+	} while(0)
+
+	conf = iface->conf;
+	UPDATE_VAL(op_class, "op_class");
+	UPDATE_VAL(hw_mode, "hw_mode");
+	UPDATE_VAL(channel, "channel");
+	UPDATE_VAL(secondary_channel, "sec_channel");
+	if (!changed &&
+	    (iface->bss[0]->beacon_set_done ||
+	     iface->state == HAPD_IFACE_DFS))
+		return ucv_boolean_new(true);
+
+	intval = ucv_int64_get(ucv_object_get(info, "center_seg0_idx", NULL));
+	if (!errno)
+		hostapd_set_oper_centr_freq_seg0_idx(conf, intval);
+
+	intval = ucv_int64_get(ucv_object_get(info, "center_seg1_idx", NULL));
+	if (!errno)
+		hostapd_set_oper_centr_freq_seg1_idx(conf, intval);
+
+	intval = ucv_int64_get(ucv_object_get(info, "oper_chwidth", NULL));
+	if (!errno)
+		hostapd_set_oper_chwidth(conf, intval);
+
+	intval = ucv_int64_get(ucv_object_get(info, "frequency", NULL));
+	if (!errno)
+		iface->freq = intval;
+	else
+		iface->freq = 0;
+	conf->acs = 0;
+
+out:
+	switch (iface->state) {
+	case HAPD_IFACE_DISABLED:
+		break;
+	case HAPD_IFACE_ENABLED:
+		if (!hostapd_is_dfs_required(iface) ||
+			hostapd_is_dfs_chan_available(iface))
+			break;
+		wpa_printf(MSG_INFO, "DFS CAC required on new channel, restart interface");
+		/* fallthrough */
+	default:
+		hostapd_disable_iface(iface);
+		break;
+	}
+
+	if (conf->channel && !iface->freq)
+		iface->freq = hostapd_hw_get_freq(iface->bss[0], conf->channel);
+
+	if (iface->state != HAPD_IFACE_ENABLED) {
+		hostapd_enable_iface(iface);
+		return ucv_boolean_new(true);
+	}
+
+	for (i = 0; i < iface->num_bss; i++) {
+		struct hostapd_data *hapd = iface->bss[i];
+		int ret;
+
+		hapd->conf->start_disabled = 0;
+		hostapd_set_freq(hapd, conf->hw_mode, iface->freq,
+				 conf->channel,
+				 conf->enable_edmg,
+				 conf->edmg_channel,
+				 conf->ieee80211n,
+				 conf->ieee80211ac,
+				 conf->ieee80211ax,
+				 conf->ieee80211be,
+				 conf->secondary_channel,
+				 hostapd_get_oper_chwidth(conf),
+				 hostapd_get_oper_centr_freq_seg0_idx(conf),
+				 hostapd_get_oper_centr_freq_seg1_idx(conf));
+
+		ieee802_11_set_beacon(hapd);
+	}
+
+	return ucv_boolean_new(true);
+}
+
+static uc_value_t *
+uc_hostapd_iface_switch_channel(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_iface *iface = uc_fn_thisval("hostapd.iface");
+	uc_value_t *info = uc_fn_arg(0);
+	struct hostapd_config *conf;
+	struct csa_settings csa = {};
+	uint64_t intval;
+	int i, ret = 0;
+
+	if (!iface || ucv_type(info) != UC_OBJECT)
+		return NULL;
+
+	conf = iface->conf;
+	if ((intval = ucv_int64_get(ucv_object_get(info, "csa_count", NULL))) && !errno)
+		csa.cs_count = intval;
+	if ((intval = ucv_int64_get(ucv_object_get(info, "sec_channel", NULL))) && !errno)
+		csa.freq_params.sec_channel_offset = intval;
+
+	csa.freq_params.ht_enabled = conf->ieee80211n;
+	csa.freq_params.vht_enabled = conf->ieee80211ac;
+	csa.freq_params.he_enabled = conf->ieee80211ax;
+#ifdef CONFIG_IEEE80211BE
+	csa.freq_params.eht_enabled = conf->ieee80211be;
+#endif
+	intval = ucv_int64_get(ucv_object_get(info, "oper_chwidth", NULL));
+	if (errno)
+		intval = hostapd_get_oper_chwidth(conf);
+	if (intval)
+		csa.freq_params.bandwidth = 40 << intval;
+	else
+		csa.freq_params.bandwidth = csa.freq_params.sec_channel_offset ? 40 : 20;
+
+	if ((intval = ucv_int64_get(ucv_object_get(info, "frequency", NULL))) && !errno)
+		csa.freq_params.freq = intval;
+	if ((intval = ucv_int64_get(ucv_object_get(info, "center_freq1", NULL))) && !errno)
+		csa.freq_params.center_freq1 = intval;
+	if ((intval = ucv_int64_get(ucv_object_get(info, "center_freq2", NULL))) && !errno)
+		csa.freq_params.center_freq2 = intval;
+
+	for (i = 0; i < iface->num_bss; i++)
+		ret = hostapd_switch_channel(iface->bss[i], &csa);
+
+	return ucv_boolean_new(!ret);
+}
+
+static uc_value_t *
+uc_hostapd_bss_rename(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_data *hapd = uc_fn_thisval("hostapd.bss");
+	uc_value_t *ifname_arg = uc_fn_arg(0);
+	char prev_ifname[IFNAMSIZ + 1];
+	struct sta_info *sta;
+	const char *ifname;
+	int ret;
+
+	if (!hapd || ucv_type(ifname_arg) != UC_STRING)
+		return NULL;
+
+	os_strlcpy(prev_ifname, hapd->conf->iface, sizeof(prev_ifname));
+	ifname = ucv_string_get(ifname_arg);
+
+	hostapd_ubus_free_bss(hapd);
+	if (interfaces->ctrl_iface_deinit)
+		interfaces->ctrl_iface_deinit(hapd);
+
+	ret = hostapd_drv_if_rename(hapd, WPA_IF_AP_BSS, NULL, ifname);
+	if (ret)
+		goto out;
+
+	for (sta = hapd->sta_list; sta; sta = sta->next) {
+		char cur_name[IFNAMSIZ + 1], new_name[IFNAMSIZ + 1];
+
+		if (!(sta->flags & WLAN_STA_WDS) || sta->pending_wds_enable)
+			continue;
+
+		snprintf(cur_name, sizeof(cur_name), "%s.sta%d", prev_ifname, sta->aid);
+		snprintf(new_name, sizeof(new_name), "%s.sta%d", ifname, sta->aid);
+		hostapd_drv_if_rename(hapd, WPA_IF_AP_VLAN, cur_name, new_name);
+	}
+
+	if (!strncmp(hapd->conf->ssid.vlan, hapd->conf->iface, sizeof(hapd->conf->ssid.vlan)))
+		os_strlcpy(hapd->conf->ssid.vlan, ifname, sizeof(hapd->conf->ssid.vlan));
+	os_strlcpy(hapd->conf->iface, ifname, sizeof(hapd->conf->iface));
+	hostapd_ubus_add_bss(hapd);
+
+	hostapd_ucode_update_interfaces();
+out:
+	if (interfaces->ctrl_iface_init)
+		interfaces->ctrl_iface_init(hapd);
+
+	return ret ? NULL : ucv_boolean_new(true);
+}
+
+
+int hostapd_ucode_init(struct hapd_interfaces *ifaces)
+{
+	static const uc_function_list_t global_fns[] = {
+		{ "printf",	uc_wpa_printf },
+		{ "getpid", uc_wpa_getpid },
+		{ "sha1", uc_wpa_sha1 },
+		{ "freq_info", uc_wpa_freq_info },
+		{ "add_iface", uc_hostapd_add_iface },
+		{ "remove_iface", uc_hostapd_remove_iface },
+	};
+	static const uc_function_list_t bss_fns[] = {
+		{ "ctrl", uc_hostapd_bss_ctrl },
+		{ "set_config", uc_hostapd_bss_set_config },
+		{ "rename", uc_hostapd_bss_rename },
+		{ "delete", uc_hostapd_bss_delete },
+	};
+	static const uc_function_list_t iface_fns[] = {
+		{ "set_bss_order", uc_hostapd_iface_set_bss_order },
+		{ "add_bss", uc_hostapd_iface_add_bss },
+		{ "stop", uc_hostapd_iface_stop },
+		{ "start", uc_hostapd_iface_start },
+		{ "switch_channel", uc_hostapd_iface_switch_channel },
+	};
+	uc_value_t *data, *proto;
+
+	interfaces = ifaces;
+	vm = wpa_ucode_create_vm();
+
+	global_type = uc_type_declare(vm, "hostapd.global", global_fns, NULL);
+	bss_type = uc_type_declare(vm, "hostapd.bss", bss_fns, NULL);
+	iface_type = uc_type_declare(vm, "hostapd.iface", iface_fns, NULL);
+
+	bss_registry = ucv_array_new(vm);
+	uc_vm_registry_set(vm, "hostap.bss_registry", bss_registry);
+
+	iface_registry = ucv_array_new(vm);
+	uc_vm_registry_set(vm, "hostap.iface_registry", iface_registry);
+
+	global = wpa_ucode_global_init("hostapd", global_type);
+
+	if (wpa_ucode_run(HOSTAPD_UC_PATH "hostapd.uc"))
+		goto free_vm;
+	ucv_gc(vm);
+
+	return 0;
+
+free_vm:
+	wpa_ucode_free_vm();
+	return -1;
+}
+
+void hostapd_ucode_free(void)
+{
+	if (wpa_ucode_call_prepare("shutdown") == 0)
+		ucv_put(wpa_ucode_call(0));
+	wpa_ucode_free_vm();
+}
+
+void hostapd_ucode_free_iface(struct hostapd_iface *iface)
+{
+	wpa_ucode_registry_remove(iface_registry, iface->ucode.idx);
+}
+
+void hostapd_ucode_add_bss(struct hostapd_data *hapd)
+{
+	uc_value_t *val;
+
+	if (wpa_ucode_call_prepare("bss_add"))
+		return;
+
+	val = hostapd_ucode_bss_get_uval(hapd);
+	uc_value_push(ucv_get(ucv_string_new(hapd->conf->iface)));
+	uc_value_push(ucv_get(val));
+	ucv_put(wpa_ucode_call(2));
+	ucv_gc(vm);
+}
+
+void hostapd_ucode_reload_bss(struct hostapd_data *hapd)
+{
+	uc_value_t *val;
+
+	if (wpa_ucode_call_prepare("bss_reload"))
+		return;
+
+	val = hostapd_ucode_bss_get_uval(hapd);
+	uc_value_push(ucv_get(ucv_string_new(hapd->conf->iface)));
+	uc_value_push(ucv_get(val));
+	ucv_put(wpa_ucode_call(2));
+	ucv_gc(vm);
+}
+
+void hostapd_ucode_free_bss(struct hostapd_data *hapd)
+{
+	uc_value_t *val;
+
+	val = wpa_ucode_registry_remove(bss_registry, hapd->ucode.idx);
+	if (!val)
+		return;
+
+	hapd->ucode.idx = 0;
+	if (wpa_ucode_call_prepare("bss_remove"))
+		return;
+
+	uc_value_push(ucv_string_new(hapd->conf->iface));
+	uc_value_push(ucv_get(val));
+	ucv_put(wpa_ucode_call(2));
+	ucv_gc(vm);
+}
diff --git a/package/network/services/hostapd/src/src/ap/ucode.h b/package/network/services/hostapd/src/src/ap/ucode.h
new file mode 100644
index 0000000000..d00b787169
--- /dev/null
+++ b/package/network/services/hostapd/src/src/ap/ucode.h
@@ -0,0 +1,54 @@
+#ifndef __HOSTAPD_AP_UCODE_H
+#define __HOSTAPD_AP_UCODE_H
+
+#include "utils/ucode.h"
+
+struct hostapd_data;
+
+struct hostapd_ucode_bss {
+#ifdef UCODE_SUPPORT
+	int idx;
+#endif
+};
+
+struct hostapd_ucode_iface {
+#ifdef UCODE_SUPPORT
+	int idx;
+#endif
+};
+
+#ifdef UCODE_SUPPORT
+
+int hostapd_ucode_init(struct hapd_interfaces *ifaces);
+
+void hostapd_ucode_free(void);
+void hostapd_ucode_free_iface(struct hostapd_iface *iface);
+void hostapd_ucode_add_bss(struct hostapd_data *hapd);
+void hostapd_ucode_free_bss(struct hostapd_data *hapd);
+void hostapd_ucode_reload_bss(struct hostapd_data *hapd);
+
+#else
+
+static inline int hostapd_ucode_init(struct hapd_interfaces *ifaces)
+{
+	return -EINVAL;
+}
+static inline void hostapd_ucode_free(void)
+{
+}
+static inline void hostapd_ucode_free_iface(struct hostapd_iface *iface)
+{
+}
+static inline void hostapd_ucode_reload_bss(struct hostapd_data *hapd)
+{
+}
+static inline void hostapd_ucode_add_bss(struct hostapd_data *hapd)
+{
+}
+static inline void hostapd_ucode_free_bss(struct hostapd_data *hapd)
+{
+}
+
+#endif
+
+#endif
diff --git a/package/network/services/hostapd/src/src/utils/ucode.c b/package/network/services/hostapd/src/src/utils/ucode.c
new file mode 100644
index 0000000000..2beeb9a7ff
--- /dev/null
+++ b/package/network/services/hostapd/src/src/utils/ucode.c
@@ -0,0 +1,335 @@
+#include <unistd.h>
+#include "ucode.h"
+#include "utils/eloop.h"
+#include "crypto/crypto.h"
+#include "crypto/sha1.h"
+#include "common/ieee802_11_common.h"
+#include <libubox/uloop.h>
+#include <ucode/compiler.h>
+
+static uc_value_t *registry;
+static uc_vm_t vm;
+static struct uloop_timeout gc_timer;
+
+static void uc_gc_timer(struct uloop_timeout *timeout)
+{
+	ucv_gc(&vm);
+}
+
+uc_value_t *uc_wpa_printf(uc_vm_t *vm, size_t nargs)
+{
+	uc_value_t *level = uc_fn_arg(0);
+	uc_value_t *ret, **args;
+	uc_cfn_ptr_t _sprintf;
+	int l = MSG_INFO;
+	int i, start = 0;
+
+	_sprintf = uc_stdlib_function("sprintf");
+	if (!sprintf)
+		return NULL;
+
+	if (ucv_type(level) == UC_INTEGER) {
+		l = ucv_int64_get(level);
+		start++;
+	}
+
+	if (nargs <= start)
+		return NULL;
+
+	ret = _sprintf(vm, nargs - start);
+	if (ucv_type(ret) != UC_STRING)
+		return NULL;
+
+	wpa_printf(l, "%s", ucv_string_get(ret));
+	ucv_put(ret);
+
+	return NULL;
+}
+
+uc_value_t *uc_wpa_freq_info(uc_vm_t *vm, size_t nargs)
+{
+	uc_value_t *freq = uc_fn_arg(0);
+	uc_value_t *sec = uc_fn_arg(1);
+	int width = ucv_uint64_get(uc_fn_arg(2));
+	int freq_val, center_idx, center_ofs;
+	enum oper_chan_width chanwidth;
+	enum hostapd_hw_mode hw_mode;
+	u8 op_class, channel, tmp_channel;
+	const char *modestr;
+	int sec_channel = 0;
+	uc_value_t *ret;
+
+	if (ucv_type(freq) != UC_INTEGER)
+		return NULL;
+
+	freq_val = ucv_int64_get(freq);
+	if (ucv_type(sec) == UC_INTEGER)
+		sec_channel = ucv_int64_get(sec);
+	else if (sec)
+		return NULL;
+	else if (freq_val > 4000)
+		sec_channel = (freq_val / 20) & 1 ? 1 : -1;
+	else
+		sec_channel = freq_val < 2442 ? 1 : -1;
+
+	if (sec_channel != -1 && sec_channel != 1 && sec_channel != 0)
+		return NULL;
+
+	switch (width) {
+	case 0:
+		chanwidth = CONF_OPER_CHWIDTH_USE_HT;
+		break;
+	case 1:
+		chanwidth = CONF_OPER_CHWIDTH_80MHZ;
+		break;
+	case 2:
+		chanwidth = CONF_OPER_CHWIDTH_160MHZ;
+		break;
+	default:
+		return NULL;
+	}
+
+	hw_mode = ieee80211_freq_to_channel_ext(freq_val, sec_channel,
+						chanwidth, &op_class, &channel);
+	switch (hw_mode) {
+	case HOSTAPD_MODE_IEEE80211B:
+		modestr = "b";
+		break;
+	case HOSTAPD_MODE_IEEE80211G:
+		modestr = "g";
+		break;
+	case HOSTAPD_MODE_IEEE80211A:
+		modestr = "a";
+		break;
+	case HOSTAPD_MODE_IEEE80211AD:
+		modestr = "ad";
+		break;
+	default:
+		return NULL;
+	}
+
+	ret = ucv_object_new(vm);
+	ucv_object_add(ret, "op_class", ucv_int64_new(op_class));
+	ucv_object_add(ret, "channel", ucv_int64_new(channel));
+	ucv_object_add(ret, "hw_mode", ucv_int64_new(hw_mode));
+	ucv_object_add(ret, "hw_mode_str", ucv_get(ucv_string_new(modestr)));
+	ucv_object_add(ret, "sec_channel", ucv_int64_new(sec_channel));
+	ucv_object_add(ret, "frequency", ucv_int64_new(freq_val));
+
+	if (!sec_channel)
+		return ret;
+
+	if (freq_val >= 5900)
+		center_ofs = 0;
+	else if (freq_val >= 5745)
+		center_ofs = 20;
+	else
+		center_ofs = 35;
+	tmp_channel = channel - center_ofs;
+	tmp_channel &= ~((8 << width) - 1);
+	center_idx = tmp_channel + center_ofs + (4 << width) - 1;
+
+	if (freq_val < 3000)
+		ucv_object_add(ret, "center_seg0_idx", ucv_int64_new(0));
+	else
+		ucv_object_add(ret, "center_seg0_idx", ucv_int64_new(center_idx));
+	center_idx = (center_idx - channel) * 5 + freq_val;
+	ucv_object_add(ret, "center_freq1", ucv_int64_new(center_idx));
+
+out:
+	return ret;
+}
+
+uc_value_t *uc_wpa_getpid(uc_vm_t *vm, size_t nargs)
+{
+	return ucv_int64_new(getpid());
+}
+
+uc_value_t *uc_wpa_sha1(uc_vm_t *vm, size_t nargs)
+{
+	u8 hash[SHA1_MAC_LEN];
+	char hash_hex[2 * ARRAY_SIZE(hash) + 1];
+	uc_value_t *val;
+	size_t *lens;
+	const u8 **args;
+	int i;
+
+	if (!nargs)
+		return NULL;
+
+	args = alloca(nargs * sizeof(*args));
+	lens = alloca(nargs * sizeof(*lens));
+	for (i = 0; i < nargs; i++) {
+		val = uc_fn_arg(i);
+		if (ucv_type(val) != UC_STRING)
+			return NULL;
+
+		args[i] = ucv_string_get(val);
+		lens[i] = ucv_string_length(val);
+	}
+
+	if (sha1_vector(nargs, args, lens, hash))
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(hash); i++)
+		sprintf(hash_hex + 2 * i, "%02x", hash[i]);
+
+	return ucv_string_new_length(hash_hex, 2 * ARRAY_SIZE(hash));
+}
+
+uc_vm_t *wpa_ucode_create_vm(void)
+{
+	static uc_parse_config_t config = {
+		.strict_declarations = true,
+		.lstrip_blocks = true,
+		.trim_blocks = true,
+		.raw_mode = true
+	};
+
+	uc_search_path_init(&config.module_search_path);
+	uc_search_path_add(&config.module_search_path, HOSTAPD_UC_PATH "*.so");
+	uc_search_path_add(&config.module_search_path, HOSTAPD_UC_PATH "*.uc");
+
+	uc_vm_init(&vm, &config);
+
+	uc_stdlib_load(uc_vm_scope_get(&vm));
+	eloop_add_uloop();
+	gc_timer.cb = uc_gc_timer;
+
+	return &vm;
+}
+
+int wpa_ucode_run(const char *script)
+{
+	uc_source_t *source;
+	uc_program_t *prog;
+	uc_value_t *ops;
+	char *err;
+	int ret;
+
+	source = uc_source_new_file(script);
+	if (!source)
+		return -1;
+
+	prog = uc_compile(vm.config, source, &err);
+	uc_source_put(source);
+	if (!prog) {
+		wpa_printf(MSG_ERROR, "Error loading ucode: %s\n", err);
+		return -1;
+	}
+
+	ret = uc_vm_execute(&vm, prog, &ops);
+	uc_program_put(prog);
+	if (ret || !ops)
+		return -1;
+
+	registry = ucv_array_new(&vm);
+	uc_vm_registry_set(&vm, "hostap.registry", registry);
+	ucv_array_set(registry, 0, ucv_get(ops));
+
+	return 0;
+}
+
+int wpa_ucode_call_prepare(const char *fname)
+{
+	uc_value_t *obj, *func;
+
+	if (!registry)
+		return -1;
+
+	obj = ucv_array_get(registry, 0);
+	if (!obj)
+		return -1;
+
+	func = ucv_object_get(obj, fname, NULL);
+	if (!ucv_is_callable(func))
+		return -1;
+
+	uc_vm_stack_push(&vm, ucv_get(obj));
+	uc_vm_stack_push(&vm, ucv_get(func));
+
+	return 0;
+}
+
+uc_value_t *wpa_ucode_global_init(const char *name, uc_resource_type_t *global_type)
+{
+	uc_value_t *global = uc_resource_new(global_type, NULL);
+	uc_value_t *proto;
+
+	uc_vm_registry_set(&vm, "hostap.global", global);
+	proto = ucv_prototype_get(global);
+	ucv_object_add(proto, "data", ucv_get(ucv_object_new(&vm)));
+
+#define ADD_CONST(x) ucv_object_add(proto, #x, ucv_int64_new(x))
+	ADD_CONST(MSG_EXCESSIVE);
+	ADD_CONST(MSG_MSGDUMP);
+	ADD_CONST(MSG_DEBUG);
+	ADD_CONST(MSG_INFO);
+	ADD_CONST(MSG_WARNING);
+	ADD_CONST(MSG_ERROR);
+#undef ADD_CONST
+
+	ucv_object_add(uc_vm_scope_get(&vm), name, ucv_get(global));
+
+	return global;
+}
+
+int wpa_ucode_registry_add(uc_value_t *reg, uc_value_t *val)
+{
+	uc_value_t *data;
+	int i = 0;
+
+	while (ucv_array_get(reg, i))
+		i++;
+
+	ucv_array_set(reg, i, ucv_get(val));
+
+	return i + 1;
+}
+
+uc_value_t *wpa_ucode_registry_get(uc_value_t *reg, int idx)
+{
+	if (!idx)
+		return NULL;
+
+	return ucv_array_get(reg, idx - 1);
+}
+
+uc_value_t *wpa_ucode_registry_remove(uc_value_t *reg, int idx)
+{
+	uc_value_t *val = wpa_ucode_registry_get(reg, idx);
+	void **dataptr;
+
+	if (!val)
+		return NULL;
+
+	ucv_array_set(reg, idx - 1, NULL);
+	dataptr = ucv_resource_dataptr(val, NULL);
+	if (dataptr)
+		*dataptr = NULL;
+
+	return val;
+}
+
+
+uc_value_t *wpa_ucode_call(size_t nargs)
+{
+	if (uc_vm_call(&vm, true, nargs) != EXCEPTION_NONE)
+		return NULL;
+
+	if (!gc_timer.pending)
+		uloop_timeout_set(&gc_timer, 10);
+
+	return uc_vm_stack_pop(&vm);
+}
+
+void wpa_ucode_free_vm(void)
+{
+	if (!vm.config)
+		return;
+
+	uc_search_path_free(&vm.config->module_search_path);
+	uc_vm_free(&vm);
+	registry = NULL;
+	vm = (uc_vm_t){};
+}
diff --git a/package/network/services/hostapd/src/src/utils/ucode.h b/package/network/services/hostapd/src/src/utils/ucode.h
new file mode 100644
index 0000000000..2c1886976e
--- /dev/null
+++ b/package/network/services/hostapd/src/src/utils/ucode.h
@@ -0,0 +1,29 @@
+#ifndef __HOSTAPD_UTILS_UCODE_H
+#define __HOSTAPD_UTILS_UCODE_H
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include <ucode/lib.h>
+#include <ucode/vm.h>
+
+#define HOSTAPD_UC_PATH	"/usr/share/hostap/"
+
+extern uc_value_t *uc_registry;
+uc_vm_t *wpa_ucode_create_vm(void);
+int wpa_ucode_run(const char *script);
+int wpa_ucode_call_prepare(const char *fname);
+uc_value_t *wpa_ucode_call(size_t nargs);
+void wpa_ucode_free_vm(void);
+
+uc_value_t *wpa_ucode_global_init(const char *name, uc_resource_type_t *global_type);
+
+int wpa_ucode_registry_add(uc_value_t *reg, uc_value_t *val);
+uc_value_t *wpa_ucode_registry_get(uc_value_t *reg, int idx);
+uc_value_t *wpa_ucode_registry_remove(uc_value_t *reg, int idx);
+
+uc_value_t *uc_wpa_printf(uc_vm_t *vm, size_t nargs);
+uc_value_t *uc_wpa_getpid(uc_vm_t *vm, size_t nargs);
+uc_value_t *uc_wpa_sha1(uc_vm_t *vm, size_t nargs);
+uc_value_t *uc_wpa_freq_info(uc_vm_t *vm, size_t nargs);
+
+#endif
diff --git a/package/network/services/hostapd/src/wpa_supplicant/ubus.c b/package/network/services/hostapd/src/wpa_supplicant/ubus.c
index 16a68c5073..1c477f0c0c 100644
--- a/package/network/services/hostapd/src/wpa_supplicant/ubus.c
+++ b/package/network/services/hostapd/src/wpa_supplicant/ubus.c
@@ -30,12 +30,6 @@ static inline struct wpa_supplicant *get_wpas_from_object(struct ubus_object *ob
 	return container_of(obj, struct wpa_supplicant, ubus.obj);
 }
 
-static void ubus_receive(int sock, void *eloop_ctx, void *sock_ctx)
-{
-	struct ubus_context *ctx = eloop_ctx;
-	ubus_handle_event(ctx);
-}
-
 static void ubus_reconnect_timeout(void *eloop_data, void *user_ctx)
 {
 	if (ubus_reconnect(ctx, NULL)) {
@@ -43,12 +37,12 @@ static void ubus_reconnect_timeout(void *eloop_data, void *user_ctx)
 		return;
 	}
 
-	eloop_register_read_sock(ctx->sock.fd, ubus_receive, ctx, NULL);
+	ubus_add_uloop(ctx);
 }
 
 static void wpas_ubus_connection_lost(struct ubus_context *ctx)
 {
-	eloop_unregister_read_sock(ctx->sock.fd);
+	uloop_fd_delete(&ctx->sock);
 	eloop_register_timeout(1, 0, ubus_reconnect_timeout, ctx, NULL);
 }
 
@@ -57,12 +51,14 @@ static bool wpas_ubus_init(void)
 	if (ctx)
 		return true;
 
+	eloop_add_uloop();
 	ctx = ubus_connect(NULL);
 	if (!ctx)
 		return false;
 
 	ctx->connection_lost = wpas_ubus_connection_lost;
-	eloop_register_read_sock(ctx->sock.fd, ubus_receive, ctx, NULL);
+	ubus_add_uloop(ctx);
+
 	return true;
 }
 
@@ -80,7 +76,7 @@ static void wpas_ubus_ref_dec(void)
 	if (ctx_ref)
 		return;
 
-	eloop_unregister_read_sock(ctx->sock.fd);
+	uloop_fd_delete(&ctx->sock);
 	ubus_free(ctx);
 	ctx = NULL;
 }
@@ -211,152 +207,6 @@ void wpas_ubus_free_bss(struct wpa_supplicant *wpa_s)
 	free(name);
 }
 
-enum {
-	WPAS_CONFIG_DRIVER,
-	WPAS_CONFIG_IFACE,
-	WPAS_CONFIG_BRIDGE,
-	WPAS_CONFIG_HOSTAPD_CTRL,
-	WPAS_CONFIG_CTRL,
-	WPAS_CONFIG_FILE,
-	__WPAS_CONFIG_MAX
-};
-
-static const struct blobmsg_policy wpas_config_add_policy[__WPAS_CONFIG_MAX] = {
-	[WPAS_CONFIG_DRIVER] = { "driver", BLOBMSG_TYPE_STRING },
-	[WPAS_CONFIG_IFACE] = { "iface", BLOBMSG_TYPE_STRING },
-	[WPAS_CONFIG_BRIDGE] = { "bridge", BLOBMSG_TYPE_STRING },
-	[WPAS_CONFIG_HOSTAPD_CTRL] = { "hostapd_ctrl", BLOBMSG_TYPE_STRING },
-	[WPAS_CONFIG_CTRL] = { "ctrl", BLOBMSG_TYPE_STRING },
-	[WPAS_CONFIG_FILE] = { "config", BLOBMSG_TYPE_STRING },
-};
-
-static int
-wpas_config_add(struct ubus_context *ctx, struct ubus_object *obj,
-		struct ubus_request_data *req, const char *method,
-		struct blob_attr *msg)
-{
-	struct blob_attr *tb[__WPAS_CONFIG_MAX];
-	struct wpa_global *global = get_wpa_global_from_object(obj);
-	struct wpa_interface *iface;
-
-	blobmsg_parse(wpas_config_add_policy, __WPAS_CONFIG_MAX, tb, blob_data(msg), blob_len(msg));
-
-	if (!tb[WPAS_CONFIG_FILE] || !tb[WPAS_CONFIG_IFACE] || !tb[WPAS_CONFIG_DRIVER])
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	iface = os_zalloc(sizeof(struct wpa_interface));
-	if (iface == NULL)
-		return UBUS_STATUS_UNKNOWN_ERROR;
-
-	iface->driver = blobmsg_get_string(tb[WPAS_CONFIG_DRIVER]);
-	iface->ifname = blobmsg_get_string(tb[WPAS_CONFIG_IFACE]);
-	iface->confname = blobmsg_get_string(tb[WPAS_CONFIG_FILE]);
-
-	if (tb[WPAS_CONFIG_BRIDGE])
-		iface->bridge_ifname = blobmsg_get_string(tb[WPAS_CONFIG_BRIDGE]);
-
-	if (tb[WPAS_CONFIG_CTRL])
-		iface->ctrl_interface = blobmsg_get_string(tb[WPAS_CONFIG_CTRL]);
-
-	if (tb[WPAS_CONFIG_HOSTAPD_CTRL])
-		iface->hostapd_ctrl = blobmsg_get_string(tb[WPAS_CONFIG_HOSTAPD_CTRL]);
-
-	if (!wpa_supplicant_add_iface(global, iface, NULL))
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	blob_buf_init(&b, 0);
-	blobmsg_add_u32(&b, "pid", getpid());
-	ubus_send_reply(ctx, req, b.head);
-
-	return UBUS_STATUS_OK;
-}
-
-enum {
-	WPAS_CONFIG_REM_IFACE,
-	__WPAS_CONFIG_REM_MAX
-};
-
-static const struct blobmsg_policy wpas_config_remove_policy[__WPAS_CONFIG_REM_MAX] = {
-	[WPAS_CONFIG_REM_IFACE] = { "iface", BLOBMSG_TYPE_STRING },
-};
-
-static int
-wpas_config_remove(struct ubus_context *ctx, struct ubus_object *obj,
-		   struct ubus_request_data *req, const char *method,
-		   struct blob_attr *msg)
-{
-	struct blob_attr *tb[__WPAS_CONFIG_REM_MAX];
-	struct wpa_global *global = get_wpa_global_from_object(obj);
-	struct wpa_supplicant *wpa_s = NULL;
-	unsigned int found = 0;
-
-	blobmsg_parse(wpas_config_remove_policy, __WPAS_CONFIG_REM_MAX, tb, blob_data(msg), blob_len(msg));
-
-	if (!tb[WPAS_CONFIG_REM_IFACE])
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	/* find wpa_s object for to-be-removed interface */
-	for (wpa_s = global->ifaces; wpa_s; wpa_s = wpa_s->next) {
-		if (!strncmp(wpa_s->ifname,
-			     blobmsg_get_string(tb[WPAS_CONFIG_REM_IFACE]),
-			     sizeof(wpa_s->ifname)))
-		{
-			found = 1;
-			break;
-		}
-	}
-
-	if (!found)
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	if (wpa_supplicant_remove_iface(global, wpa_s, 0))
-		return UBUS_STATUS_INVALID_ARGUMENT;
-
-	return UBUS_STATUS_OK;
-}
-
-static const struct ubus_method wpas_daemon_methods[] = {
-	UBUS_METHOD("config_add", wpas_config_add, wpas_config_add_policy),
-	UBUS_METHOD("config_remove", wpas_config_remove, wpas_config_remove_policy),
-};
-
-static struct ubus_object_type wpas_daemon_object_type =
-	UBUS_OBJECT_TYPE("wpa_supplicant", wpas_daemon_methods);
-
-void wpas_ubus_add(struct wpa_global *global)
-{
-	struct ubus_object *obj = &global->ubus_global;
-	int ret;
-
-	if (!wpas_ubus_init())
-		return;
-
-	obj->name = strdup("wpa_supplicant");
-
-	obj->type = &wpas_daemon_object_type;
-	obj->methods = wpas_daemon_object_type.methods;
-	obj->n_methods = wpas_daemon_object_type.n_methods;
-	ret = ubus_add_object(ctx, obj);
-	wpas_ubus_ref_inc();
-}
-
-void wpas_ubus_free(struct wpa_global *global)
-{
-	struct ubus_object *obj = &global->ubus_global;
-	char *name = (char *) obj->name;
-
-	if (!ctx)
-		return;
-
-	if (obj->id) {
-		ubus_remove_object(ctx, obj);
-		wpas_ubus_ref_dec();
-	}
-
-	free(name);
-}
-
-
 #ifdef CONFIG_WPS
 void wpas_ubus_notify(struct wpa_supplicant *wpa_s, const struct wps_credential *cred)
 {
diff --git a/package/network/services/hostapd/src/wpa_supplicant/ubus.h b/package/network/services/hostapd/src/wpa_supplicant/ubus.h
index bf92b98c01..f6681cb26d 100644
--- a/package/network/services/hostapd/src/wpa_supplicant/ubus.h
+++ b/package/network/services/hostapd/src/wpa_supplicant/ubus.h
@@ -24,9 +24,6 @@ struct wpas_ubus_bss {
 void wpas_ubus_add_bss(struct wpa_supplicant *wpa_s);
 void wpas_ubus_free_bss(struct wpa_supplicant *wpa_s);
 
-void wpas_ubus_add(struct wpa_global *global);
-void wpas_ubus_free(struct wpa_global *global);
-
 #ifdef CONFIG_WPS
 void wpas_ubus_notify(struct wpa_supplicant *wpa_s, const struct wps_credential *cred);
 #endif
@@ -34,14 +31,6 @@ void wpas_ubus_notify(struct wpa_supplicant *wpa_s, const struct wps_credential
 #else
 struct wpas_ubus_bss {};
 
-static inline void wpas_ubus_add_iface(struct wpa_supplicant *wpa_s)
-{
-}
-
-static inline void wpas_ubus_free_iface(struct wpa_supplicant *wpa_s)
-{
-}
-
 static inline void wpas_ubus_add_bss(struct wpa_supplicant *wpa_s)
 {
 }
diff --git a/package/network/services/hostapd/src/wpa_supplicant/ucode.c b/package/network/services/hostapd/src/wpa_supplicant/ucode.c
new file mode 100644
index 0000000000..55d22584ff
--- /dev/null
+++ b/package/network/services/hostapd/src/wpa_supplicant/ucode.c
@@ -0,0 +1,281 @@
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "utils/ucode.h"
+#include "drivers/driver.h"
+#include "ap/hostapd.h"
+#include "wpa_supplicant_i.h"
+#include "wps_supplicant.h"
+#include "bss.h"
+#include "ucode.h"
+
+static struct wpa_global *wpa_global;
+static uc_resource_type_t *global_type, *iface_type;
+static uc_value_t *global, *iface_registry;
+static uc_vm_t *vm;
+
+static uc_value_t *
+wpas_ucode_iface_get_uval(struct wpa_supplicant *wpa_s)
+{
+	uc_value_t *val;
+
+	if (wpa_s->ucode.idx)
+		return wpa_ucode_registry_get(iface_registry, wpa_s->ucode.idx);
+
+	val = uc_resource_new(iface_type, wpa_s);
+	wpa_s->ucode.idx = wpa_ucode_registry_add(iface_registry, val);
+
+	return val;
+}
+
+static void
+wpas_ucode_update_interfaces(void)
+{
+	uc_value_t *ifs = ucv_object_new(vm);
+	struct wpa_supplicant *wpa_s;
+	int i;
+
+	for (wpa_s = wpa_global->ifaces; wpa_s; wpa_s = wpa_s->next)
+		ucv_object_add(ifs, wpa_s->ifname, ucv_get(wpas_ucode_iface_get_uval(wpa_s)));
+
+	ucv_object_add(ucv_prototype_get(global), "interfaces", ucv_get(ifs));
+	ucv_gc(vm);
+}
+
+void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s)
+{
+	uc_value_t *val;
+
+	if (wpa_ucode_call_prepare("iface_add"))
+		return;
+
+	uc_value_push(ucv_get(ucv_string_new(wpa_s->ifname)));
+	uc_value_push(ucv_get(wpas_ucode_iface_get_uval(wpa_s)));
+	ucv_put(wpa_ucode_call(2));
+	ucv_gc(vm);
+}
+
+void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s)
+{
+	uc_value_t *val;
+
+	val = wpa_ucode_registry_remove(iface_registry, wpa_s->ucode.idx);
+	if (!val)
+		return;
+
+	wpa_s->ucode.idx = 0;
+	if (wpa_ucode_call_prepare("iface_remove"))
+		return;
+
+	uc_value_push(ucv_get(ucv_string_new(wpa_s->ifname)));
+	uc_value_push(ucv_get(val));
+	ucv_put(wpa_ucode_call(2));
+	ucv_gc(vm);
+}
+
+void wpas_ucode_update_state(struct wpa_supplicant *wpa_s)
+{
+	const char *state;
+	uc_value_t *val;
+
+	val = wpa_ucode_registry_get(iface_registry, wpa_s->ucode.idx);
+	if (!val)
+		return;
+
+	if (wpa_ucode_call_prepare("state"))
+		return;
+
+	state = wpa_supplicant_state_txt(wpa_s->wpa_state);
+	uc_value_push(ucv_get(ucv_string_new(wpa_s->ifname)));
+	uc_value_push(ucv_get(val));
+	uc_value_push(ucv_get(ucv_string_new(state)));
+	ucv_put(wpa_ucode_call(3));
+	ucv_gc(vm);
+}
+
+void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, union wpa_event_data *data)
+{
+	const char *state;
+	uc_value_t *val;
+
+	if (event != EVENT_CH_SWITCH_STARTED)
+		return;
+
+	val = wpa_ucode_registry_get(iface_registry, wpa_s->ucode.idx);
+	if (!val)
+		return;
+
+	if (wpa_ucode_call_prepare("event"))
+		return;
+
+	uc_value_push(ucv_get(ucv_string_new(wpa_s->ifname)));
+	uc_value_push(ucv_get(val));
+	uc_value_push(ucv_get(ucv_string_new(event_to_string(event))));
+	val = ucv_object_new(vm);
+	uc_value_push(ucv_get(val));
+
+	if (event == EVENT_CH_SWITCH_STARTED) {
+		ucv_object_add(val, "csa_count", ucv_int64_new(data->ch_switch.count));
+		ucv_object_add(val, "frequency", ucv_int64_new(data->ch_switch.freq));
+		ucv_object_add(val, "sec_chan_offset", ucv_int64_new(data->ch_switch.ch_offset));
+		ucv_object_add(val, "center_freq1", ucv_int64_new(data->ch_switch.cf1));
+		ucv_object_add(val, "center_freq2", ucv_int64_new(data->ch_switch.cf2));
+	}
+
+	ucv_put(wpa_ucode_call(4));
+	ucv_gc(vm);
+}
+
+static const char *obj_stringval(uc_value_t *obj, const char *name)
+{
+	uc_value_t *val = ucv_object_get(obj, name, NULL);
+
+	return ucv_string_get(val);
+}
+
+static uc_value_t *
+uc_wpas_add_iface(uc_vm_t *vm, size_t nargs)
+{
+	uc_value_t *info = uc_fn_arg(0);
+	uc_value_t *ifname = ucv_object_get(info, "iface", NULL);
+	uc_value_t *bridge = ucv_object_get(info, "bridge", NULL);
+	uc_value_t *config = ucv_object_get(info, "config", NULL);
+	uc_value_t *ctrl = ucv_object_get(info, "ctrl", NULL);
+	struct wpa_interface iface;
+	int ret = -1;
+
+	if (ucv_type(info) != UC_OBJECT)
+		goto out;
+
+	iface = (struct wpa_interface){
+		.driver = "nl80211",
+		.ifname = ucv_string_get(ifname),
+		.bridge_ifname = ucv_string_get(bridge),
+		.confname = ucv_string_get(config),
+		.ctrl_interface = ucv_string_get(ctrl),
+	};
+
+	if (!iface.ifname || !iface.confname)
+		goto out;
+
+	ret = wpa_supplicant_add_iface(wpa_global, &iface, 0) ? 0 : -1;
+	wpas_ucode_update_interfaces();
+
+out:
+	return ucv_int64_new(ret);
+}
+
+static uc_value_t *
+uc_wpas_remove_iface(uc_vm_t *vm, size_t nargs)
+{
+	struct wpa_supplicant *wpa_s = NULL;
+	uc_value_t *ifname_arg = uc_fn_arg(0);
+	const char *ifname = ucv_string_get(ifname_arg);
+	int ret = -1;
+
+	if (!ifname)
+		goto out;
+
+	for (wpa_s = wpa_global->ifaces; wpa_s; wpa_s = wpa_s->next)
+		if (!strcmp(wpa_s->ifname, ifname))
+			break;
+
+	if (!wpa_s)
+		goto out;
+
+	ret = wpa_supplicant_remove_iface(wpa_global, wpa_s, 0);
+	wpas_ucode_update_interfaces();
+
+out:
+	return ucv_int64_new(ret);
+}
+
+static uc_value_t *
+uc_wpas_iface_status(uc_vm_t *vm, size_t nargs)
+{
+	struct wpa_supplicant *wpa_s = uc_fn_thisval("wpas.iface");
+	struct wpa_bss *bss;
+	uc_value_t *ret, *val;
+
+	if (!wpa_s)
+		return NULL;
+
+	ret = ucv_object_new(vm);
+
+	val = ucv_string_new(wpa_supplicant_state_txt(wpa_s->wpa_state));
+	ucv_object_add(ret, "state", ucv_get(val));
+
+	bss = wpa_s->current_bss;
+	if (bss) {
+		int sec_chan = 0;
+		const u8 *ie;
+
+		ie = wpa_bss_get_ie(bss, WLAN_EID_HT_OPERATION);
+		if (ie && ie[1] >= 2) {
+			const struct ieee80211_ht_operation *ht_oper;
+			int sec;
+
+			ht_oper = (const void *) (ie + 2);
+			sec = ht_oper->ht_param & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK;
+			if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_ABOVE)
+				sec_chan = 1;
+			else if (sec == HT_INFO_HT_PARAM_SECONDARY_CHNL_BELOW)
+				sec_chan = -1;
+		}
+
+		ucv_object_add(ret, "sec_chan_offset", ucv_int64_new(sec_chan));
+		ucv_object_add(ret, "frequency", ucv_int64_new(bss->freq));
+	}
+
+#ifdef CONFIG_MESH
+	if (wpa_s->ifmsh) {
+		struct hostapd_iface *ifmsh = wpa_s->ifmsh;
+
+		ucv_object_add(ret, "sec_chan_offset", ucv_int64_new(ifmsh->conf->secondary_channel));
+		ucv_object_add(ret, "frequency", ucv_int64_new(ifmsh->freq));
+	}
+#endif
+
+	return ret;
+}
+
+int wpas_ucode_init(struct wpa_global *gl)
+{
+	static const uc_function_list_t global_fns[] = {
+		{ "printf",	uc_wpa_printf },
+		{ "getpid", uc_wpa_getpid },
+		{ "add_iface", uc_wpas_add_iface },
+		{ "remove_iface", uc_wpas_remove_iface },
+	};
+	static const uc_function_list_t iface_fns[] = {
+		{ "status", uc_wpas_iface_status },
+	};
+	uc_value_t *data, *proto;
+
+	wpa_global = gl;
+	vm = wpa_ucode_create_vm();
+
+	global_type = uc_type_declare(vm, "wpas.global", global_fns, NULL);
+	iface_type = uc_type_declare(vm, "wpas.iface", iface_fns, NULL);
+
+	iface_registry = ucv_array_new(vm);
+	uc_vm_registry_set(vm, "wpas.iface_registry", iface_registry);
+
+	global = wpa_ucode_global_init("wpas", global_type);
+
+	if (wpa_ucode_run(HOSTAPD_UC_PATH "wpa_supplicant.uc"))
+		goto free_vm;
+
+	ucv_gc(vm);
+	return 0;
+
+free_vm:
+	wpa_ucode_free_vm();
+	return -1;
+}
+
+void wpas_ucode_free(void)
+{
+	if (wpa_ucode_call_prepare("shutdown") == 0)
+		ucv_put(wpa_ucode_call(0));
+	wpa_ucode_free_vm();
+}
diff --git a/package/network/services/hostapd/src/wpa_supplicant/ucode.h b/package/network/services/hostapd/src/wpa_supplicant/ucode.h
new file mode 100644
index 0000000000..a429a0ed87
--- /dev/null
+++ b/package/network/services/hostapd/src/wpa_supplicant/ucode.h
@@ -0,0 +1,49 @@
+#ifndef __WPAS_UCODE_H
+#define __WPAS_UCODE_H
+
+#include "utils/ucode.h"
+
+struct wpa_global;
+union wpa_event_data;
+struct wpa_supplicant;
+
+struct wpas_ucode_bss {
+#ifdef UCODE_SUPPORT
+	unsigned int idx;
+#endif
+};
+
+#ifdef UCODE_SUPPORT
+int wpas_ucode_init(struct wpa_global *gl);
+void wpas_ucode_free(void);
+void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s);
+void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s);
+void wpas_ucode_update_state(struct wpa_supplicant *wpa_s);
+void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, union wpa_event_data *data);
+#else
+static inline int wpas_ucode_init(struct wpa_global *gl)
+{
+	return -EINVAL;
+}
+static inline void wpas_ucode_free(void)
+{
+}
+static inline void wpas_ucode_add_bss(struct wpa_supplicant *wpa_s)
+{
+}
+
+static inline void wpas_ucode_free_bss(struct wpa_supplicant *wpa_s)
+{
+}
+
+static inline void wpas_ucode_update_state(struct wpa_supplicant *wpa_s)
+{
+}
+
+static inline void wpas_ucode_event(struct wpa_supplicant *wpa_s, int event, union wpa_event_data *data)
+{
+}
+
+#endif
+
+#endif




More information about the lede-commits mailing list