ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/package/network/config/wifi-scripts/files/etc/hotplug.d/ieee80211/10-wifi-detect b/package/network/config/wifi-scripts/files/etc/hotplug.d/ieee80211/10-wifi-detect
new file mode 100755
index 0000000..b865552
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/etc/hotplug.d/ieee80211/10-wifi-detect
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+[ "${ACTION}" = "add" ] && {
+	/sbin/wifi config
+}
diff --git a/package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh b/package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh
new file mode 100644
index 0000000..d673f54
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/lib/netifd/hostapd.sh
@@ -0,0 +1,1656 @@
+. /lib/functions/network.sh
+. /lib/functions.sh
+
+wpa_supplicant_add_rate() {
+	local var="$1"
+	local val="$(($2 / 1000))"
+	local sub="$((($2 / 100) % 10))"
+	append $var "$val" ","
+	[ $sub -gt 0 ] && append $var "."
+}
+
+hostapd_add_rate() {
+	local var="$1"
+	local val="$(($2 / 100))"
+	append $var "$val" " "
+}
+
+hostapd_append_wep_key() {
+	local var="$1"
+
+	wep_keyidx=0
+	set_default key 1
+	case "$key" in
+		[1234])
+			for idx in 1 2 3 4; do
+				local zidx
+				zidx="$(($idx - 1))"
+				json_get_var ckey "key${idx}"
+				[ -n "$ckey" ] && \
+					append $var "wep_key${zidx}=$(prepare_key_wep "$ckey")" "$N$T"
+			done
+			wep_keyidx="$((key - 1))"
+		;;
+		*)
+			append $var "wep_key0=$(prepare_key_wep "$key")" "$N$T"
+		;;
+	esac
+}
+
+hostapd_append_wpa_key_mgmt() {
+	local auth_type_l="$(echo $auth_type | tr 'a-z' 'A-Z')"
+
+	case "$auth_type" in
+		psk|eap)
+			append wpa_key_mgmt "WPA-$auth_type_l"
+			[ "${wpa:-2}" -ge 2 ] && [ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt "FT-${auth_type_l}"
+			[ "${ieee80211w:-0}" -gt 0 ] && append wpa_key_mgmt "WPA-${auth_type_l}-SHA256"
+		;;
+		eap192)
+			append wpa_key_mgmt "WPA-EAP-SUITE-B-192"
+			[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt "FT-EAP-SHA384"
+		;;
+		eap-eap2)
+			append wpa_key_mgmt "WPA-EAP"
+			append wpa_key_mgmt "WPA-EAP-SHA256"
+			[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt "FT-EAP"
+		;;
+		eap2)
+			[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt "FT-EAP"
+			append wpa_key_mgmt "WPA-EAP-SHA256"
+		;;
+		sae)
+			append wpa_key_mgmt "SAE"
+			[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt "FT-SAE"
+		;;
+		psk-sae)
+			append wpa_key_mgmt "WPA-PSK"
+			[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt "FT-PSK"
+			[ "${ieee80211w:-0}" -gt 0 ] && append wpa_key_mgmt "WPA-PSK-SHA256"
+			append wpa_key_mgmt "SAE"
+			[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt "FT-SAE"
+		;;
+		owe)
+			append wpa_key_mgmt "OWE"
+		;;
+	esac
+
+	[ "$fils" -gt 0 ] && {
+		case "$auth_type" in
+			eap192)
+				append wpa_key_mgmt FILS-SHA384
+				[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt FT-FILS-SHA384
+			;;
+			eap*)
+				append wpa_key_mgmt FILS-SHA256
+				[ "${ieee80211r:-0}" -gt 0 ] && append wpa_key_mgmt FT-FILS-SHA256
+			;;
+		esac
+	}
+
+	[ "$auth_osen" = "1" ] && append wpa_key_mgmt "OSEN"
+}
+
+hostapd_add_log_config() {
+	config_add_boolean \
+		log_80211 \
+		log_8021x \
+		log_radius \
+		log_wpa \
+		log_driver \
+		log_iapp \
+		log_mlme
+
+	config_add_int log_level
+}
+
+hostapd_common_add_device_config() {
+	config_add_array basic_rate
+	config_add_array supported_rates
+	config_add_string beacon_rate
+
+	config_add_string country country3
+	config_add_boolean country_ie doth
+	config_add_boolean spectrum_mgmt_required
+	config_add_int local_pwr_constraint
+	config_add_string require_mode
+	config_add_boolean legacy_rates
+	config_add_int cell_density
+	config_add_int rts_threshold
+	config_add_int rssi_reject_assoc_rssi
+	config_add_int rssi_ignore_probe_request
+	config_add_int maxassoc
+	config_add_int reg_power_type
+	config_add_boolean stationary_ap
+
+	config_add_string acs_chan_bias
+	config_add_array hostapd_options
+
+	config_add_int airtime_mode
+	config_add_int mbssid
+
+	hostapd_add_log_config
+}
+
+hostapd_prepare_device_config() {
+	local config="$1"
+	local driver="$2"
+
+	local base_cfg=
+
+	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 \
+		mbssid:0 band reg_power_type stationary_ap
+
+	hostapd_set_log_options base_cfg
+
+	set_default country_ie 1
+	set_default spectrum_mgmt_required 0
+	set_default doth 1
+	set_default legacy_rates 0
+	set_default airtime_mode 0
+	set_default cell_density 0
+
+	[ -n "$country" ] && {
+		append base_cfg "country_code=$country" "$N"
+		[ -n "$country3" ] && append base_cfg "country3=$country3" "$N"
+
+		[ "$country_ie" -gt 0 ] && {
+			append base_cfg "ieee80211d=1" "$N"
+			[ -n "$local_pwr_constraint" ] && append base_cfg "local_pwr_constraint=$local_pwr_constraint" "$N"
+			[ "$spectrum_mgmt_required" -gt 0 ] && append base_cfg "spectrum_mgmt_required=$spectrum_mgmt_required" "$N"
+		}
+		[ "$hwmode" = "a" -a "$doth" -gt 0 ] && append base_cfg "ieee80211h=1" "$N"
+	}
+
+	[ -n "$acs_chan_bias" ] && append base_cfg "acs_chan_bias=$acs_chan_bias" "$N"
+
+	local brlist= br
+	json_get_values basic_rate_list basic_rate
+	local rlist= r
+	json_get_values rate_list supported_rates
+
+	[ -n "$hwmode" ] && append base_cfg "hw_mode=$hwmode" "$N"
+	if [ "$hwmode" = "g" ] || [ "$hwmode" = "a" ]; then
+		[ -n "$require_mode" ] && legacy_rates=0
+		case "$require_mode" in
+			n) append base_cfg "require_ht=1" "$N";;
+			ac) append base_cfg "require_vht=1" "$N";;
+		esac
+	fi
+	case "$hwmode" in
+		b)
+			if [ "$cell_density" -eq 1 ]; then
+				set_default rate_list "5500 11000"
+				set_default basic_rate_list "5500 11000"
+			elif [ "$cell_density" -ge 2 ]; then
+				set_default rate_list "11000"
+				set_default basic_rate_list "11000"
+			fi
+		;;
+		g)
+			if [ "$cell_density" -eq 0 ] || [ "$cell_density" -eq 1 ]; then
+				if [ "$legacy_rates" -eq 0 ]; then
+					set_default rate_list "6000 9000 12000 18000 24000 36000 48000 54000"
+					set_default basic_rate_list "6000 12000 24000"
+				elif [ "$cell_density" -eq 1 ]; then
+					set_default rate_list "5500 6000 9000 11000 12000 18000 24000 36000 48000 54000"
+					set_default basic_rate_list "5500 11000"
+				fi
+			elif [ "$cell_density" -ge 3 ] && [ "$legacy_rates" -ne 0 ] || [ "$cell_density" -eq 2 ]; then
+				if [ "$legacy_rates" -eq 0 ]; then
+					set_default rate_list "12000 18000 24000 36000 48000 54000"
+					set_default basic_rate_list "12000 24000"
+				else
+					set_default rate_list "11000 12000 18000 24000 36000 48000 54000"
+					set_default basic_rate_list "11000"
+				fi
+			elif [ "$cell_density" -ge 3 ]; then
+				set_default rate_list "24000 36000 48000 54000"
+				set_default basic_rate_list "24000"
+			fi
+		;;
+		a)
+			if [ "$cell_density" -eq 1 ]; then
+				set_default rate_list "6000 9000 12000 18000 24000 36000 48000 54000"
+				set_default basic_rate_list "6000 12000 24000"
+			elif [ "$cell_density" -eq 2 ]; then
+				set_default rate_list "12000 18000 24000 36000 48000 54000"
+				set_default basic_rate_list "12000 24000"
+			elif [ "$cell_density" -ge 3 ]; then
+				set_default rate_list "24000 36000 48000 54000"
+				set_default basic_rate_list "24000"
+			fi
+		;;
+	esac
+
+	for r in $rate_list; do
+		hostapd_add_rate rlist "$r"
+	done
+
+	for br in $basic_rate_list; do
+		hostapd_add_rate brlist "$br"
+	done
+
+	[ -n "$rssi_reject_assoc_rssi" ] && append base_cfg "rssi_reject_assoc_rssi=$rssi_reject_assoc_rssi" "$N"
+	[ -n "$rssi_ignore_probe_request" ] && append base_cfg "rssi_ignore_probe_request=$rssi_ignore_probe_request" "$N"
+	[ -n "$beacon_rate" ] && append base_cfg "beacon_rate=$beacon_rate" "$N"
+	[ -n "$rlist" ] && append base_cfg "supported_rates=$rlist" "$N"
+	[ -n "$brlist" ] && append base_cfg "basic_rates=$brlist" "$N"
+	append base_cfg "beacon_int=$beacon_int" "$N"
+	[ -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"
+
+	[ "$band" = "6g" ] && {
+		set_default reg_power_type 0
+		append base_cfg "he_6ghz_reg_pwr_type=$reg_power_type" "$N"
+	}
+
+	set_default stationary_ap 1
+	append base_cfg "stationary_ap=$stationary_ap" "$N"
+
+	json_get_values opts hostapd_options
+	for val in $opts; do
+		append base_cfg "$val" "$N"
+	done
+
+	cat > "$config" <<EOF
+driver=$driver
+$base_cfg
+EOF
+}
+
+hostapd_common_add_bss_config() {
+	config_add_string 'bssid:macaddr' 'ssid:string'
+	config_add_boolean wds wmm uapsd hidden utf8_ssid ppsk
+
+	config_add_int maxassoc max_inactivity
+	config_add_boolean disassoc_low_ack isolate short_preamble skip_inactivity_poll
+
+	config_add_int \
+		wep_rekey eap_reauth_period \
+		wpa_group_rekey wpa_pair_rekey wpa_master_rekey
+	config_add_boolean wpa_strict_rekey
+	config_add_boolean wpa_disable_eapol_key_retries
+
+	config_add_boolean tdls_prohibit
+
+	config_add_boolean rsn_preauth auth_cache
+	config_add_int ieee80211w
+	config_add_int eapol_version
+
+	config_add_array auth_server acct_server
+	config_add_string 'server:host'
+	config_add_string auth_secret key
+	config_add_int 'auth_port:port' 'port:port'
+
+	config_add_string acct_secret
+	config_add_int acct_port
+	config_add_int acct_interval
+
+	config_add_int bss_load_update_period chan_util_avg_period
+
+	config_add_string dae_client
+	config_add_string dae_secret
+	config_add_int dae_port
+
+	config_add_string nasid
+	config_add_string ownip
+	config_add_string radius_client_addr
+	config_add_string iapp_interface
+	config_add_string eap_type ca_cert client_cert identity anonymous_identity auth priv_key priv_key_pwd
+	config_add_boolean ca_cert_usesystem ca_cert2_usesystem
+	config_add_string subject_match subject_match2
+	config_add_array altsubject_match altsubject_match2
+	config_add_array domain_match domain_match2 domain_suffix_match domain_suffix_match2
+	config_add_string ieee80211w_mgmt_cipher
+
+	config_add_int dynamic_vlan vlan_naming vlan_no_bridge
+	config_add_string vlan_tagged_interface vlan_bridge
+	config_add_string vlan_file
+
+	config_add_string 'key1:wepkey' 'key2:wepkey' 'key3:wepkey' 'key4:wepkey' 'password:wpakey'
+
+	config_add_string wpa_psk_file
+
+	config_add_int multi_ap
+
+	config_add_boolean wps_pushbutton wps_label ext_registrar wps_pbc_in_m1
+	config_add_int wps_ap_setup_locked wps_independent
+	config_add_string wps_device_type wps_device_name wps_manufacturer wps_pin
+	config_add_string multi_ap_backhaul_ssid multi_ap_backhaul_key
+
+	config_add_boolean wnm_sleep_mode wnm_sleep_mode_no_keys bss_transition mbo
+	config_add_int time_advertisement
+	config_add_string time_zone
+	config_add_string vendor_elements
+
+	config_add_boolean ieee80211k rrm_neighbor_report rrm_beacon_report
+
+	config_add_boolean ftm_responder stationary_ap
+	config_add_string lci civic
+
+	config_add_boolean ieee80211r pmk_r1_push ft_psk_generate_local ft_over_ds
+	config_add_int r0_key_lifetime reassociation_deadline
+	config_add_string mobility_domain r1_key_holder
+	config_add_array r0kh r1kh
+
+	config_add_int ieee80211w_max_timeout ieee80211w_retry_timeout
+
+	config_add_string macfilter 'macfile:file'
+	config_add_array 'maclist:list(macaddr)'
+
+	config_add_array bssid_blacklist
+	config_add_array bssid_whitelist
+
+	config_add_int mcast_rate
+	config_add_array basic_rate
+	config_add_array supported_rates
+
+	config_add_boolean sae_require_mfp
+	config_add_int sae_pwe
+
+	config_add_string 'owe_transition_bssid:macaddr' 'owe_transition_ssid:string'
+	config_add_string owe_transition_ifname
+
+	config_add_boolean iw_enabled iw_internet iw_asra iw_esr iw_uesa
+	config_add_int iw_access_network_type iw_venue_group iw_venue_type
+	config_add_int iw_ipaddr_type_availability iw_gas_address3
+	config_add_string iw_hessid iw_network_auth_type iw_qos_map_set
+	config_add_array iw_roaming_consortium iw_domain_name iw_anqp_3gpp_cell_net iw_nai_realm
+	config_add_array iw_anqp_elem iw_venue_name iw_venue_url
+
+	config_add_boolean hs20 disable_dgaf osen
+	config_add_int anqp_domain_id
+	config_add_int hs20_deauth_req_timeout
+	config_add_array hs20_oper_friendly_name
+	config_add_array osu_provider
+	config_add_array operator_icon
+	config_add_array hs20_conn_capab
+	config_add_string osu_ssid hs20_wan_metrics hs20_operating_class hs20_t_c_filename hs20_t_c_timestamp
+
+	config_add_string hs20_t_c_server_url
+
+	config_add_array airtime_sta_weight
+	config_add_int airtime_bss_weight airtime_bss_limit
+
+	config_add_boolean multicast_to_unicast multicast_to_unicast_all proxy_arp per_sta_vif
+
+	config_add_array hostapd_bss_options
+	config_add_boolean default_disabled
+
+	config_add_boolean request_cui
+	config_add_array radius_auth_req_attr
+	config_add_array radius_acct_req_attr
+
+	config_add_int eap_server radius_server_auth_port
+	config_add_string eap_user_file ca_cert server_cert private_key private_key_passwd server_id radius_server_clients
+
+	config_add_boolean fils
+	config_add_string fils_dhcp
+
+	config_add_int ocv
+
+	config_add_boolean apup
+	config_add_string apup_peer_ifname_prefix
+}
+
+hostapd_set_vlan_file() {
+	local ifname="$1"
+	local vlan="$2"
+	json_get_vars name vid
+	echo "${vid} ${ifname}-${name}" >> /var/run/hostapd-${ifname}.vlan
+	wireless_add_vlan "${vlan}" "${ifname}-${name}"
+}
+
+hostapd_set_vlan() {
+	local ifname="$1"
+
+	rm -f /var/run/hostapd-${ifname}.vlan
+	for_each_vlan hostapd_set_vlan_file ${ifname}
+}
+
+hostapd_set_psk_file() {
+	local ifname="$1"
+	local vlan="$2"
+	local vlan_id=""
+
+	json_get_vars mac vid key
+	set_default mac "00:00:00:00:00:00"
+	[ -n "$vid" ] && vlan_id="vlanid=$vid "
+	echo "${vlan_id} ${mac} ${key}" >> /var/run/hostapd-${ifname}.psk
+}
+
+hostapd_set_psk() {
+	local ifname="$1"
+
+	rm -f /var/run/hostapd-${ifname}.psk
+	case "$auth_type" in
+		psk|psk-sae) ;;
+		*) return ;;
+	esac
+	for_each_station hostapd_set_psk_file ${ifname}
+}
+
+hostapd_set_sae_file() {
+	local ifname="$1"
+	local vlan="$2"
+	local vlan_id=""
+
+	json_get_vars mac vid key
+	set_default mac "ff:ff:ff:ff:ff:ff"
+	[ -n "$mac" ] && mac="|mac=$mac"
+	[ -n "$vid" ] && vlan_id="|vlanid=$vid"
+	printf '%s%s%s\n' "${key}" "${mac}" "${vlan_id}" >> /var/run/hostapd-${ifname}.sae
+}
+
+hostapd_set_sae() {
+	local ifname="$1"
+
+	rm -f /var/run/hostapd-${ifname}.sae
+	case "$auth_type" in
+		sae|psk-sae) ;;
+		*) return ;;
+	esac
+	for_each_station hostapd_set_sae_file ${ifname}
+}
+
+append_iw_roaming_consortium() {
+	[ -n "$1" ] && append bss_conf "roaming_consortium=$1" "$N"
+}
+
+append_iw_domain_name() {
+	if [ -z "$iw_domain_name_conf" ]; then
+		iw_domain_name_conf="$1"
+	else
+		iw_domain_name_conf="$iw_domain_name_conf,$1"
+	fi
+}
+
+append_iw_anqp_3gpp_cell_net() {
+	if [ -z "$iw_anqp_3gpp_cell_net_conf" ]; then
+		iw_anqp_3gpp_cell_net_conf="$1"
+	else
+		iw_anqp_3gpp_cell_net_conf="$iw_anqp_3gpp_cell_net_conf;$1"
+	fi
+}
+
+append_iw_anqp_elem() {
+	[ -n "$1" ] && append bss_conf "anqp_elem=$1" "$N"
+}
+
+append_iw_nai_realm() {
+	[ -n "$1" ] && append bss_conf "nai_realm=$1" "$N"
+}
+
+append_iw_venue_name() {
+	append bss_conf "venue_name=$1" "$N"
+}
+
+append_iw_venue_url() {
+	append bss_conf "venue_url=$1" "$N"
+}
+
+append_hs20_oper_friendly_name() {
+	append bss_conf "hs20_oper_friendly_name=$1" "$N"
+}
+
+append_osu_provider_friendly_name() {
+	append bss_conf "osu_friendly_name=$1" "$N"
+}
+
+append_osu_provider_service_desc() {
+	append bss_conf "osu_service_desc=$1" "$N"
+}
+
+append_hs20_icon() {
+	local width height lang type path
+	config_get width "$1" width
+	config_get height "$1" height
+	config_get lang "$1" lang
+	config_get type "$1" type
+	config_get path "$1" path
+
+	append bss_conf "hs20_icon=$width:$height:$lang:$type:$1:$path" "$N"
+}
+
+append_hs20_icons() {
+	config_load wireless
+	config_foreach append_hs20_icon hs20-icon
+}
+
+append_operator_icon() {
+	append bss_conf "operator_icon=$1" "$N"
+}
+
+append_osu_icon() {
+	append bss_conf "osu_icon=$1" "$N"
+}
+
+append_osu_provider() {
+	local cfgtype osu_server_uri osu_friendly_name osu_nai osu_nai2 osu_method_list
+
+	config_load wireless
+	config_get cfgtype "$1" TYPE
+	[ "$cfgtype" != "osu-provider" ] && return
+
+	append bss_conf "# provider $1" "$N"
+	config_get osu_server_uri "$1" osu_server_uri
+	config_get osu_nai "$1" osu_nai
+	config_get osu_nai2 "$1" osu_nai2
+	config_get osu_method_list "$1" osu_method
+
+	append bss_conf "osu_server_uri=$osu_server_uri" "$N"
+	append bss_conf "osu_nai=$osu_nai" "$N"
+	append bss_conf "osu_nai2=$osu_nai2" "$N"
+	append bss_conf "osu_method_list=$osu_method_list" "$N"
+
+	config_list_foreach "$1" osu_service_desc append_osu_provider_service_desc
+	config_list_foreach "$1" osu_friendly_name append_osu_friendly_name
+	config_list_foreach "$1" osu_icon append_osu_icon
+
+	append bss_conf "$N"
+}
+
+append_hs20_conn_capab() {
+	[ -n "$1" ] && append bss_conf "hs20_conn_capab=$1" "$N"
+}
+
+append_radius_acct_req_attr() {
+	[ -n "$1" ] && append bss_conf "radius_acct_req_attr=$1" "$N"
+}
+
+append_radius_auth_req_attr() {
+	[ -n "$1" ] && append bss_conf "radius_auth_req_attr=$1" "$N"
+}
+
+append_airtime_sta_weight() {
+	[ -n "$1" ] && append bss_conf "airtime_sta_weight=$1" "$N"
+}
+
+append_auth_server() {
+	[ -n "$1" ] || return
+	append bss_conf "auth_server_addr=$1" "$N"
+	append bss_conf "auth_server_port=$auth_port" "$N"
+	[ -n "$auth_secret" ] && append bss_conf "auth_server_shared_secret=$auth_secret" "$N"
+}
+
+append_acct_server() {
+	[ -n "$1" ] || return
+	append bss_conf "acct_server_addr=$1" "$N"
+	append bss_conf "acct_server_port=$acct_port" "$N"
+	[ -n "$acct_secret" ] && append bss_conf "acct_server_shared_secret=$acct_secret" "$N"
+}
+
+hostapd_set_bss_options() {
+	local var="$1"
+	local phy="$2"
+	local vif="$3"
+
+	wireless_vif_parse_encryption
+
+	local bss_conf bss_md5sum ft_key
+	local wep_rekey wpa_group_rekey wpa_pair_rekey wpa_master_rekey wpa_key_mgmt
+
+	json_get_vars \
+		wep_rekey wpa_group_rekey wpa_pair_rekey wpa_master_rekey wpa_strict_rekey \
+		wpa_disable_eapol_key_retries tdls_prohibit \
+		maxassoc max_inactivity disassoc_low_ack isolate auth_cache \
+		wps_pushbutton wps_label ext_registrar wps_pbc_in_m1 wps_ap_setup_locked \
+		wps_independent wps_device_type wps_device_name wps_manufacturer wps_pin \
+		macfilter ssid utf8_ssid wmm uapsd hidden short_preamble rsn_preauth \
+		iapp_interface eapol_version dynamic_vlan ieee80211w nasid \
+		acct_secret acct_port acct_interval \
+		bss_load_update_period chan_util_avg_period sae_require_mfp sae_pwe \
+		multi_ap multi_ap_backhaul_ssid multi_ap_backhaul_key skip_inactivity_poll \
+		ppsk airtime_bss_weight airtime_bss_limit airtime_sta_weight \
+		multicast_to_unicast_all proxy_arp per_sta_vif \
+		eap_server eap_user_file ca_cert server_cert private_key private_key_passwd server_id radius_server_clients radius_server_auth_port \
+		vendor_elements fils ocv apup
+
+	set_default fils 0
+	set_default isolate 0
+	set_default maxassoc 0
+	set_default max_inactivity 0
+	set_default short_preamble 1
+	set_default disassoc_low_ack 1
+	set_default skip_inactivity_poll 0
+	set_default hidden 0
+	set_default wmm 1
+	set_default uapsd 1
+	set_default wpa_disable_eapol_key_retries 0
+	set_default tdls_prohibit 0
+	set_default eapol_version $((wpa & 1))
+	set_default acct_port 1813
+	set_default bss_load_update_period 60
+	set_default chan_util_avg_period 600
+	set_default utf8_ssid 1
+	set_default multi_ap 0
+	set_default ppsk 0
+	set_default airtime_bss_weight 0
+	set_default airtime_bss_limit 0
+	set_default eap_server 0
+	set_default apup 0
+
+	/usr/sbin/hostapd -vfils || fils=0
+
+	append bss_conf "ctrl_interface=/var/run/hostapd"
+	if [ "$isolate" -gt 0 ]; then
+		append bss_conf "ap_isolate=$isolate" "$N"
+	fi
+	if [ "$maxassoc" -gt 0 ]; then
+		append bss_conf "max_num_sta=$maxassoc" "$N"
+	fi
+	if [ "$max_inactivity" -gt 0 ]; then
+		append bss_conf "ap_max_inactivity=$max_inactivity" "$N"
+	fi
+
+	[ "$airtime_bss_weight" -gt 0 ] && append bss_conf "airtime_bss_weight=$airtime_bss_weight" "$N"
+	[ "$airtime_bss_limit" -gt 0 ] && append bss_conf "airtime_bss_limit=$airtime_bss_limit" "$N"
+	json_for_each_item append_airtime_sta_weight airtime_sta_weight
+
+	append bss_conf "bss_load_update_period=$bss_load_update_period" "$N"
+	append bss_conf "chan_util_avg_period=$chan_util_avg_period" "$N"
+	append bss_conf "disassoc_low_ack=$disassoc_low_ack" "$N"
+	append bss_conf "skip_inactivity_poll=$skip_inactivity_poll" "$N"
+	append bss_conf "preamble=$short_preamble" "$N"
+	append bss_conf "wmm_enabled=$wmm" "$N"
+	append bss_conf "ignore_broadcast_ssid=$hidden" "$N"
+	append bss_conf "uapsd_advertisement_enabled=$uapsd" "$N"
+	append bss_conf "utf8_ssid=$utf8_ssid" "$N"
+	append bss_conf "multi_ap=$multi_ap" "$N"
+	[ -n "$vendor_elements" ] && append bss_conf "vendor_elements=$vendor_elements" "$N"
+
+	[ "$tdls_prohibit" -gt 0 ] && append bss_conf "tdls_prohibit=$tdls_prohibit" "$N"
+
+	[ "$wpa" -gt 0 ] && {
+		[ -n "$wpa_group_rekey"  ] && append bss_conf "wpa_group_rekey=$wpa_group_rekey" "$N"
+		[ -n "$wpa_pair_rekey"   ] && append bss_conf "wpa_ptk_rekey=$wpa_pair_rekey"    "$N"
+		[ -n "$wpa_master_rekey" ] && append bss_conf "wpa_gmk_rekey=$wpa_master_rekey"  "$N"
+		[ -n "$wpa_strict_rekey" ] && append bss_conf "wpa_strict_rekey=$wpa_strict_rekey" "$N"
+	}
+
+	[ -n "$nasid" ] && append bss_conf "nas_identifier=$nasid" "$N"
+
+	[ -n "$acct_interval" ] && \
+		append bss_conf "radius_acct_interim_interval=$acct_interval" "$N"
+	json_for_each_item append_acct_server acct_server
+	json_for_each_item append_radius_acct_req_attr radius_acct_req_attr
+
+	[ -n "$ocv" ] && append bss_conf "ocv=$ocv" "$N"
+
+	case "$auth_type" in
+		sae|owe|eap2|eap192)
+			set_default ieee80211w 2
+			set_default sae_require_mfp 1
+			[ "$ppsk" -eq 0 ] && set_default sae_pwe 2
+		;;
+		psk-sae|eap-eap2)
+			set_default ieee80211w 1
+			set_default sae_require_mfp 1
+			[ "$ppsk" -eq 0 ] && set_default sae_pwe 2
+		;;
+	esac
+	[ -n "$sae_require_mfp" ] && append bss_conf "sae_require_mfp=$sae_require_mfp" "$N"
+	[ -n "$sae_pwe" ] && append bss_conf "sae_pwe=$sae_pwe" "$N"
+
+	local vlan_possible=""
+
+	case "$auth_type" in
+		none|owe)
+			json_get_vars owe_transition_bssid owe_transition_ssid owe_transition_ifname
+
+			[ -n "$owe_transition_ssid" ] && append bss_conf "owe_transition_ssid=\"$owe_transition_ssid\"" "$N"
+			[ -n "$owe_transition_bssid" ] && append bss_conf "owe_transition_bssid=$owe_transition_bssid" "$N"
+			[ -n "$owe_transition_ifname" ] && append bss_conf "owe_transition_ifname=$owe_transition_ifname" "$N"
+
+			wps_possible=1
+			# Here we make the assumption that if we're in open mode
+			# with WPS enabled, we got to be in unconfigured state.
+			wps_not_configured=1
+		;;
+		psk|sae|psk-sae)
+			json_get_vars key wpa_psk_file sae_password_file
+			if [ "$ppsk" -ne 0 ]; then
+				json_get_vars auth_secret auth_port
+				set_default auth_port 1812
+				json_for_each_item append_auth_server auth_server
+				append bss_conf "macaddr_acl=2" "$N"
+				append bss_conf "wpa_psk_radius=2" "$N"
+			elif [ ${#key} -eq 64 ]; then
+				append bss_conf "wpa_psk=$key" "$N"
+			elif [ ${#key} -ge 8 ] && [ ${#key} -le 63 ]; then
+				append bss_conf "wpa_passphrase=$key" "$N"
+			elif [ -n "$key" ]; then
+				wireless_setup_vif_failed INVALID_WPA_PSK
+				return 1
+			fi
+			[ -z "$wpa_psk_file" ] && set_default wpa_psk_file /var/run/hostapd-$ifname.psk
+			[ -n "$wpa_psk_file" ] && [ "$auth_type" = "psk" -o "$auth_type" = "psk-sae" ] && {
+				[ -e "$wpa_psk_file" ] || touch "$wpa_psk_file"
+				append bss_conf "wpa_psk_file=$wpa_psk_file" "$N"
+			}
+			[ -z "$sae_password_file" ] && set_default sae_password_file /var/run/hostapd-$ifname.sae
+			[ -n "$sae_password_file" ] && [ "$auth_type" = "sae" -o "$auth_type" = "psk-sae" ] && {
+				[ -e "$sae_password_file" ] || touch "$sae_password_file"
+				append bss_conf "sae_password_file=$sae_password_file" "$N"
+			}
+			[ "$eapol_version" -ge "1" -a "$eapol_version" -le "2" ] && append bss_conf "eapol_version=$eapol_version" "$N"
+
+			set_default dynamic_vlan 0
+			vlan_possible=1
+			wps_possible=1
+		;;
+		eap|eap2|eap-eap2|eap192)
+			json_get_vars \
+				auth_server auth_secret auth_port \
+				dae_client dae_secret dae_port \
+				dynamic_ownip ownip radius_client_addr \
+				eap_reauth_period request_cui \
+				erp_domain mobility_domain \
+				fils_realm fils_dhcp
+
+			# radius can provide VLAN ID for clients
+			vlan_possible=1
+
+			set_default dynamic_ownip 1
+
+			# legacy compatibility
+			[ -n "$auth_server" ] || json_get_var auth_server server
+			[ -n "$auth_port" ] || json_get_var auth_port port
+			[ -n "$auth_secret" ] || json_get_var auth_secret key
+
+			[ "$fils" -gt 0 ] && {
+				set_default erp_domain "$mobility_domain"
+				set_default erp_domain "$(echo "$ssid" | md5sum | head -c 8)"
+				set_default fils_realm "$erp_domain"
+
+				append bss_conf "erp_send_reauth_start=1" "$N"
+				append bss_conf "erp_domain=$erp_domain" "$N"
+				append bss_conf "fils_realm=$fils_realm" "$N"
+				append bss_conf "fils_cache_id=$(echo "$fils_realm" | md5sum | head -c 4)" "$N"
+
+				[ "$fils_dhcp" = "*" ] && {
+					json_get_values network network
+					fils_dhcp=
+					for net in $network; do
+						fils_dhcp="$(ifstatus "$net" | jsonfilter -e '@.data.dhcpserver')"
+						[ -n "$fils_dhcp" ] && break
+					done
+
+					[ -z "$fils_dhcp" -a -n "$network_bridge" -a -n "$network_ifname" ] && \
+						fils_dhcp="$(udhcpc -B -n -q -s /lib/netifd/dhcp-get-server.sh -t 1 -i "$network_ifname" 2>/dev/null)"
+				}
+				[ -n "$fils_dhcp" ] && append bss_conf "dhcp_server=$fils_dhcp" "$N"
+			}
+
+			set_default auth_port 1812
+			set_default dae_port 3799
+			set_default request_cui 0
+
+			[ "$eap_server" -eq 0 ] && json_for_each_item append_auth_server auth_server
+			[ "$request_cui" -gt 0 ] && append bss_conf "radius_request_cui=$request_cui" "$N"
+			[ -n "$eap_reauth_period" ] && append bss_conf "eap_reauth_period=$eap_reauth_period" "$N"
+
+			[ -n "$dae_client" -a -n "$dae_secret" ] && {
+				append bss_conf "radius_das_port=$dae_port" "$N"
+				append bss_conf "radius_das_client=$dae_client $dae_secret" "$N"
+			}
+			json_for_each_item append_radius_auth_req_attr radius_auth_req_attr
+
+			if [ -n "$ownip" ]; then
+				append bss_conf "own_ip_addr=$ownip" "$N"
+			elif [ "$dynamic_ownip" -gt 0 ]; then
+				append bss_conf "dynamic_own_ip_addr=$dynamic_ownip" "$N"
+			fi
+
+			[ -n "$radius_client_addr" ] && append bss_conf "radius_client_addr=$radius_client_addr" "$N"
+			append bss_conf "eapol_key_index_workaround=1" "$N"
+			append bss_conf "ieee8021x=1" "$N"
+
+			[ "$eapol_version" -ge "1" -a "$eapol_version" -le "2" ] && append bss_conf "eapol_version=$eapol_version" "$N"
+		;;
+		wep)
+			local wep_keyidx=0
+			json_get_vars key
+			hostapd_append_wep_key bss_conf
+			append bss_conf "wep_default_key=$wep_keyidx" "$N"
+			[ -n "$wep_rekey" ] && append bss_conf "wep_rekey_period=$wep_rekey" "$N"
+		;;
+	esac
+
+	case "$auth_type" in
+		none|owe|psk|sae|psk-sae|wep)
+			json_get_vars \
+			auth_server auth_port auth_secret \
+			ownip radius_client_addr
+
+			[ -n "$auth_server" ] &&  {
+				set_default auth_port 1812
+
+				json_for_each_item append_auth_server auth_server
+				[ -n "$ownip" ] && append bss_conf "own_ip_addr=$ownip" "$N"
+				[ -n "$radius_client_addr" ] && append bss_conf "radius_client_addr=$radius_client_addr" "$N"
+				append bss_conf "macaddr_acl=2" "$N"
+			}
+		;;
+	esac
+
+	local auth_algs="$((($auth_mode_shared << 1) | $auth_mode_open))"
+	append bss_conf "auth_algs=${auth_algs:-1}" "$N"
+	append bss_conf "wpa=$wpa" "$N"
+	[ -n "$wpa_pairwise" ] && append bss_conf "wpa_pairwise=$wpa_pairwise" "$N"
+
+	set_default wps_pushbutton 0
+	set_default wps_label 0
+	set_default wps_pbc_in_m1 0
+
+	config_methods=
+	[ "$wps_pushbutton" -gt 0 ] && append config_methods push_button
+	[ "$wps_label" -gt 0 ] && append config_methods label
+
+	# WPS not possible on Multi-AP backhaul-only SSID
+	[ "$multi_ap" = 1 ] && wps_possible=
+
+	[ -n "$wps_possible" -a -n "$config_methods" ] && {
+		set_default ext_registrar 0
+		set_default wps_device_type "6-0050F204-1"
+		set_default wps_device_name "OpenWrt AP"
+		set_default wps_manufacturer "www.openwrt.org"
+		set_default wps_independent 1
+
+		wps_state=2
+		[ -n "$wps_not_configured" ] && wps_state=1
+
+		[ "$ext_registrar" -gt 0 -a -n "$network_bridge" ] && append bss_conf "upnp_iface=$network_bridge" "$N"
+
+		append bss_conf "eap_server=1" "$N"
+		[ -n "$wps_pin" ] && append bss_conf "ap_pin=$wps_pin" "$N"
+		append bss_conf "wps_state=$wps_state" "$N"
+		append bss_conf "device_type=$wps_device_type" "$N"
+		append bss_conf "device_name=$wps_device_name" "$N"
+		append bss_conf "manufacturer=$wps_manufacturer" "$N"
+		append bss_conf "config_methods=$config_methods" "$N"
+		append bss_conf "wps_independent=$wps_independent" "$N"
+		[ -n "$wps_ap_setup_locked" ] && append bss_conf "ap_setup_locked=$wps_ap_setup_locked" "$N"
+		[ "$wps_pbc_in_m1" -gt 0 ] && append bss_conf "pbc_in_m1=$wps_pbc_in_m1" "$N"
+		[ "$multi_ap" -gt 0 ] && [ -n "$multi_ap_backhaul_ssid" ] && {
+			append bss_conf "multi_ap_backhaul_ssid=\"$multi_ap_backhaul_ssid\"" "$N"
+			if [ -z "$multi_ap_backhaul_key" ]; then
+				:
+			elif [ ${#multi_ap_backhaul_key} -lt 8 ]; then
+				wireless_setup_vif_failed INVALID_WPA_PSK
+				return 1
+			elif [ ${#multi_ap_backhaul_key} -eq 64 ]; then
+				append bss_conf "multi_ap_backhaul_wpa_psk=$multi_ap_backhaul_key" "$N"
+			else
+				append bss_conf "multi_ap_backhaul_wpa_passphrase=$multi_ap_backhaul_key" "$N"
+			fi
+		}
+	}
+
+	append bss_conf "ssid=$ssid" "$N"
+	[ -n "$network_bridge" ] && append bss_conf "bridge=$network_bridge${N}wds_bridge=" "$N"
+	[ -n "$network_ifname" ] && append bss_conf "snoop_iface=$network_ifname" "$N"
+	[ -n "$iapp_interface" ] && {
+		local ifname
+		network_get_device ifname "$iapp_interface" || ifname="$iapp_interface"
+		append bss_conf "iapp_interface=$ifname" "$N"
+	}
+
+	json_get_vars time_advertisement time_zone wnm_sleep_mode wnm_sleep_mode_no_keys bss_transition mbo
+	set_default bss_transition 0
+	set_default wnm_sleep_mode 0
+	set_default wnm_sleep_mode_no_keys 0
+	set_default mbo 0
+
+	[ -n "$time_advertisement" ] && append bss_conf "time_advertisement=$time_advertisement" "$N"
+	[ -n "$time_zone" ] && append bss_conf "time_zone=$time_zone" "$N"
+	if [ "$wnm_sleep_mode" -eq "1" ]; then
+		append bss_conf "wnm_sleep_mode=1" "$N"
+		[ "$wnm_sleep_mode_no_keys" -eq "1" ] && append bss_conf "wnm_sleep_mode_no_keys=1" "$N"
+	fi
+	[ "$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 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
+	else
+		set_default rrm_neighbor_report 0
+		set_default rrm_beacon_report 0
+	fi
+
+	[ "$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
+	if [ "$ftm_responder" -eq "1" ]; then
+		set_default stationary_ap 0
+		iw phy "$phy" info | grep -q "ENABLE_FTM_RESPONDER" && {
+			append bss_conf "ftm_responder=1" "$N"
+			[ "$stationary_ap" -eq "1" ] && append bss_conf "stationary_ap=1" "$N"
+			[ -n "$lci" ] && append bss_conf "lci=$lci" "$N"
+			[ -n "$civic" ] && append bss_conf "civic=$civic" "$N"
+		}
+	fi
+
+	json_get_vars ieee80211r
+	set_default ieee80211r 0
+	if [ "$wpa" -ge "1" ]; then
+		if [ "$fils" -gt 0 ]; then
+			json_get_vars fils_realm
+			set_default fils_realm "$(echo "$ssid" | md5sum | head -c 8)"
+		fi
+
+		append bss_conf "wpa_disable_eapol_key_retries=$wpa_disable_eapol_key_retries" "$N"
+
+		hostapd_append_wpa_key_mgmt
+		[ -n "$wpa_key_mgmt" ] && append bss_conf "wpa_key_mgmt=$wpa_key_mgmt" "$N"
+	fi
+
+	if [ "$wpa" -ge "2" ]; then
+		if [ "$ieee80211r" -gt "0" ]; then
+			json_get_vars mobility_domain ft_psk_generate_local ft_over_ds reassociation_deadline
+
+			set_default mobility_domain "$(echo "$ssid" | md5sum | head -c 4)"
+			set_default ft_over_ds 0
+			set_default reassociation_deadline 1000
+
+			case "$auth_type" in
+				psk)
+					set_default ft_psk_generate_local 1
+				;;
+				*)
+					set_default ft_psk_generate_local 0
+				;;
+			esac
+
+			[ -n "$network_ifname" ] && append bss_conf "ft_iface=$network_ifname" "$N"
+			append bss_conf "mobility_domain=$mobility_domain" "$N"
+			append bss_conf "ft_psk_generate_local=$ft_psk_generate_local" "$N"
+			append bss_conf "ft_over_ds=$ft_over_ds" "$N"
+			append bss_conf "reassociation_deadline=$reassociation_deadline" "$N"
+
+			if [ "$ft_psk_generate_local" -eq "0" ]; then
+				json_get_vars r0_key_lifetime r1_key_holder pmk_r1_push
+				json_get_values r0kh r0kh
+				json_get_values r1kh r1kh
+
+				set_default r0_key_lifetime 10000
+				set_default pmk_r1_push 0
+
+				[ -n "$r0kh" -a -n "$r1kh" ] || {
+					if [ -z "$auth_secret" -a -z "$key" ]; then
+						wireless_setup_vif_failed FT_KEY_CANT_BE_DERIVED
+						return 1
+					fi
+					ft_key=`echo -n "$mobility_domain/${auth_secret:-${key}}" | md5sum | awk '{print $1}'`
+
+					set_default r0kh "ff:ff:ff:ff:ff:ff,*,$ft_key"
+					set_default r1kh "00:00:00:00:00:00,00:00:00:00:00:00,$ft_key"
+				}
+
+				[ -n "$r1_key_holder" ] && append bss_conf "r1_key_holder=$r1_key_holder" "$N"
+				append bss_conf "r0_key_lifetime=$r0_key_lifetime" "$N"
+				append bss_conf "pmk_r1_push=$pmk_r1_push" "$N"
+
+				for kh in $r0kh; do
+					append bss_conf "r0kh=${kh//,/ }" "$N"
+				done
+				for kh in $r1kh; do
+					append bss_conf "r1kh=${kh//,/ }" "$N"
+				done
+			fi
+		fi
+
+		if [ -n "$network_bridge" -a "$rsn_preauth" = 1 ]; then
+			set_default auth_cache 1
+			append bss_conf "rsn_preauth=1" "$N"
+			append bss_conf "rsn_preauth_interfaces=$network_bridge" "$N"
+		else
+			case "$auth_type" in
+			sae|psk-sae|owe)
+				set_default auth_cache 1
+			;;
+			*)
+				set_default auth_cache 0
+			;;
+			esac
+		fi
+
+		append bss_conf "okc=$auth_cache" "$N"
+		[ "$auth_cache" = 0 -a "$fils" = 0 ] && append bss_conf "disable_pmksa_caching=1" "$N"
+
+		# RSN -> allow management frame protection
+		case "$ieee80211w" in
+			[012])
+				json_get_vars ieee80211w_mgmt_cipher ieee80211w_max_timeout ieee80211w_retry_timeout
+				append bss_conf "ieee80211w=$ieee80211w" "$N"
+				[ "$ieee80211w" -gt "0" ] && {
+					if [ "$auth_type" = "eap192" ]; then
+						append bss_conf "group_mgmt_cipher=BIP-GMAC-256" "$N"
+					else
+						append bss_conf "group_mgmt_cipher=${ieee80211w_mgmt_cipher:-AES-128-CMAC}" "$N"
+					fi
+					[ -n "$ieee80211w_max_timeout" ] && \
+						append bss_conf "assoc_sa_query_max_timeout=$ieee80211w_max_timeout" "$N"
+					[ -n "$ieee80211w_retry_timeout" ] && \
+						append bss_conf "assoc_sa_query_retry_timeout=$ieee80211w_retry_timeout" "$N"
+				}
+			;;
+		esac
+	fi
+
+	_macfile="/var/run/hostapd-$ifname.maclist"
+	case "$macfilter" in
+		allow)
+			append bss_conf "macaddr_acl=1" "$N"
+			append bss_conf "accept_mac_file=$_macfile" "$N"
+			# accept_mac_file can be used to set MAC to VLAN ID mapping
+			vlan_possible=1
+		;;
+		deny)
+			append bss_conf "macaddr_acl=0" "$N"
+			append bss_conf "deny_mac_file=$_macfile" "$N"
+		;;
+		*)
+			_macfile=""
+		;;
+	esac
+
+	[ -n "$_macfile" ] && {
+		json_get_vars macfile
+		json_get_values maclist maclist
+
+		rm -f "$_macfile"
+		(
+			for mac in $maclist; do
+				echo "$mac"
+			done
+			[ -n "$macfile" -a -f "$macfile" ] && cat "$macfile"
+		) > "$_macfile"
+	}
+
+	[ -n "$vlan_possible" -a -n "$dynamic_vlan" ] && {
+		json_get_vars vlan_naming vlan_tagged_interface vlan_bridge vlan_file vlan_no_bridge
+		set_default vlan_naming 1
+		[ -z "$vlan_file" ] && set_default vlan_file /var/run/hostapd-$ifname.vlan
+		append bss_conf "dynamic_vlan=$dynamic_vlan" "$N"
+		append bss_conf "vlan_naming=$vlan_naming" "$N"
+		if [ -n "$vlan_bridge" ]; then
+			append bss_conf "vlan_bridge=$vlan_bridge" "$N"
+		else
+			set_default vlan_no_bridge 1
+		fi
+		append bss_conf "vlan_no_bridge=$vlan_no_bridge" "$N"
+		[ -n "$vlan_tagged_interface" ] && \
+			append bss_conf "vlan_tagged_interface=$vlan_tagged_interface" "$N"
+		[ -n "$vlan_file" ] && {
+			[ -e "$vlan_file" ] || touch "$vlan_file"
+			append bss_conf "vlan_file=$vlan_file" "$N"
+		}
+	}
+
+	json_get_vars iw_enabled iw_internet iw_asra iw_esr iw_uesa iw_access_network_type
+	json_get_vars iw_hessid iw_venue_group iw_venue_type iw_network_auth_type
+	json_get_vars iw_roaming_consortium iw_domain_name iw_anqp_3gpp_cell_net iw_nai_realm
+	json_get_vars iw_anqp_elem iw_qos_map_set iw_ipaddr_type_availability iw_gas_address3
+	json_get_vars iw_venue_name iw_venue_url
+
+	set_default iw_enabled 0
+	if [ "$iw_enabled" = "1" ]; then
+		append bss_conf "interworking=1" "$N"
+		set_default iw_internet 1
+		set_default iw_asra 0
+		set_default iw_esr 0
+		set_default iw_uesa 0
+
+		append bss_conf "internet=$iw_internet" "$N"
+		append bss_conf "asra=$iw_asra" "$N"
+		append bss_conf "esr=$iw_esr" "$N"
+		append bss_conf "uesa=$iw_uesa" "$N"
+
+		[ -n "$iw_access_network_type" ] && \
+			append bss_conf "access_network_type=$iw_access_network_type" "$N"
+		[ -n "$iw_hessid" ] && append bss_conf "hessid=$iw_hessid" "$N"
+		[ -n "$iw_venue_group" ] && \
+			append bss_conf "venue_group=$iw_venue_group" "$N"
+		[ -n "$iw_venue_type" ] && append bss_conf "venue_type=$iw_venue_type" "$N"
+		[ -n "$iw_network_auth_type" ] && \
+			append bss_conf "network_auth_type=$iw_network_auth_type" "$N"
+		[ -n "$iw_gas_address3" ] && append bss_conf "gas_address3=$iw_gas_address3" "$N"
+
+		json_for_each_item append_iw_roaming_consortium iw_roaming_consortium
+		json_for_each_item append_iw_anqp_elem iw_anqp_elem
+		json_for_each_item append_iw_nai_realm iw_nai_realm
+		json_for_each_item append_iw_venue_name iw_venue_name
+		json_for_each_item append_iw_venue_url iw_venue_url
+
+		iw_domain_name_conf=
+		json_for_each_item append_iw_domain_name iw_domain_name
+		[ -n "$iw_domain_name_conf" ] && \
+			append bss_conf "domain_name=$iw_domain_name_conf" "$N"
+
+		iw_anqp_3gpp_cell_net_conf=
+		json_for_each_item append_iw_anqp_3gpp_cell_net iw_anqp_3gpp_cell_net
+		[ -n "$iw_anqp_3gpp_cell_net_conf" ] && \
+			append bss_conf "anqp_3gpp_cell_net=$iw_anqp_3gpp_cell_net_conf" "$N"
+	fi
+
+	set_default iw_qos_map_set 0,0,2,16,1,1,255,255,18,22,24,38,40,40,44,46,48,56
+	case "$iw_qos_map_set" in
+		*,*);;
+		*) iw_qos_map_set="";;
+	esac
+	[ -n "$iw_qos_map_set" ] && append bss_conf "qos_map_set=$iw_qos_map_set" "$N"
+
+	local hs20 disable_dgaf osen anqp_domain_id hs20_deauth_req_timeout \
+		osu_ssid hs20_wan_metrics hs20_operating_class hs20_t_c_filename hs20_t_c_timestamp \
+		hs20_t_c_server_url
+	json_get_vars hs20 disable_dgaf osen anqp_domain_id hs20_deauth_req_timeout \
+		osu_ssid hs20_wan_metrics hs20_operating_class hs20_t_c_filename hs20_t_c_timestamp \
+		hs20_t_c_server_url
+
+	set_default hs20 0
+	set_default disable_dgaf $hs20
+	set_default osen 0
+	set_default anqp_domain_id 0
+	set_default hs20_deauth_req_timeout 60
+	if [ "$hs20" = "1" ]; then
+		append bss_conf "hs20=1" "$N"
+		append_hs20_icons
+		append bss_conf "disable_dgaf=$disable_dgaf" "$N"
+		append bss_conf "osen=$osen" "$N"
+		append bss_conf "anqp_domain_id=$anqp_domain_id" "$N"
+		append bss_conf "hs20_deauth_req_timeout=$hs20_deauth_req_timeout" "$N"
+		[ -n "$osu_ssid" ] && append bss_conf "osu_ssid=$osu_ssid" "$N"
+		[ -n "$hs20_wan_metrics" ] && append bss_conf "hs20_wan_metrics=$hs20_wan_metrics" "$N"
+		[ -n "$hs20_operating_class" ] && append bss_conf "hs20_operating_class=$hs20_operating_class" "$N"
+		[ -n "$hs20_t_c_filename" ] && append bss_conf "hs20_t_c_filename=$hs20_t_c_filename" "$N"
+		[ -n "$hs20_t_c_timestamp" ] && append bss_conf "hs20_t_c_timestamp=$hs20_t_c_timestamp" "$N"
+		[ -n "$hs20_t_c_server_url" ] && append bss_conf "hs20_t_c_server_url=$hs20_t_c_server_url" "$N"
+		json_for_each_item append_hs20_oper_friendly_name hs20_oper_friendly_name
+		json_for_each_item append_hs20_conn_capab hs20_conn_capab
+		json_for_each_item append_osu_provider osu_provider
+		json_for_each_item append_operator_icon operator_icon
+	fi
+
+	if [ "$eap_server" = "1" ]; then
+		append bss_conf "eap_server=1" "$N"
+		append bss_conf "eap_server_erp=1" "$N"
+		[ -n "$eap_user_file" ] && append bss_conf "eap_user_file=$eap_user_file" "$N"
+		[ -n "$ca_cert" ] && append bss_conf "ca_cert=$ca_cert" "$N"
+		[ -n "$server_cert" ] && append bss_conf "server_cert=$server_cert" "$N"
+		[ -n "$private_key" ] && append bss_conf "private_key=$private_key" "$N"
+		[ -n "$private_key_passwd" ] && append bss_conf "private_key_passwd=$private_key_passwd" "$N"
+		[ -n "$server_id" ] && append bss_conf "server_id=$server_id" "$N"
+		[ -n "$radius_server_clients" ] && append bss_conf "radius_server_clients=$radius_server_clients" "$N"
+		[ -n "$radius_server_auth_port" ] && append bss_conf "radius_server_auth_port=$radius_server_auth_port" "$N"
+	fi
+
+	set_default multicast_to_unicast_all 0
+	if [ "$multicast_to_unicast_all" -gt 0 ]; then
+		append bss_conf "multicast_to_unicast=$multicast_to_unicast_all" "$N"
+	fi
+	set_default proxy_arp 0
+	if [ "$proxy_arp" -gt 0 ]; then
+		append bss_conf "proxy_arp=$proxy_arp" "$N"
+	fi
+
+	set_default per_sta_vif 0
+	if [ "$per_sta_vif" -gt 0 ]; then
+		append bss_conf "per_sta_vif=$per_sta_vif" "$N"
+	fi
+
+	if [ "$apup" -gt 0 ]; then
+		append bss_conf "apup=$apup" "$N"
+
+		local apup_peer_ifname_prefix
+		json_get_vars apup_peer_ifname_prefix
+		if [ -n "$apup_peer_ifname_prefix" ] ; then
+			append bss_conf "apup_peer_ifname_prefix=$apup_peer_ifname_prefix" "$N"
+		fi
+	fi
+
+	json_get_values opts hostapd_bss_options
+	for val in $opts; do
+		append bss_conf "$val" "$N"
+	done
+
+	append "$var" "$bss_conf" "$N"
+	return 0
+}
+
+hostapd_set_log_options() {
+	local var="$1"
+
+	local log_level log_80211 log_8021x log_radius log_wpa log_driver log_iapp log_mlme
+	json_get_vars log_level log_80211 log_8021x log_radius log_wpa log_driver log_iapp log_mlme
+
+	set_default log_level 2
+	set_default log_80211  1
+	set_default log_8021x  1
+	set_default log_radius 1
+	set_default log_wpa    1
+	set_default log_driver 1
+	set_default log_iapp   1
+	set_default log_mlme   1
+
+	local log_mask="$(( \
+		($log_80211  << 0) | \
+		($log_8021x  << 1) | \
+		($log_radius << 2) | \
+		($log_wpa    << 3) | \
+		($log_driver << 4) | \
+		($log_iapp   << 5) | \
+		($log_mlme   << 6)   \
+	))"
+
+	append "$var" "logger_syslog=$log_mask" "$N"
+	append "$var" "logger_syslog_level=$log_level" "$N"
+	append "$var" "logger_stdout=$log_mask" "$N"
+	append "$var" "logger_stdout_level=$log_level" "$N"
+
+	return 0
+}
+
+_wpa_supplicant_common() {
+	local ifname="$1"
+
+	_rpath="/var/run/wpa_supplicant"
+	_config="${_rpath}-$ifname.conf"
+}
+
+wpa_supplicant_teardown_interface() {
+	_wpa_supplicant_common "$1"
+	rm -rf "$_rpath/$1" "$_config"
+}
+
+wpa_supplicant_prepare_interface() {
+	local ifname="$1"
+	_w_driver="$2"
+
+	_wpa_supplicant_common "$1"
+
+	json_get_vars mode wds multi_ap
+
+	[ -n "$network_bridge" ] && {
+		fail=
+		case "$mode" in
+			adhoc)
+				fail=1
+			;;
+			sta)
+				[ "$wds" = 1 -o "$multi_ap" = 1 ] || fail=1
+			;;
+		esac
+
+		[ -n "$fail" ] && {
+			wireless_setup_vif_failed BRIDGE_NOT_ALLOWED
+			return 1
+		}
+	}
+
+	local ap_scan=
+
+	_w_mode="$mode"
+
+	[ "$mode" = adhoc ] && {
+		ap_scan="ap_scan=2"
+	}
+
+	local country_str=
+	[ -n "$country" ] && {
+		country_str="country=$country"
+	}
+
+	multiap_flag_file="${_config}.is_multiap"
+	if [ "$multi_ap" = "1" ]; then
+		touch "$multiap_flag_file"
+	else
+		[ -e "$multiap_flag_file" ] && rm "$multiap_flag_file"
+	fi
+	wpa_supplicant_teardown_interface "$ifname"
+	cat > "$_config" <<EOF
+${scan_list:+freq_list=$scan_list}
+$ap_scan
+$country_str
+EOF
+	return 0
+}
+
+wpa_supplicant_set_fixed_freq() {
+	local freq="$1"
+	local htmode="$2"
+
+	append network_data "fixed_freq=1" "$N$T"
+	append network_data "frequency=$freq" "$N$T"
+	case "$htmode" in
+		NOHT) append network_data "disable_ht=1" "$N$T";;
+		HE20|HT20|VHT20) append network_data "disable_ht40=1" "$N$T";;
+		HT40*|VHT40|VHT80|VHT160|HE40|HE80|HE160) append network_data "ht40=1" "$N$T";;
+	esac
+	case "$htmode" in
+		VHT*) append network_data "vht=1" "$N$T";;
+	esac
+	case "$htmode" in
+		HE80|VHT80) append network_data "max_oper_chwidth=1" "$N$T";;
+		HE160|VHT160) append network_data "max_oper_chwidth=2" "$N$T";;
+		HE20|HE40|VHT20|VHT40) append network_data "max_oper_chwidth=0" "$N$T";;
+		*) append network_data "disable_vht=1" "$N$T";;
+	esac
+}
+
+wpa_supplicant_add_network() {
+	local ifname="$1"
+	local freq="$2"
+	local htmode="$3"
+	local noscan="$4"
+
+	_wpa_supplicant_common "$1"
+	wireless_vif_parse_encryption
+
+	json_get_vars \
+		ssid bssid key \
+		basic_rate mcast_rate \
+		ieee80211w ieee80211r fils ocv \
+		multi_ap \
+		default_disabled
+
+	case "$auth_type" in
+		sae|owe|eap2|eap192)
+			set_default ieee80211w 2
+		;;
+		psk-sae)
+			set_default ieee80211w 1
+		;;
+	esac
+
+	set_default ieee80211r 0
+	set_default multi_ap 0
+	set_default default_disabled 0
+
+	local key_mgmt='NONE'
+	local network_data=
+	local T="	"
+
+	local scan_ssid="scan_ssid=1"
+	local freq wpa_key_mgmt
+
+	[ "$_w_mode" = "adhoc" ] && {
+		append network_data "mode=1" "$N$T"
+		[ -n "$freq" ] && wpa_supplicant_set_fixed_freq "$freq" "$htmode"
+		[ "$noscan" = "1" ] && append network_data "noscan=1" "$N$T"
+
+		scan_ssid="scan_ssid=0"
+
+		[ "$_w_driver" = "nl80211" ] ||	append wpa_key_mgmt "WPA-NONE"
+	}
+
+	[ "$_w_mode" = "mesh" ] && {
+		json_get_vars mesh_id mesh_fwding mesh_rssi_threshold encryption
+		[ -n "$mesh_id" ] && ssid="${mesh_id}"
+
+		append network_data "mode=5" "$N$T"
+		[ -n "$mesh_fwding" ] && append network_data "mesh_fwding=${mesh_fwding}" "$N$T"
+		[ -n "$mesh_rssi_threshold" ] && append network_data "mesh_rssi_threshold=${mesh_rssi_threshold}" "$N$T"
+		[ -n "$freq" ] && wpa_supplicant_set_fixed_freq "$freq" "$htmode"
+		[ "$noscan" = "1" ] && append network_data "noscan=1" "$N$T"
+		[ "$encryption" = "none" -o -z "$encryption" ] || append wpa_key_mgmt "SAE"
+		scan_ssid=""
+	}
+
+	[ "$_w_mode" = "sta" ] && {
+		[ "$multi_ap" = 1 ] && append network_data "multi_ap_backhaul_sta=1" "$N$T"
+		[ "$default_disabled" = 1 ] && append network_data "disabled=1" "$N$T"
+	}
+
+	[ -n "$ocv" ] && append network_data "ocv=$ocv" "$N$T"
+
+	case "$auth_type" in
+		none) ;;
+		owe)
+			hostapd_append_wpa_key_mgmt
+			key_mgmt="$wpa_key_mgmt"
+		;;
+		wep)
+			local wep_keyidx=0
+			hostapd_append_wep_key network_data
+			append network_data "wep_tx_keyidx=$wep_keyidx" "$N$T"
+		;;
+		wps)
+			key_mgmt='WPS'
+		;;
+		psk|sae|psk-sae)
+			local passphrase
+
+			if [ "$_w_mode" != "mesh" ]; then
+				hostapd_append_wpa_key_mgmt
+			fi
+
+			key_mgmt="$wpa_key_mgmt"
+
+			if [ "$_w_mode" = "mesh" ] || [ "$auth_type" = "sae" ]; then
+				passphrase="sae_password=\"${key}\""
+			else
+				if [ ${#key} -eq 64 ]; then
+					passphrase="psk=${key}"
+				else
+					passphrase="psk=\"${key}\""
+				fi
+			fi
+			append network_data "$passphrase" "$N$T"
+		;;
+		eap|eap2|eap192)
+			hostapd_append_wpa_key_mgmt
+			key_mgmt="$wpa_key_mgmt"
+
+			json_get_vars eap_type identity anonymous_identity ca_cert ca_cert_usesystem
+
+			[ "$fils" -gt 0 ] && append network_data "erp=1" "$N$T"
+			if [ "$ca_cert_usesystem" -eq "1" -a -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+				append network_data "ca_cert=\"/etc/ssl/certs/ca-certificates.crt\"" "$N$T"
+			else
+				[ -n "$ca_cert" ] && append network_data "ca_cert=\"$ca_cert\"" "$N$T"
+			fi
+			[ -n "$identity" ] && append network_data "identity=\"$identity\"" "$N$T"
+			[ -n "$anonymous_identity" ] && append network_data "anonymous_identity=\"$anonymous_identity\"" "$N$T"
+			case "$eap_type" in
+				tls)
+					json_get_vars client_cert priv_key priv_key_pwd
+					append network_data "client_cert=\"$client_cert\"" "$N$T"
+					append network_data "private_key=\"$priv_key\"" "$N$T"
+					append network_data "private_key_passwd=\"$priv_key_pwd\"" "$N$T"
+
+					json_get_vars subject_match
+					[ -n "$subject_match" ] && append network_data "subject_match=\"$subject_match\"" "$N$T"
+
+					json_get_values altsubject_match altsubject_match
+					if [ -n "$altsubject_match" ]; then
+						local list=
+						for x in $altsubject_match; do
+							append list "$x" ";"
+						done
+						append network_data "altsubject_match=\"$list\"" "$N$T"
+					fi
+
+					json_get_values domain_match domain_match
+					if [ -n "$domain_match" ]; then
+						local list=
+						for x in $domain_match; do
+							append list "$x" ";"
+						done
+						append network_data "domain_match=\"$list\"" "$N$T"
+					fi
+
+					json_get_values domain_suffix_match domain_suffix_match
+					if [ -n "$domain_suffix_match" ]; then
+						local list=
+						for x in $domain_suffix_match; do
+							append list "$x" ";"
+						done
+						append network_data "domain_suffix_match=\"$list\"" "$N$T"
+					fi
+				;;
+				fast|peap|ttls)
+					json_get_vars auth password ca_cert2 ca_cert2_usesystem client_cert2 priv_key2 priv_key2_pwd
+					set_default auth MSCHAPV2
+
+					if [ "$auth" = "EAP-TLS" ]; then
+						if [ "$ca_cert2_usesystem" -eq "1" -a -f "/etc/ssl/certs/ca-certificates.crt" ]; then
+							append network_data "ca_cert2=\"/etc/ssl/certs/ca-certificates.crt\"" "$N$T"
+						else
+							[ -n "$ca_cert2" ] && append network_data "ca_cert2=\"$ca_cert2\"" "$N$T"
+						fi
+						append network_data "client_cert2=\"$client_cert2\"" "$N$T"
+						append network_data "private_key2=\"$priv_key2\"" "$N$T"
+						append network_data "private_key2_passwd=\"$priv_key2_pwd\"" "$N$T"
+					else
+						append network_data "password=\"$password\"" "$N$T"
+					fi
+
+					json_get_vars subject_match
+					[ -n "$subject_match" ] && append network_data "subject_match=\"$subject_match\"" "$N$T"
+
+					json_get_values altsubject_match altsubject_match
+					if [ -n "$altsubject_match" ]; then
+						local list=
+						for x in $altsubject_match; do
+							append list "$x" ";"
+						done
+						append network_data "altsubject_match=\"$list\"" "$N$T"
+					fi
+
+					json_get_values domain_match domain_match
+					if [ -n "$domain_match" ]; then
+						local list=
+						for x in $domain_match; do
+							append list "$x" ";"
+						done
+						append network_data "domain_match=\"$list\"" "$N$T"
+					fi
+
+					json_get_values domain_suffix_match domain_suffix_match
+					if [ -n "$domain_suffix_match" ]; then
+						local list=
+						for x in $domain_suffix_match; do
+							append list "$x" ";"
+						done
+						append network_data "domain_suffix_match=\"$list\"" "$N$T"
+					fi
+
+					phase2proto="auth="
+					case "$auth" in
+						"auth"*)
+							phase2proto=""
+						;;
+						"EAP-"*)
+							auth="$(echo $auth | cut -b 5- )"
+							[ "$eap_type" = "ttls" ] &&
+								phase2proto="autheap="
+							json_get_vars subject_match2
+							[ -n "$subject_match2" ] && append network_data "subject_match2=\"$subject_match2\"" "$N$T"
+
+							json_get_values altsubject_match2 altsubject_match2
+							if [ -n "$altsubject_match2" ]; then
+								local list=
+								for x in $altsubject_match2; do
+									append list "$x" ";"
+								done
+								append network_data "altsubject_match2=\"$list\"" "$N$T"
+							fi
+
+							json_get_values domain_match2 domain_match2
+							if [ -n "$domain_match2" ]; then
+								local list=
+								for x in $domain_match2; do
+									append list "$x" ";"
+								done
+								append network_data "domain_match2=\"$list\"" "$N$T"
+							fi
+
+							json_get_values domain_suffix_match2 domain_suffix_match2
+							if [ -n "$domain_suffix_match2" ]; then
+								local list=
+								for x in $domain_suffix_match2; do
+									append list "$x" ";"
+								done
+								append network_data "domain_suffix_match2=\"$list\"" "$N$T"
+							fi
+						;;
+					esac
+					append network_data "phase2=\"$phase2proto$auth\"" "$N$T"
+				;;
+			esac
+			append network_data "eap=$(echo $eap_type | tr 'a-z' 'A-Z')" "$N$T"
+		;;
+	esac
+
+	[ "$wpa_cipher" = GCMP ] && {
+		append network_data "pairwise=GCMP" "$N$T"
+		append network_data "group=GCMP" "$N$T"
+	}
+
+	[ "$mode" = mesh ] || {
+		case "$wpa" in
+			1)
+				append network_data "proto=WPA" "$N$T"
+			;;
+			2)
+				append network_data "proto=RSN" "$N$T"
+			;;
+		esac
+
+		case "$ieee80211w" in
+			[012])
+				[ "$wpa" -ge 2 ] && append network_data "ieee80211w=$ieee80211w" "$N$T"
+			;;
+		esac
+	}
+	[ -n "$bssid" ] && append network_data "bssid=$bssid" "$N$T"
+	[ -n "$beacon_int" ] && append network_data "beacon_int=$beacon_int" "$N$T"
+
+	local bssid_blacklist bssid_whitelist
+	json_get_values bssid_blacklist bssid_blacklist
+	json_get_values bssid_whitelist bssid_whitelist
+
+	[ -n "$bssid_blacklist" ] && append network_data "bssid_blacklist=$bssid_blacklist" "$N$T"
+	[ -n "$bssid_whitelist" ] && append network_data "bssid_whitelist=$bssid_whitelist" "$N$T"
+
+	[ -n "$basic_rate" ] && {
+		local br rate_list=
+		for br in $basic_rate; do
+			wpa_supplicant_add_rate rate_list "$br"
+		done
+		[ -n "$rate_list" ] && append network_data "rates=$rate_list" "$N$T"
+	}
+
+	[ -n "$mcast_rate" ] && {
+		local mc_rate=
+		wpa_supplicant_add_rate mc_rate "$mcast_rate"
+		append network_data "mcast_rate=$mc_rate" "$N$T"
+	}
+
+	if [ "$key_mgmt" = "WPS" ]; then
+		echo "wps_cred_processing=1" >> "$_config"
+	else
+		cat >> "$_config" <<EOF
+network={
+	$scan_ssid
+	ssid="$ssid"
+	key_mgmt=$key_mgmt
+	$network_data
+}
+EOF
+	fi
+	return 0
+}
diff --git a/package/network/config/wifi-scripts/files/lib/netifd/netifd-wireless.sh b/package/network/config/wifi-scripts/files/lib/netifd/netifd-wireless.sh
new file mode 100644
index 0000000..c3772bb
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/lib/netifd/netifd-wireless.sh
@@ -0,0 +1,440 @@
+NETIFD_MAIN_DIR="${NETIFD_MAIN_DIR:-/lib/netifd}"
+
+. /usr/share/libubox/jshn.sh
+. $NETIFD_MAIN_DIR/utils.sh
+
+CMD_UP=0
+CMD_SET_DATA=1
+CMD_PROCESS_ADD=2
+CMD_PROCESS_KILL_ALL=3
+CMD_SET_RETRY=4
+
+add_driver() {
+	return
+}
+
+wireless_setup_vif_failed() {
+	local error="$1"
+	echo "Interface $_w_iface setup failed: $error"
+}
+
+wireless_setup_failed() {
+	local error="$1"
+
+	echo "Device setup failed: $error"
+	wireless_set_retry 0
+}
+
+prepare_key_wep() {
+	local key="$1"
+	local hex=1
+
+	echo -n "$key" | grep -qE "[^a-fA-F0-9]" && hex=0
+	[ "${#key}" -eq 10 -a $hex -eq 1 ] || \
+	[ "${#key}" -eq 26 -a $hex -eq 1 ] || {
+		[ "${key:0:2}" = "s:" ] && key="${key#s:}"
+		key="$(echo -n "$key" | hexdump -ve '1/1 "%02x" ""')"
+	}
+	echo "$key"
+}
+
+_wdev_prepare_channel() {
+	json_get_vars channel band hwmode
+
+	auto_channel=0
+	enable_ht=0
+	htmode=
+	hwmode="${hwmode##11}"
+
+	case "$channel" in
+		""|0|auto)
+			channel=0
+			auto_channel=1
+		;;
+		[0-9]*) ;;
+		*)
+			wireless_setup_failed "INVALID_CHANNEL"
+		;;
+	esac
+
+	case "$hwmode" in
+		a|b|g|ad) ;;
+		*)
+			if [ "$channel" -gt 14 ]; then
+				hwmode=a
+			else
+				hwmode=g
+			fi
+		;;
+	esac
+
+	case "$band" in
+		2g) hwmode=g;;
+		5g|6g) hwmode=a;;
+		60g) hwmode=ad;;
+		*)
+			case "$hwmode" in
+				*a) band=5g;;
+				*ad) band=60g;;
+				*b|*g) band=2g;;
+			esac
+		;;
+	esac
+}
+
+_wdev_handler() {
+	json_load "$data"
+
+	json_select config
+	_wdev_prepare_channel
+	json_select ..
+
+	eval "drv_$1_$2 \"$interface\""
+}
+
+_wdev_msg_call() {
+	local old_cb
+
+	json_set_namespace wdev old_cb
+	"$@"
+	json_set_namespace $old_cb
+}
+
+_wdev_wrapper() {
+	while [ -n "$1" ]; do
+		eval "$1() { _wdev_msg_call _$1 \"\$@\"; }"
+		shift
+	done
+}
+
+_wdev_notify_init() {
+	local command="$1"; shift;
+
+	json_init
+	json_add_int "command" "$command"
+	json_add_string "device" "$__netifd_device"
+	while [ -n "$1" ]; do
+		local name="$1"; shift
+		local value="$1"; shift
+		json_add_string "$name" "$value"
+	done
+	json_add_object "data"
+}
+
+_wdev_notify() {
+	local options="$1"
+
+	json_close_object
+	ubus $options call network.wireless notify "$(json_dump)"
+}
+
+_wdev_add_variables() {
+	while [ -n "$1" ]; do
+		local var="${1%%=*}"
+		local val="$1"
+		shift
+		[[ "$var" = "$val" ]] && continue
+		val="${val#*=}"
+		json_add_string "$var" "$val"
+	done
+}
+
+_wireless_add_vif() {
+	local name="$1"; shift
+	local ifname="$1"; shift
+
+	_wdev_notify_init $CMD_SET_DATA "interface" "$name"
+	json_add_string "ifname" "$ifname"
+	_wdev_add_variables "$@"
+	_wdev_notify
+}
+
+_wireless_add_vlan() {
+	local name="$1"; shift
+	local ifname="$1"; shift
+
+	_wdev_notify_init $CMD_SET_DATA interface "$__cur_interface" "vlan" "$name"
+	json_add_string "ifname" "$ifname"
+	_wdev_add_variables "$@"
+	_wdev_notify
+}
+
+_wireless_set_up() {
+	_wdev_notify_init $CMD_UP
+	_wdev_notify
+}
+
+_wireless_set_data() {
+	_wdev_notify_init $CMD_SET_DATA
+	_wdev_add_variables "$@"
+	_wdev_notify
+}
+
+_wireless_add_process() {
+	_wdev_notify_init $CMD_PROCESS_ADD
+	local exe="$2"
+	[ -L "$exe" ] && exe="$(readlink -f "$exe")"
+	json_add_int pid "$1"
+	json_add_string exe "$exe"
+	[ -n "$3" ] && json_add_boolean required 1
+	[ -n "$4" ] && json_add_boolean keep 1
+	exe2="$(readlink -f /proc/$1/exe)"
+	[ "$exe" != "$exe2" ] && echo "WARNING (wireless_add_process): executable path $exe does not match process $1 path ($exe2)"
+	_wdev_notify
+}
+
+_wireless_process_kill_all() {
+	_wdev_notify_init $CMD_PROCESS_KILL_ALL
+	[ -n "$1" ] && json_add_int signal "$1"
+	_wdev_notify
+}
+
+_wireless_set_retry() {
+	_wdev_notify_init $CMD_SET_RETRY
+	json_add_int retry "$1"
+	_wdev_notify
+}
+
+_wdev_wrapper \
+	wireless_add_vif \
+	wireless_add_vlan \
+	wireless_set_up \
+	wireless_set_data \
+	wireless_add_process \
+	wireless_process_kill_all \
+	wireless_set_retry \
+
+wireless_vif_parse_encryption() {
+	json_get_vars encryption
+	set_default encryption none
+
+	auth_mode_open=1
+	auth_mode_shared=0
+	auth_type=none
+
+	if [ "$hwmode" = "ad" ]; then
+		wpa_cipher="GCMP"
+	else
+		wpa_cipher="CCMP"
+	fi
+
+	case "$encryption" in
+		*tkip+aes|*tkip+ccmp|*aes+tkip|*ccmp+tkip) wpa_cipher="CCMP TKIP";;
+		*ccmp256) wpa_cipher="CCMP-256";;
+		*aes|*ccmp) wpa_cipher="CCMP";;
+		*tkip) wpa_cipher="TKIP";;
+		*gcmp256) wpa_cipher="GCMP-256";;
+		*gcmp) wpa_cipher="GCMP";;
+		wpa3-192*) wpa_cipher="GCMP-256";;
+	esac
+
+	# 802.11n requires CCMP for WPA
+	[ "$enable_ht:$wpa_cipher" = "1:TKIP" ] && wpa_cipher="CCMP TKIP"
+
+	# Examples:
+	# psk-mixed/tkip    => WPA1+2 PSK, TKIP
+	# wpa-psk2/tkip+aes => WPA2 PSK, CCMP+TKIP
+	# wpa2/tkip+aes     => WPA2 RADIUS, CCMP+TKIP
+
+	case "$encryption" in
+		wpa2*|wpa3*|*psk2*|psk3*|sae*|owe*)
+			wpa=2
+		;;
+		wpa*mixed*|*psk*mixed*)
+			wpa=3
+		;;
+		wpa*|*psk*)
+			wpa=1
+		;;
+		*)
+			wpa=0
+			wpa_cipher=
+		;;
+	esac
+	wpa_pairwise="$wpa_cipher"
+
+	case "$encryption" in
+		owe*)
+			auth_type=owe
+		;;
+		wpa3-192*)
+			auth_type=eap192
+		;;
+		wpa3-mixed*)
+			auth_type=eap-eap2
+		;;
+		wpa3*)
+			auth_type=eap2
+		;;
+		psk3-mixed*|sae-mixed*)
+			auth_type=psk-sae
+		;;
+		psk3*|sae*)
+			auth_type=sae
+		;;
+		*psk*)
+			auth_type=psk
+		;;
+		*wpa*|*8021x*)
+			auth_type=eap
+		;;
+		*wep*)
+			auth_type=wep
+			case "$encryption" in
+				*shared*)
+					auth_mode_open=0
+					auth_mode_shared=1
+				;;
+				*mixed*)
+					auth_mode_shared=1
+				;;
+			esac
+		;;
+	esac
+
+	case "$encryption" in
+		*osen*)
+			auth_osen=1
+		;;
+	esac
+}
+
+_wireless_set_brsnoop_isolation() {
+	local multicast_to_unicast="$1"
+	local isolate
+
+	json_get_vars isolate proxy_arp
+
+	[ ${isolate:-0} -gt 0 -o -z "$network_bridge" ] && return
+	[ ${multicast_to_unicast:-1} -gt 0 -o ${proxy_arp:-0} -gt 0 ] && json_add_boolean isolate 1
+}
+
+for_each_interface() {
+	local _w_types="$1"; shift
+	local _w_ifaces _w_iface
+	local _w_type
+	local _w_found
+
+	local multicast_to_unicast
+
+	json_get_keys _w_ifaces interfaces
+	json_select interfaces
+	for _w_iface in $_w_ifaces; do
+		json_select "$_w_iface"
+		if [ -n "$_w_types" ]; then
+			json_get_var network_bridge bridge
+			json_get_var network_ifname bridge-ifname
+			json_get_var multicast_to_unicast multicast_to_unicast
+			json_select config
+			_wireless_set_brsnoop_isolation "$multicast_to_unicast"
+			json_get_var _w_type mode
+			json_select ..
+			_w_types=" $_w_types "
+			[[ "${_w_types%$_w_type*}" = "$_w_types" ]] && {
+				json_select ..
+				continue
+			}
+		fi
+		__cur_interface="$_w_iface"
+		"$@" "$_w_iface"
+		json_select ..
+	done
+	json_select ..
+}
+
+for_each_vlan() {
+	local _w_vlans _w_vlan
+
+	json_get_keys _w_vlans vlans
+	json_select vlans
+	for _w_vlan in $_w_vlans; do
+		json_select "$_w_vlan"
+		json_select config
+		"$@" "$_w_vlan"
+		json_select ..
+		json_select ..
+	done
+	json_select ..
+}
+
+for_each_station() {
+	local _w_stas _w_sta
+
+	json_get_keys _w_stas stas
+	json_select stas
+	for _w_sta in $_w_stas; do
+		json_select "$_w_sta"
+		json_select config
+		"$@" "$_w_sta"
+		json_select ..
+		json_select ..
+	done
+	json_select ..
+}
+
+_wdev_common_device_config() {
+	config_add_string channel hwmode band htmode noscan
+}
+
+_wdev_common_iface_config() {
+	config_add_string mode ssid encryption 'key:wpakey'
+	config_add_boolean bridge_isolate
+	config_add_array tags
+}
+
+_wdev_common_vlan_config() {
+	config_add_string name vid iface
+	config_add_boolean bridge_isolate
+}
+
+_wdev_common_station_config() {
+	config_add_string mac key vid iface
+}
+
+init_wireless_driver() {
+	name="$1"; shift
+	cmd="$1"; shift
+
+	case "$cmd" in
+		dump)
+			add_driver() {
+				eval "drv_$1_cleanup"
+
+				json_init
+				json_add_string name "$1"
+
+				json_add_array device
+				_wdev_common_device_config
+				eval "drv_$1_init_device_config"
+				json_close_array
+
+				json_add_array iface
+				_wdev_common_iface_config
+				eval "drv_$1_init_iface_config"
+				json_close_array
+
+				json_add_array vlan
+				_wdev_common_vlan_config
+				eval "drv_$1_init_vlan_config"
+				json_close_array
+
+				json_add_array station
+				_wdev_common_station_config
+				eval "drv_$1_init_station_config"
+				json_close_array
+
+				json_dump
+			}
+		;;
+		setup|teardown)
+			interface="$1"; shift
+			data="$1"; shift
+			export __netifd_device="$interface"
+
+			add_driver() {
+				[[ "$name" == "$1" ]] || return 0
+				_wdev_handler "$1" "$cmd"
+			}
+		;;
+	esac
+}
diff --git a/package/network/config/wifi-scripts/files/lib/netifd/wireless/mac80211.sh b/package/network/config/wifi-scripts/files/lib/netifd/wireless/mac80211.sh
new file mode 100755
index 0000000..6c2d735
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/lib/netifd/wireless/mac80211.sh
@@ -0,0 +1,1268 @@
+#!/bin/sh
+. /lib/netifd/netifd-wireless.sh
+. /lib/netifd/hostapd.sh
+. /lib/functions/system.sh
+
+init_wireless_driver "$@"
+
+MP_CONFIG_INT="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"
+MP_CONFIG_BOOL="mesh_auto_open_plinks mesh_fwding"
+MP_CONFIG_STRING="mesh_power_mode"
+
+wdev_tool() {
+	ucode /usr/share/hostap/wdev.uc "$@"
+}
+
+ubus_call() {
+	flock /var/run/hostapd.lock ubus call "$@"
+}
+
+drv_mac80211_init_device_config() {
+	hostapd_common_add_device_config
+
+	config_add_string path phy 'macaddr:macaddr'
+	config_add_string tx_burst
+	config_add_string distance
+	config_add_string ifname_prefix
+	config_add_string macaddr_base
+	config_add_int radio beacon_int chanbw frag rts
+	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
+	config_add_array scan_list
+	config_add_boolean \
+		rxldpc \
+		short_gi_80 \
+		short_gi_160 \
+		tx_stbc_2by1 \
+		su_beamformer \
+		su_beamformee \
+		mu_beamformer \
+		mu_beamformee \
+		he_su_beamformer \
+		he_su_beamformee \
+		he_mu_beamformer \
+		vht_txop_ps \
+		htc_vht \
+		rx_antenna_pattern \
+		tx_antenna_pattern \
+		he_spr_sr_control \
+		he_spr_psr_enabled \
+		he_bss_color_enabled \
+		he_twt_required
+	config_add_int \
+		beamformer_antennas \
+		beamformee_antennas \
+		vht_max_a_mpdu_len_exp \
+		vht_max_mpdu \
+		vht_link_adapt \
+		vht160 \
+		rx_stbc \
+		tx_stbc \
+		he_bss_color \
+		he_spr_non_srg_obss_pd_max_offset
+	config_add_boolean \
+		ldpc \
+		greenfield \
+		short_gi_20 \
+		short_gi_40 \
+		max_amsdu \
+		dsss_cck_40
+}
+
+drv_mac80211_init_iface_config() {
+	hostapd_common_add_bss_config
+
+	config_add_string 'macaddr:macaddr' ifname
+
+	config_add_boolean wds powersave enable
+	config_add_string wds_bridge
+	config_add_int maxassoc
+	config_add_int max_listen_int
+	config_add_int dtim_period
+	config_add_int start_disabled
+
+	# mesh
+	config_add_string mesh_id
+	config_add_int $MP_CONFIG_INT
+	config_add_boolean $MP_CONFIG_BOOL
+	config_add_string $MP_CONFIG_STRING
+}
+
+mac80211_add_capabilities() {
+	local __var="$1"; shift
+	local __mask="$1"; shift
+	local __out= oifs
+
+	oifs="$IFS"
+	IFS=:
+	for capab in "$@"; do
+		set -- $capab
+
+		[ "$(($4))" -gt 0 ] || continue
+		[ "$(($__mask & $2))" -eq "$((${3:-$2}))" ] || continue
+		__out="$__out[$1]"
+	done
+	IFS="$oifs"
+
+	export -n -- "$__var=$__out"
+}
+
+mac80211_add_he_capabilities() {
+	local __out= oifs
+
+	oifs="$IFS"
+	IFS=:
+	for capab in "$@"; do
+		set -- $capab
+		[ "$(($4))" -gt 0 ] || continue
+		[ "$(((0x$2) & $3))" -gt 0 ] || {
+			eval "$1=0"
+			continue
+		}
+		append base_cfg "$1=1" "$N"
+	done
+	IFS="$oifs"
+}
+
+mac80211_hostapd_setup_base() {
+	local phy="$1"
+
+	json_select config
+
+	[ "$auto_channel" -gt 0 ] && channel=acs_survey
+
+	[ "$auto_channel" -gt 0 ] && json_get_vars acs_exclude_dfs
+	[ -n "$acs_exclude_dfs" ] && [ "$acs_exclude_dfs" -gt 0 ] &&
+		append base_cfg "acs_exclude_dfs=1" "$N"
+
+	json_get_vars noscan ht_coex min_tx_power:0 tx_burst
+	json_get_values ht_capab_list ht_capab
+	json_get_values channel_list channels
+
+	[ "$auto_channel" = 0 ] && [ -z "$channel_list" ] && \
+		channel_list="$channel"
+
+	[ "$min_tx_power" -gt 0 ] && append base_cfg "min_tx_power=$min_tx_power" "$N"
+
+	set_default noscan 0
+
+	[ "$noscan" -gt 0 ] && hostapd_noscan=1
+	[ "$tx_burst" = 0 ] && tx_burst=
+
+	chan_ofs=0
+	[ "$band" = "6g" ] && chan_ofs=1
+
+	if [ "$band" != "6g" ]; then
+		ieee80211n=1
+		ht_capab=
+		case "$htmode" in
+			VHT20|HT20|HE20|EHT20) ;;
+			HT40*|VHT40|VHT80|VHT160|HE40|HE80|HE160|EHT40|EHT80|EHT160)
+				case "$hwmode" in
+					a)
+						case "$(( (($channel / 4) + $chan_ofs) % 2 ))" in
+							1) ht_capab="[HT40+]";;
+							0) ht_capab="[HT40-]";;
+						esac
+					;;
+					*)
+						case "$htmode" in
+							HT40+) ht_capab="[HT40+]";;
+							HT40-) ht_capab="[HT40-]";;
+							*)
+								if [ "$channel" -lt 7 ]; then
+									ht_capab="[HT40+]"
+								else
+									ht_capab="[HT40-]"
+								fi
+							;;
+						esac
+					;;
+				esac
+				[ "$auto_channel" -gt 0 ] && ht_capab="[HT40+]"
+			;;
+			*) ieee80211n= ;;
+		esac
+
+		[ -n "$ieee80211n" ] && {
+			append base_cfg "ieee80211n=1" "$N"
+
+			set_default ht_coex 0
+			append base_cfg "ht_coex=$ht_coex" "$N"
+
+			json_get_vars \
+				ldpc:1 \
+				greenfield:0 \
+				short_gi_20:1 \
+				short_gi_40:1 \
+				tx_stbc:1 \
+				rx_stbc:3 \
+				max_amsdu:1 \
+				dsss_cck_40:1
+
+			ht_cap_mask=0
+			for cap in $(iw phy "$phy" info | grep -E '^\s*Capabilities:' | cut -d: -f2); do
+				ht_cap_mask="$(($ht_cap_mask | $cap))"
+			done
+
+			cap_rx_stbc=$((($ht_cap_mask >> 8) & 3))
+			[ "$rx_stbc" -lt "$cap_rx_stbc" ] && cap_rx_stbc="$rx_stbc"
+			ht_cap_mask="$(( ($ht_cap_mask & ~(0x300)) | ($cap_rx_stbc << 8) ))"
+
+			mac80211_add_capabilities ht_capab_flags $ht_cap_mask \
+				LDPC:0x1::$ldpc \
+				GF:0x10::$greenfield \
+				SHORT-GI-20:0x20::$short_gi_20 \
+				SHORT-GI-40:0x40::$short_gi_40 \
+				TX-STBC:0x80::$tx_stbc \
+				RX-STBC1:0x300:0x100:1 \
+				RX-STBC12:0x300:0x200:1 \
+				RX-STBC123:0x300:0x300:1 \
+				MAX-AMSDU-7935:0x800::$max_amsdu \
+				DSSS_CCK-40:0x1000::$dsss_cck_40
+
+			ht_capab="$ht_capab$ht_capab_flags"
+			[ -n "$ht_capab" ] && append base_cfg "ht_capab=$ht_capab" "$N"
+		}
+	fi
+
+	# 802.11ac
+	enable_ac=0
+	vht_oper_chwidth=0
+	vht_center_seg0=
+
+	idx="$channel"
+	case "$htmode" in
+		VHT20|HE20|EHT20) enable_ac=1;;
+		VHT40|HE40|EHT40)
+			case "$(( (($channel / 4) + $chan_ofs) % 2 ))" in
+				1) idx=$(($channel + 2));;
+				0) idx=$(($channel - 2));;
+			esac
+			enable_ac=1
+			vht_center_seg0=$idx
+		;;
+		VHT80|HE80|EHT80)
+			case "$(( (($channel / 4) + $chan_ofs) % 4 ))" in
+				1) idx=$(($channel + 6));;
+				2) idx=$(($channel + 2));;
+				3) idx=$(($channel - 2));;
+				0) idx=$(($channel - 6));;
+			esac
+			enable_ac=1
+			vht_oper_chwidth=1
+			vht_center_seg0=$idx
+		;;
+		VHT160|HE160|EHT160|EHT320)
+			if [ "$band" = "6g" ]; then
+				case "$channel" in
+					1|5|9|13|17|21|25|29) idx=15;;
+					33|37|41|45|49|53|57|61) idx=47;;
+					65|69|73|77|81|85|89|93) idx=79;;
+					97|101|105|109|113|117|121|125) idx=111;;
+					129|133|137|141|145|149|153|157) idx=143;;
+					161|165|169|173|177|181|185|189) idx=175;;
+					193|197|201|205|209|213|217|221) idx=207;;
+				esac
+			else
+				case "$channel" in
+					36|40|44|48|52|56|60|64) idx=50;;
+					100|104|108|112|116|120|124|128) idx=114;;
+					149|153|157|161|165|169|173|177) idx=163;;
+				esac
+			fi
+			enable_ac=1
+			vht_oper_chwidth=2
+			vht_center_seg0=$idx
+		;;
+	esac
+	[ "$band" = "5g" ] && {
+		json_get_vars background_radar:0
+
+		[ "$background_radar" -eq 1 ] && append base_cfg "enable_background_radar=1" "$N"
+	}
+
+	eht_oper_chwidth=$vht_oper_chwidth
+	eht_center_seg0=$vht_center_seg0
+
+	[ "$band" = "6g" ] && {
+		op_class=
+		case "$htmode" in
+			HE20|EHT20) op_class=131;;
+			EHT320)
+				case "$channel" in
+					1|5|9|13|17|21|25|29|33|37|41|45|49|53|57|61) idx=31;;
+					65|69|73|77|81|85|89|93|97|101|105|109|113|117|121|125) idx=95;;
+					129|133|137|141|145|149|153|157|161|165|169|173|177|181|185|189) idx=159;;
+					193|197|201|205|209|213|217|221) idx=191;;
+				esac
+
+				op_class=137
+				eht_center_seg0=$idx
+				eht_oper_chwidth=9
+			;;
+			HE*|EHT*) op_class=$((132 + $vht_oper_chwidth));;
+		esac
+		[ -n "$op_class" ] && append base_cfg "op_class=$op_class" "$N"
+	}
+	[ "$hwmode" = "a" ] || enable_ac=0
+	[ "$band" = "6g" ] && enable_ac=0
+
+	if [ "$enable_ac" != "0" ]; then
+		json_get_vars \
+			rxldpc:1 \
+			short_gi_80:1 \
+			short_gi_160:1 \
+			tx_stbc_2by1:1 \
+			su_beamformer:1 \
+			su_beamformee:1 \
+			mu_beamformer:1 \
+			mu_beamformee:1 \
+			vht_txop_ps:1 \
+			htc_vht:1 \
+			beamformee_antennas:4 \
+			beamformer_antennas:4 \
+			rx_antenna_pattern:1 \
+			tx_antenna_pattern:1 \
+			vht_max_a_mpdu_len_exp:7 \
+			vht_max_mpdu:11454 \
+			rx_stbc:4 \
+			vht_link_adapt:3 \
+			vht160:2
+
+		set_default tx_burst 2.0
+		append base_cfg "ieee80211ac=1" "$N"
+		vht_cap=0
+		for cap in $(iw phy "$phy" info | awk -F "[()]" '/VHT Capabilities/ { print $2 }'); do
+			vht_cap="$(($vht_cap | $cap))"
+		done
+
+		append base_cfg "vht_oper_chwidth=$vht_oper_chwidth" "$N"
+		append base_cfg "vht_oper_centr_freq_seg0_idx=$vht_center_seg0" "$N"
+
+		cap_rx_stbc=$((($vht_cap >> 8) & 7))
+		[ "$rx_stbc" -lt "$cap_rx_stbc" ] && cap_rx_stbc="$rx_stbc"
+		vht_cap="$(( ($vht_cap & ~(0x700)) | ($cap_rx_stbc << 8) ))"
+
+		[ "$vht_oper_chwidth" -lt 2 ] && {
+			vht160=0
+			short_gi_160=0
+		}
+
+		mac80211_add_capabilities vht_capab $vht_cap \
+			RXLDPC:0x10::$rxldpc \
+			SHORT-GI-80:0x20::$short_gi_80 \
+			SHORT-GI-160:0x40::$short_gi_160 \
+			TX-STBC-2BY1:0x80::$tx_stbc_2by1 \
+			SU-BEAMFORMER:0x800::$su_beamformer \
+			SU-BEAMFORMEE:0x1000::$su_beamformee \
+			MU-BEAMFORMER:0x80000::$mu_beamformer \
+			MU-BEAMFORMEE:0x100000::$mu_beamformee \
+			VHT-TXOP-PS:0x200000::$vht_txop_ps \
+			HTC-VHT:0x400000::$htc_vht \
+			RX-ANTENNA-PATTERN:0x10000000::$rx_antenna_pattern \
+			TX-ANTENNA-PATTERN:0x20000000::$tx_antenna_pattern \
+			RX-STBC-1:0x700:0x100:1 \
+			RX-STBC-12:0x700:0x200:1 \
+			RX-STBC-123:0x700:0x300:1 \
+			RX-STBC-1234:0x700:0x400:1 \
+
+		[ "$(($vht_cap & 0x800))" -gt 0 -a "$su_beamformer" -gt 0 ] && {
+			cap_ant="$(( ( ($vht_cap >> 16) & 3 ) + 1 ))"
+			[ "$cap_ant" -gt "$beamformer_antennas" ] && cap_ant="$beamformer_antennas"
+			[ "$cap_ant" -gt 1 ] && vht_capab="$vht_capab[SOUNDING-DIMENSION-$cap_ant]"
+		}
+
+		[ "$(($vht_cap & 0x1000))" -gt 0 -a "$su_beamformee" -gt 0 ] && {
+			cap_ant="$(( ( ($vht_cap >> 13) & 3 ) + 1 ))"
+			[ "$cap_ant" -gt "$beamformee_antennas" ] && cap_ant="$beamformee_antennas"
+			[ "$cap_ant" -gt 1 ] && vht_capab="$vht_capab[BF-ANTENNA-$cap_ant]"
+		}
+
+		# supported Channel widths
+		vht160_hw=0
+		[ "$(($vht_cap & 12))" -eq 4 -a 1 -le "$vht160" ] && \
+			vht160_hw=1
+		[ "$(($vht_cap & 12))" -eq 8 -a 2 -le "$vht160" ] && \
+			vht160_hw=2
+		[ "$vht160_hw" = 1 ] && vht_capab="$vht_capab[VHT160]"
+		[ "$vht160_hw" = 2 ] && vht_capab="$vht_capab[VHT160-80PLUS80]"
+
+		# maximum MPDU length
+		vht_max_mpdu_hw=3895
+		[ "$(($vht_cap & 3))" -ge 1 -a 7991 -le "$vht_max_mpdu" ] && \
+			vht_max_mpdu_hw=7991
+		[ "$(($vht_cap & 3))" -ge 2 -a 11454 -le "$vht_max_mpdu" ] && \
+			vht_max_mpdu_hw=11454
+		[ "$vht_max_mpdu_hw" != 3895 ] && \
+			vht_capab="$vht_capab[MAX-MPDU-$vht_max_mpdu_hw]"
+
+		# maximum A-MPDU length exponent
+		vht_max_a_mpdu_len_exp_hw=0
+		[ "$(($vht_cap & 58720256))" -ge 8388608 -a 1 -le "$vht_max_a_mpdu_len_exp" ] && \
+			vht_max_a_mpdu_len_exp_hw=1
+		[ "$(($vht_cap & 58720256))" -ge 16777216 -a 2 -le "$vht_max_a_mpdu_len_exp" ] && \
+			vht_max_a_mpdu_len_exp_hw=2
+		[ "$(($vht_cap & 58720256))" -ge 25165824 -a 3 -le "$vht_max_a_mpdu_len_exp" ] && \
+			vht_max_a_mpdu_len_exp_hw=3
+		[ "$(($vht_cap & 58720256))" -ge 33554432 -a 4 -le "$vht_max_a_mpdu_len_exp" ] && \
+			vht_max_a_mpdu_len_exp_hw=4
+		[ "$(($vht_cap & 58720256))" -ge 41943040 -a 5 -le "$vht_max_a_mpdu_len_exp" ] && \
+			vht_max_a_mpdu_len_exp_hw=5
+		[ "$(($vht_cap & 58720256))" -ge 50331648 -a 6 -le "$vht_max_a_mpdu_len_exp" ] && \
+			vht_max_a_mpdu_len_exp_hw=6
+		[ "$(($vht_cap & 58720256))" -ge 58720256 -a 7 -le "$vht_max_a_mpdu_len_exp" ] && \
+			vht_max_a_mpdu_len_exp_hw=7
+		vht_capab="$vht_capab[MAX-A-MPDU-LEN-EXP$vht_max_a_mpdu_len_exp_hw]"
+
+		# whether or not the STA supports link adaptation using VHT variant
+		vht_link_adapt_hw=0
+		[ "$(($vht_cap & 201326592))" -ge 134217728 -a 2 -le "$vht_link_adapt" ] && \
+			vht_link_adapt_hw=2
+		[ "$(($vht_cap & 201326592))" -ge 201326592 -a 3 -le "$vht_link_adapt" ] && \
+			vht_link_adapt_hw=3
+		[ "$vht_link_adapt_hw" != 0 ] && \
+			vht_capab="$vht_capab[VHT-LINK-ADAPT-$vht_link_adapt_hw]"
+
+		[ -n "$vht_capab" ] && append base_cfg "vht_capab=$vht_capab" "$N"
+	fi
+
+	# 802.11ax
+	enable_ax=0
+	enable_be=0
+	case "$htmode" in
+		HE*) enable_ax=1 ;;
+		EHT*) enable_ax=1; enable_be=1 ;;
+	esac
+
+	if [ "$enable_ax" != "0" ]; then
+		json_get_vars \
+			he_su_beamformer:1 \
+			he_su_beamformee:1 \
+			he_mu_beamformer:1 \
+			he_twt_required:0 \
+			he_spr_sr_control:3 \
+			he_spr_psr_enabled:0 \
+			he_spr_non_srg_obss_pd_max_offset:0 \
+			he_bss_color:128 \
+			he_bss_color_enabled:1
+
+		he_phy_cap=$(iw phy "$phy" info | sed -n '/HE Iftypes: .*AP/,$p' | awk -F "[()]" '/HE PHY Capabilities/ { print $2 }' | head -1)
+		he_phy_cap=${he_phy_cap:2}
+		he_mac_cap=$(iw phy "$phy" info | sed -n '/HE Iftypes: .*AP/,$p' | awk -F "[()]" '/HE MAC Capabilities/ { print $2 }' | head -1)
+		he_mac_cap=${he_mac_cap:2}
+
+		append base_cfg "ieee80211ax=1" "$N"
+		[ "$hwmode" = "a" ] && {
+			append base_cfg "he_oper_chwidth=$vht_oper_chwidth" "$N"
+			append base_cfg "he_oper_centr_freq_seg0_idx=$vht_center_seg0" "$N"
+		}
+
+#		mac80211_add_he_capabilities \
+			he_su_beamformer:${he_phy_cap:6:2}:0x80:$he_su_beamformer \
+			he_su_beamformee:${he_phy_cap:8:2}:0x1:$he_su_beamformee \
+			he_mu_beamformer:${he_phy_cap:8:2}:0x2:$he_mu_beamformer \
+			he_spr_psr_enabled:${he_phy_cap:14:2}:0x1:$he_spr_psr_enabled \
+			he_twt_required:${he_mac_cap:0:2}:0x6:$he_twt_required
+
+		if [ "$he_bss_color_enabled" -gt 0 ]; then
+#			append base_cfg "he_bss_color=$he_bss_color" "$N"
+			[ "$he_spr_non_srg_obss_pd_max_offset" -gt 0 ] && { \
+				append base_cfg "he_spr_non_srg_obss_pd_max_offset=$he_spr_non_srg_obss_pd_max_offset" "$N"
+				he_spr_sr_control=$((he_spr_sr_control | (1 << 2)))
+			}
+			[ "$he_spr_psr_enabled" -gt 0 ] || he_spr_sr_control=$((he_spr_sr_control | (1 << 0)))
+#			append base_cfg "he_spr_sr_control=$he_spr_sr_control" "$N"
+		else
+			append base_cfg "he_bss_color_disabled=1" "$N"
+		fi
+
+
+		append base_cfg "he_default_pe_duration=4" "$N"
+		append base_cfg "he_rts_threshold=1023" "$N"
+		append base_cfg "he_mu_edca_qos_info_param_count=0" "$N"
+		append base_cfg "he_mu_edca_qos_info_q_ack=0" "$N"
+		append base_cfg "he_mu_edca_qos_info_queue_request=0" "$N"
+		append base_cfg "he_mu_edca_qos_info_txop_request=0" "$N"
+		append base_cfg "he_mu_edca_ac_be_aifsn=8" "$N"
+		append base_cfg "he_mu_edca_ac_be_aci=0" "$N"
+		append base_cfg "he_mu_edca_ac_be_ecwmin=9" "$N"
+		append base_cfg "he_mu_edca_ac_be_ecwmax=10" "$N"
+		append base_cfg "he_mu_edca_ac_be_timer=255" "$N"
+		append base_cfg "he_mu_edca_ac_bk_aifsn=15" "$N"
+		append base_cfg "he_mu_edca_ac_bk_aci=1" "$N"
+		append base_cfg "he_mu_edca_ac_bk_ecwmin=9" "$N"
+		append base_cfg "he_mu_edca_ac_bk_ecwmax=10" "$N"
+		append base_cfg "he_mu_edca_ac_bk_timer=255" "$N"
+		append base_cfg "he_mu_edca_ac_vi_ecwmin=5" "$N"
+		append base_cfg "he_mu_edca_ac_vi_ecwmax=7" "$N"
+		append base_cfg "he_mu_edca_ac_vi_aifsn=5" "$N"
+		append base_cfg "he_mu_edca_ac_vi_aci=2" "$N"
+		append base_cfg "he_mu_edca_ac_vi_timer=255" "$N"
+		append base_cfg "he_mu_edca_ac_vo_aifsn=5" "$N"
+		append base_cfg "he_mu_edca_ac_vo_aci=3" "$N"
+		append base_cfg "he_mu_edca_ac_vo_ecwmin=5" "$N"
+		append base_cfg "he_mu_edca_ac_vo_ecwmax=7" "$N"
+		append base_cfg "he_mu_edca_ac_vo_timer=255" "$N"
+	fi
+
+	if [ "$enable_be" != "0" ]; then
+		append base_cfg "ieee80211be=1" "$N"
+		[ "$hwmode" = "a" ] && {
+			append base_cfg "eht_oper_chwidth=$eht_oper_chwidth" "$N"
+			append base_cfg "eht_oper_centr_freq_seg0_idx=$eht_center_seg0" "$N"
+		}
+	fi
+
+	hostapd_prepare_device_config "$hostapd_conf_file" nl80211
+	cat >> "$hostapd_conf_file" <<EOF
+${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
+#macaddr_base=$macaddr_base
+$base_cfg
+
+EOF
+	json_select ..
+}
+
+mac80211_hostapd_setup_bss() {
+	local phy="$1"
+	local ifname="$2"
+	local macaddr="$3"
+	local type="$4"
+
+	hostapd_cfg=
+	append hostapd_cfg "$type=$ifname" "$N"
+
+	hostapd_set_bss_options hostapd_cfg "$phy" "$vif" || return 1
+	json_get_vars wds wds_bridge dtim_period max_listen_int start_disabled
+
+	set_default wds 0
+	set_default start_disabled 0
+
+	[ "$wds" -gt 0 ] && {
+		append hostapd_cfg "wds_sta=1" "$N"
+		[ -n "$wds_bridge" ] && append hostapd_cfg "wds_bridge=$wds_bridge" "$N"
+	}
+	[ "$staidx" -gt 0 -o "$start_disabled" -eq 1 ] && append hostapd_cfg "start_disabled=1" "$N"
+
+	cat >> /var/run/hostapd-$phy$vif_phy_suffix.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
+}
+
+mac80211_get_addr() {
+	local phy="$1"
+	local idx="$(($2 + 1))"
+
+	head -n $idx /sys/class/ieee80211/${phy}/addresses | tail -n1
+}
+
+mac80211_generate_mac() {
+	local phy="$1"
+	local id="${macidx:-0}"
+
+	wdev_tool "$phy$phy_suffix" get_macaddr id=$id num_global=$num_global_macaddr mbssid=${multiple_bssid:-0} macaddr_base=${macaddr_base}
+}
+
+get_board_phy_name() (
+	local path="$1"
+	local fallback_phy=""
+
+	__check_phy() {
+		local val="$1"
+		local key="$2"
+		local ref_path="$3"
+
+		json_select "$key"
+		json_get_vars path
+		json_select ..
+
+		[ "${ref_path%+*}" = "$path" ] && fallback_phy=$key
+		[ "$ref_path" = "$path" ] || return 0
+
+		echo "$key"
+		exit
+	}
+
+	json_load_file /etc/board.json
+	json_for_each_item __check_phy wlan "$path"
+	[ -n "$fallback_phy" ] && echo "${fallback_phy}.${path##*+}"
+)
+
+rename_board_phy_by_path() {
+	local path="$1"
+
+	local new_phy="$(get_board_phy_name "$path")"
+	[ -z "$new_phy" -o "$new_phy" = "$phy" ] && return
+
+	iw "$phy" set name "$new_phy" && phy="$new_phy"
+}
+
+rename_board_phy_by_name() (
+	local phy="$1"
+	local suffix="${phy##*.}"
+	[ "$suffix" = "$phy" ] && suffix=
+
+	json_load_file /etc/board.json
+	json_select wlan
+	json_select "${phy%.*}" || return 0
+	json_get_vars path
+
+	prev_phy="$(iwinfo nl80211 phyname "path=$path${suffix:++$suffix}")"
+	[ -n "$prev_phy" ] || return 0
+
+	[ "$prev_phy" = "$phy" ] && return 0
+
+	iw "$prev_phy" set name "$phy"
+)
+
+find_phy() {
+	[ -n "$phy" ] && {
+		rename_board_phy_by_name "$phy"
+		[ -d /sys/class/ieee80211/$phy ] && return 0
+	}
+	[ -n "$path" ] && {
+		phy="$(iwinfo nl80211 phyname "path=$path")"
+		[ -n "$phy" ] && {
+			rename_board_phy_by_path "$path"
+			return 0
+		}
+	}
+	[ -n "$macaddr" ] && {
+		for phy in $(ls /sys/class/ieee80211 2>/dev/null); do
+			grep -i -q "$macaddr" "/sys/class/ieee80211/${phy}/macaddress" && {
+				path="$(iwinfo nl80211 path "$phy")"
+				rename_board_phy_by_path "$path"
+				return 0
+			}
+		done
+	}
+	return 1
+}
+
+mac80211_check_ap() {
+	has_ap=1
+}
+
+mac80211_set_ifname() {
+	local prefix="$1"
+	local type="$2"
+	eval "ifname=\"$prefix$type\${idx_$type:-0}\"; idx_$type=\$((\${idx_$type:-0 } + 1))"
+}
+
+mac80211_prepare_vif() {
+	json_select config
+
+	json_get_vars ifname mode ssid wds powersave macaddr enable wpa_psk_file sae_password_file vlan_file
+
+	[ -n "$ifname" ] || {
+		local prefix;
+
+		case "$mode" in
+		ap|sta|mesh) prefix=$mode;;
+		adhoc) prefix=ibss;;
+		monitor) prefix=mon;;
+		esac
+
+		mac80211_set_ifname "$ifname_prefix" "$prefix"
+	}
+
+	append active_ifnames "$ifname"
+	set_default wds 0
+	set_default powersave 0
+	json_add_string _ifname "$ifname"
+
+	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 ..
+
+
+	[ "$mode" == "ap" ] && {
+		json_select config
+		wireless_vif_parse_encryption
+		json_select ..
+
+		[ -z "$wpa_psk_file" ] && hostapd_set_psk "$ifname"
+		[ -z "$sae_password_file" ] && hostapd_set_sae "$ifname"
+		[ -z "$vlan_file" ] && hostapd_set_vlan "$ifname"
+	}
+
+	json_select config
+
+	# It is far easier to delete and create the desired interface
+	case "$mode" in
+		ap)
+			# Hostapd will handle recreating the interface and
+			# subsequent virtual APs belonging to the same PHY
+			if [ -n "$hostapd_ctrl" ]; then
+				type=bss
+			else
+				type=interface
+			fi
+
+			mac80211_hostapd_setup_bss "$phy" "$ifname" "$macaddr" "$type" || return
+
+			[ -n "$hostapd_ctrl" ] || {
+				ap_ifname="${ifname}"
+				hostapd_ctrl="${hostapd_ctrl:-/var/run/hostapd/$ifname}"
+			}
+		;;
+	esac
+
+	json_select ..
+}
+
+mac80211_prepare_iw_htmode() {
+	case "$htmode" in
+		VHT20|HT20|HE20) iw_htmode=HT20;;
+		HT40*|VHT40|VHT160|HE40)
+			case "$band" in
+				2g)
+					case "$htmode" in
+						HT40+) iw_htmode="HT40+";;
+						HT40-) iw_htmode="HT40-";;
+						*)
+							if [ "$channel" -lt 7 ]; then
+								iw_htmode="HT40+"
+							else
+								iw_htmode="HT40-"
+							fi
+						;;
+					esac
+				;;
+				*)
+					case "$(( ($channel / 4) % 2 ))" in
+						1) iw_htmode="HT40+" ;;
+						0) iw_htmode="HT40-";;
+					esac
+				;;
+			esac
+			[ "$auto_channel" -gt 0 ] && iw_htmode="HT40+"
+		;;
+		VHT80|HE80)
+			iw_htmode="80MHZ"
+		;;
+		NONE|NOHT)
+			iw_htmode="NOHT"
+		;;
+		*) 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
+
+	NEWUMLIST="${NEWUMLIST}$ifname "
+
+	[ "$enable" = 0 ] && {
+		ip link set dev "$ifname" down
+		return 0
+	}
+
+	keyspec=
+	[ "$auth_type" = "wep" ] && {
+		set_default key 1
+		case "$key" in
+			[1234])
+				local idx
+				for idx in 1 2 3 4; do
+					json_get_var ikey "key$idx"
+
+					[ -n "$ikey" ] && {
+						ikey="$(($idx - 1)):$(prepare_key_wep "$ikey")"
+						[ $idx -eq $key ] && ikey="d:$ikey"
+						append keyspec "$ikey"
+					}
+				done
+			;;
+			*)
+				append keyspec "d:0:$(prepare_key_wep "$key")"
+			;;
+		esac
+	}
+
+	brstr=
+	for br in $basic_rate_list; do
+		wpa_supplicant_add_rate brstr "$br"
+	done
+
+	mcval=
+	[ -n "$mcast_rate" ] && wpa_supplicant_add_rate mcval "$mcast_rate"
+
+	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() {
+	json_get_vars ssid mesh_id mcast_rate
+	json_get_values iface_basic_rate_list basic_rate
+
+	mcval=
+	[ -n "$mcast_rate" ] && wpa_supplicant_add_rate mcval "$mcast_rate"
+	[ -n "$mesh_id" ] && ssid="$mesh_id"
+
+	br_list="$basic_rate_list"
+	if [ -n "$iface_basic_rate_list" ]; then
+		br_list="$iface_basic_rate_list"
+	fi
+
+	brstr=
+	for br in $br_list; do
+		wpa_supplicant_add_rate brstr "$br"
+	done
+
+	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"
+	[ -n "$brstr" ] && json_add_string basic-rates "$brstr"
+	json_add_int beacon-interval "$beacon_int"
+	mac80211_add_mesh_params
+
+	json_close_object
+
+	json_set_namespace "$prev"
+}
+
+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"
+
+	json_select config
+	json_get_var ifname _ifname
+	json_get_vars vif_txpower
+	json_select ..
+
+	set_default vif_txpower "$txpower"
+	if [ -n "$vif_txpower" ]; then
+		iw dev "$ifname" set txpower fixed "${vif_txpower%%.*}00"
+	else
+		iw dev "$ifname" set txpower auto
+	fi
+}
+
+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 radio="$2"
+	local prev
+
+	json_set_namespace wpa_supp prev
+	json_close_array
+	json_add_string phy "$phy"
+	json_add_int radio "$radio"
+	json_add_int num_global_macaddr "$num_global_macaddr"
+	json_add_string macaddr_base "$macaddr_base"
+	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() {
+	local phy="$1"
+	local radio="$2"
+
+	[ -n "$hostapd_ctrl" ] || {
+		ubus_call hostapd config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "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\", \"radio\": $radio, \"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"
+	local radio="$2"
+
+	[ -n "$wpa_supp_init" ] || return 0
+
+	ubus_call wpa_supplicant config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "num_global_macaddr": '"$num_global_macaddr"', "macaddr_base": "'"$macaddr_base"'" }' > /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" "$hostapd_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 [ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant -vmesh; then
+				mac80211_setup_supplicant || failed=1
+			else
+				mac80211_setup_mesh
+			fi
+		;;
+		adhoc)
+			wireless_vif_parse_encryption
+			if [ "$wpa" -gt 0 -o "$auto_channel" -gt 0 ]; then
+				mac80211_setup_supplicant || failed=1
+			else
+				mac80211_setup_adhoc
+			fi
+		;;
+		sta)
+			mac80211_setup_supplicant || failed=1
+		;;
+		monitor)
+			mac80211_setup_monitor
+		;;
+	esac
+
+	json_select ..
+	[ -n "$failed" ] || wireless_add_vif "$name" "$ifname"
+}
+
+get_freq() {
+	local phy="$1"
+	local channel="$2"
+	local band="$3"
+
+	case "$band" in
+		2g) band="1:";;
+		5g) band="2:";;
+		60g) band="3:";;
+		6g) band="4:";;
+	esac
+
+	iw "$phy" info | awk -v band="$band" -v channel="[$channel]" '
+
+$1 ~ /Band/ {
+	band_match = band == $2
+}
+
+band_match && $3 == "MHz" && $4 == channel {
+	print int($2)
+	exit
+}
+'
+}
+
+chan_is_dfs() {
+	local phy="$1"
+	local chan="$2"
+	iw "$phy" info | grep -E -m1 "(\* ${chan:-....} MHz${chan:+|\\[$chan\\]})" | grep -q "MHz.*radar detection"
+	return $!
+}
+
+mac80211_set_noscan() {
+	hostapd_noscan=1
+}
+
+drv_mac80211_cleanup() {
+	:
+}
+
+mac80211_reset_config() {
+	hostapd_conf_file="/var/run/hostapd-$phy$vif_phy_suffix.conf"
+	ubus_call hostapd config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "config": "", "prev_config": "'"$hostapd_conf_file"'" }' > /dev/null
+	ubus_call wpa_supplicant config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "config": [] }' > /dev/null
+	wdev_tool "$phy$phy_suffix" set_config '{}'
+}
+
+mac80211_set_suffix() {
+	[ "$radio" = "-1" ] && radio=
+	phy_suffix="${radio:+:$radio}"
+	vif_phy_suffix="${radio:+.$radio}"
+	set_default radio -1
+}
+
+drv_mac80211_setup() {
+	json_select config
+	json_get_vars \
+		radio phy macaddr path \
+		country chanbw distance \
+		txpower \
+		rxantenna txantenna \
+		frag rts beacon_int:100 htmode \
+		num_global_macaddr:1 multiple_bssid \
+		ifname_prefix macaddr_base
+	json_get_values basic_rate_list basic_rate
+	json_get_values scan_list scan_list
+	json_select ..
+
+	mac80211_set_suffix
+
+	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
+	}
+
+	set_default ifname_prefix "$phy$vif_phy_suffix-"
+
+	local wdev
+	local cwdev
+	local found
+
+	# convert channel to frequency
+	[ "$auto_channel" -gt 0 ] || freq="$(get_freq "$phy" "$channel" "$band")"
+
+	[ -n "$country" ] && {
+		iw reg get | grep -q "^country $country:" || {
+			iw reg set "$country"
+			sleep 1
+		}
+	}
+
+	hostapd_conf_file="/var/run/hostapd-$phy$vif_phy_suffix.conf"
+
+	macidx=0
+	staidx=0
+
+	[ -n "$chanbw" ] && {
+		for file in /sys/kernel/debug/ieee80211/$phy/ath9k*/chanbw /sys/kernel/debug/ieee80211/$phy/ath5k/bwmode; do
+			[ -f "$file" ] && echo "$chanbw" > "$file"
+		done
+	}
+
+	set_default rxantenna 0xffffffff
+	set_default txantenna 0xffffffff
+	set_default distance 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" radio="$radio" txantenna="$txantenna" rxantenna="$rxantenna"
+
+	iw phy "$phy" set antenna $txantenna $rxantenna >/dev/null 2>&1
+	iw phy "$phy" set distance "$distance" >/dev/null 2>&1
+
+	[ -n "$frag" ] && iw phy "$phy" set frag "${frag%%.*}"
+	[ -n "$rts" ] && iw phy "$phy" set rts "${rts%%.*}"
+
+	has_ap=
+	hostapd_ctrl=
+	ap_ifname=
+	hostapd_noscan=
+	wpa_supp_init=
+	for_each_interface "ap" mac80211_check_ap
+
+	[ -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"
+
+	local prev
+	json_set_namespace wdev_uc prev
+	json_init
+	json_set_namespace "$prev"
+
+	wpa_supplicant_init_config
+
+	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
+
+	[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_set_config "$phy" "$radio"
+	[ -x /usr/sbin/hostapd ] && hostapd_set_config "$phy" "$radio"
+
+	[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_start "$phy" "$radio"
+
+	json_set_namespace wdev_uc prev
+	wdev_tool "$phy$phy_suffix" set_config "$(json_dump)" $active_ifnames
+	json_set_namespace "$prev"
+
+	[ -z "$phy_suffix" ] && {
+		if [ -n "$txpower" ]; then
+			iw phy "$phy" set txpower fixed "${txpower%%.*}00"
+		else
+			iw phy "$phy" set txpower auto
+		fi
+	}
+
+	for_each_interface "ap sta adhoc mesh monitor" mac80211_set_vif_txpower
+	wireless_set_up
+}
+
+_list_phy_interfaces() {
+	local phy="$1"
+	if [ -d "/sys/class/ieee80211/${phy}/device/net" ]; then
+		ls "/sys/class/ieee80211/${phy}/device/net" 2>/dev/null;
+	else
+		ls "/sys/class/ieee80211/${phy}/device" 2>/dev/null | grep net: | sed -e 's,net:,,g'
+	fi
+}
+
+list_phy_interfaces() {
+	local phy="$1"
+
+	for dev in $(_list_phy_interfaces "$phy"); do
+		readlink "/sys/class/net/${dev}/phy80211" | grep -q "/${phy}\$" || continue
+		echo "$dev"
+	done
+}
+
+drv_mac80211_teardown() {
+	json_select data
+	json_get_vars phy radio
+	json_select ..
+	[ -n "$phy" ] || {
+		echo "Bug: PHY is undefined for device '$1'"
+		return 1
+	}
+
+	mac80211_set_suffix
+	mac80211_reset_config "$phy"
+}
+
+add_driver mac80211
diff --git a/package/network/config/wifi-scripts/files/lib/wifi/mac80211.uc b/package/network/config/wifi-scripts/files/lib/wifi/mac80211.uc
new file mode 100644
index 0000000..e82525e
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/lib/wifi/mac80211.uc
@@ -0,0 +1,118 @@
+#!/usr/bin/env ucode
+import { readfile } from "fs";
+import * as uci from 'uci';
+
+const bands_order = [ "6G", "5G", "2G" ];
+const htmode_order = [ "EHT", "HE", "VHT", "HT" ];
+
+let board = json(readfile("/etc/board.json"));
+if (!board.wlan)
+	exit(0);
+
+let idx = 0;
+let commit;
+
+let config = uci.cursor().get_all("wireless") ?? {};
+
+function radio_exists(path, macaddr, phy, radio) {
+	for (let name, s in config) {
+		if (s[".type"] != "wifi-device")
+			continue;
+		if (radio != null && int(s.radio) != radio)
+			continue;
+		if (s.macaddr & lc(s.macaddr) == lc(macaddr))
+			return true;
+		if (s.phy == phy)
+			return true;
+		if (!s.path || !path)
+			continue;
+		if (substr(s.path, -length(path)) == path)
+			return true;
+	}
+}
+
+for (let phy_name, phy in board.wlan) {
+	let info = phy.info;
+	if (!info || !length(info.bands))
+		continue;
+
+	let radios = length(info.radios) > 0 ? info.radios : [{ bands: info.bands }];
+	for (let radio in radios) {
+		while (config[`radio${idx}`])
+			idx++;
+		let name = "radio" + idx;
+
+		let s = "wireless." + name;
+		let si = "wireless.default_" + name;
+
+		let band_name = filter(bands_order, (b) => radio.bands[b])[0];
+		if (!band_name)
+			continue;
+
+		let band = info.bands[band_name];
+		let rband = radio.bands[band_name];
+		let channel = rband.default_channel ?? "auto";
+
+		let width = band.max_width;
+		if (band_name == "2G")
+			width = 20;
+		else if (width > 80)
+			width = 80;
+
+		let htmode = filter(htmode_order, (m) => band[lc(m)])[0];
+		if (htmode)
+			htmode += width;
+		else
+			htmode = "NOHT";
+
+		if (!phy.path)
+			continue;
+
+		let macaddr = trim(readfile(`/sys/class/ieee80211/${phy_name}/macaddress`));
+		if (radio_exists(phy.path, macaddr, phy_name, radio.index))
+			continue;
+
+		let id = `phy='${phy_name}'`;
+		if (match(phy_name, /^phy[0-9]/))
+			id = `path='${phy.path}'`;
+
+		band_name = lc(band_name);
+
+		let country, defaults, num_global_macaddr;
+		if (board.wlan.defaults) {
+			defaults = board.wlan.defaults.ssids?.[band_name]?.ssid ? board.wlan.defaults.ssids?.[band_name] : board.wlan.defaults.ssids?.all;
+			country = board.wlan.defaults.country;
+			if (!country && band_name != '2g')
+				defaults = null;
+			num_global_macaddr = board.wlan.defaults.ssids?.[band_name]?.mac_count;
+		}
+
+		if (length(info.radios) > 0)
+			id += `\nset ${s}.radio='${radio.index}'`;
+
+		print(`set ${s}=wifi-device
+set ${s}.type='mac80211'
+set ${s}.${id}
+set ${s}.band='${band_name}'
+set ${s}.channel='${channel}'
+set ${s}.htmode='${htmode}'
+set ${s}.country='${country || ''}'
+set ${s}.num_global_macaddr='${num_global_macaddr || ''}'
+set ${s}.disabled='${defaults ? 0 : 1}'
+
+set ${si}=wifi-iface
+set ${si}.device='${name}'
+set ${si}.network='lan'
+set ${si}.mode='ap'
+set ${si}.ssid='${defaults?.ssid || "OpenWrt"}'
+set ${si}.encryption='${defaults?.encryption || "none"}'
+set ${si}.key='${defaults?.key || ""}'
+
+`);
+		config[name] = {};
+		commit = true;
+	}
+}
+
+if (commit)
+	print("commit wireless\n");
diff --git a/package/network/config/wifi-scripts/files/sbin/wifi b/package/network/config/wifi-scripts/files/sbin/wifi
new file mode 100755
index 0000000..c707235
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/sbin/wifi
@@ -0,0 +1,94 @@
+#!/bin/sh
+# Copyright (C) 2006 OpenWrt.org
+
+. /lib/functions.sh
+. /usr/share/libubox/jshn.sh
+
+usage() {
+	cat <<EOF
+Usage: $0 [config|up|down|reconf|reload|status|isup]
+enables (default), disables or configures devices not yet configured.
+EOF
+	exit 1
+}
+
+ubus_wifi_cmd() {
+	local cmd="$1"
+	local dev="$2"
+
+	json_init
+	[ -n "$dev" ] && json_add_string device "$dev"
+	ubus call network.wireless "$cmd" "$(json_dump)"
+}
+
+wifi_isup() {
+	local dev="$1"
+
+	json_load "$(ubus_wifi_cmd "status" "$dev")"
+	json_get_keys devices
+
+	for device in $devices; do
+		json_select "$device"
+			json_get_var up up
+			[ $up -eq 0 ] && return 1
+		json_select ..
+	done
+
+	return 0
+}
+
+wifi_updown() {
+	cmd=down
+	[ enable = "$1" ] && {
+		ubus_wifi_cmd "$cmd" "$2"
+		ubus call network reload
+		cmd=up
+	}
+	[ reconf = "$1" ] && {
+		ubus call network reload
+		cmd=reconf
+	}
+	ubus_wifi_cmd "$cmd" "$2"
+}
+
+wifi_reload() {
+	ubus call network reload
+}
+
+wifi_detect_notice() {
+	>&2 echo "WARNING: Wifi detect is deprecated. Use wifi config instead"
+	>&2 echo "For more information, see commit 5f8f8a366136a07df661e31decce2458357c167a"
+	exit 1
+}
+
+wifi_config() {
+	[ -e /tmp/.config_pending ] && return
+	ucode /usr/share/hostap/wifi-detect.uc
+	[ ! -f /etc/config/wireless ] && touch /etc/config/wireless
+	ucode /lib/wifi/mac80211.uc | uci -q batch
+
+	for driver in $DRIVERS; do (
+		if eval "type detect_$driver" 2>/dev/null >/dev/null; then
+			eval "detect_$driver" || echo "$driver: Detect failed" >&2
+		else
+			echo "$driver: Hardware detection not supported" >&2
+		fi
+	); done
+}
+
+DEVICES=
+DRIVERS=
+include /lib/wifi
+
+case "$1" in
+	down) wifi_updown "disable" "$2";;
+	detect) wifi_detect_notice ;;
+	config) wifi_config ;;
+	status) ubus_wifi_cmd "status" "$2";;
+	isup) wifi_isup "$2"; exit $?;;
+	reload) wifi_reload "$2";;
+	--help|help) usage;;
+	reconf) wifi_updown "reconf" "$2";;
+	''|up) wifi_updown "enable" "$2";;
+	*) usage; exit 1;;
+esac
diff --git a/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc b/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc
new file mode 100644
index 0000000..31b526b
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/usr/share/hostap/common.uc
@@ -0,0 +1,421 @@
+import * as nl80211 from "nl80211";
+import * as rtnl from "rtnl";
+import { readfile, glob, basename, readlink, open } 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,
+};
+
+const mesh_params = {
+	mesh_retry_timeout: "retry_timeout",
+	mesh_confirm_timeout: "confirm_timeout",
+	mesh_holding_timeout: "holding_timeout",
+	mesh_max_peer_links: "max_peer_links",
+	mesh_max_retries: "max_retries",
+	mesh_ttl: "ttl",
+	mesh_element_ttl: "element_ttl",
+	mesh_auto_open_plinks: "auto_open_plinks",
+	mesh_hwmp_max_preq_retries: "hwmp_max_preq_retries",
+	mesh_path_refresh_time: "path_refresh_time",
+	mesh_min_discovery_timeout: "min_discovery_timeout",
+	mesh_hwmp_active_path_timeout: "hwmp_active_path_timeout",
+	mesh_hwmp_preq_min_interval: "hwmp_preq_min_interval",
+	mesh_hwmp_net_diameter_traversal_time: "hwmp_net_diam_trvs_time",
+	mesh_hwmp_rootmode: "hwmp_rootmode",
+	mesh_hwmp_rann_interval: "hwmp_rann_interval",
+	mesh_gate_announcements: "gate_announcements",
+	mesh_sync_offset_max_neighor: "sync_offset_max_neighbor",
+	mesh_rssi_threshold: "rssi_threshold",
+	mesh_hwmp_active_path_to_root_timeout: "hwmp_path_to_root_timeout",
+	mesh_hwmp_root_interval: "hwmp_root_interval",
+	mesh_hwmp_confirmation_interval: "hwmp_confirmation_interval",
+	mesh_awake_window: "awake_window",
+	mesh_plink_timeout: "plink_timeout",
+	mesh_fwding: "forwarding",
+	mesh_power_mode: "power_mode",
+	mesh_nolearn: "nolearn"
+};
+
+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.monitor;
+}
+
+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_set_radio_mask(name, mask)
+{
+	nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, {
+		dev: name,
+		vif_radio_mask: mask
+	});
+}
+
+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;
+	if (data.radio != null && data.radio >= 0)
+		req.vif_radio_mask = 1 << data.radio;
+
+	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)) {
+		req.dev = req.ifname;
+		delete req.ifname;
+		nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, req);
+	} 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 wdev_set_mesh_params(name, data)
+{
+	let mesh_cfg = {};
+
+	for (let key in mesh_params) {
+		let val = data[key];
+		if (val == null)
+			continue;
+		mesh_cfg[mesh_params[key]] = int(val);
+	}
+
+	if (!length(mesh_cfg))
+		return null;
+
+	nl80211.request(nl80211.const.NL80211_CMD_SET_MESH_CONFIG, 0,
+		{ dev: name, mesh_params: mesh_cfg });
+
+	return nl80211.error();
+}
+
+function wdev_set_up(name, up)
+{
+	rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: name, change: 1, flags: up ? 1 : 0 });
+}
+
+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.phy;
+		let radio_idx = this.radio;
+		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;
+
+		let base_mask = phy_sysfs_file(phy, "address_mask");
+		if (!base_mask)
+			return null;
+
+		if (base_mask == "00:00:00:00:00:00")
+			base_mask = "ff:ff:ff:ff:ff:ff";
+
+		if (data.macaddr_base)
+			base_addr = data.macaddr_base;
+		else if (base_mask == "ff:ff:ff:ff:ff:ff" &&
+		    (radio_idx > 0 || idx >= num_global)) {
+			let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
+
+			if (radio_idx != null) {
+				if (radio_idx && radio_idx < length(addrs))
+					base_addr = addrs[radio_idx];
+				else
+					idx += radio_idx * 16;
+			} else {
+				if (idx < length(addrs))
+					return addrs[idx];
+			}
+		}
+
+		if (!idx && !mbssid)
+			return base_addr;
+
+		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;
+		}
+	},
+
+	wdev_add: function(name, data) {
+		let phydev = this;
+		wdev_create(this.phy, name, {
+			...data,
+			radio: this.radio,
+		});
+	},
+
+	for_each_wdev: function(cb) {
+		let wdevs = nl80211.request(
+			nl80211.const.NL80211_CMD_GET_INTERFACE,
+			nl80211.const.NLM_F_DUMP,
+			{ wiphy: this.idx }
+		);
+
+		let mac_wdev = {};
+		for (let wdev in wdevs) {
+			if (wdev.iftype == nl80211.const.NL80211_IFTYPE_AP_VLAN)
+				continue;
+			if (this.radio != null && wdev.vif_radio_mask != null &&
+			    !(wdev.vif_radio_mask & (1 << this.radio)))
+				continue;
+			mac_wdev[wdev.mac] = wdev;
+		}
+
+		for (let wdev in wdevs) {
+			if (!mac_wdev[wdev.mac])
+				continue;
+
+			cb(wdev.ifname);
+		}
+	}
+};
+
+function phy_open(phy, radio)
+{
+	let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`);
+	if (!phyidx)
+		return null;
+
+	let name = phy;
+	if (radio === "" || radio < 0)
+		radio = null;
+	if (radio != null)
+		name += "." + radio;
+
+	return proto({
+		phy, name, radio,
+		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, wdev_set_mesh_params, wdev_set_radio_mask, wdev_set_up, is_equal, vlist_new, phy_is_fullmac, phy_open };
diff --git a/package/network/config/wifi-scripts/files/usr/share/hostap/wdev.uc b/package/network/config/wifi-scripts/files/usr/share/hostap/wdev.uc
new file mode 100644
index 0000000..166e940
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/usr/share/hostap/wdev.uc
@@ -0,0 +1,183 @@
+#!/usr/bin/env ucode
+'use strict';
+import { vlist_new, is_equal, wdev_set_mesh_params, wdev_remove, wdev_set_up, 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_name = shift(ARGV);
+let command = shift(ARGV);
+let phy, phydev;
+
+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`)) {
+		wdev_set_up(ifname, false);
+		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();
+	phydev.wdev_add(ifname, wdev_config);
+	wdev_set_up(ifname, true);
+	let htmode = wdev.htmode || "NOHT";
+	if (wdev.freq)
+		system(`iw dev ${ifname} set freq ${wdev.freq} ${htmode}`);
+	if (wdev.mode == "adhoc") {
+		let cmd = ["iw", "dev", ifname, "ibss", "join", wdev.ssid, wdev.freq, 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, htmode ];
+		for (let key in [ "basic-rates", "mcast-rate", "beacon-interval" ])
+			if (wdev[key])
+				push(cmd, key, wdev[key]);
+		system(cmd);
+
+		wdev_set_mesh_params(ifname, wdev);
+	}
+}
+
+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(phydev, config)
+{
+	phydev.for_each_wdev((wdev) => {
+		if (config[wdev])
+			return;
+
+		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_name}.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(phydev, config.data);
+		add_ifname(config.data);
+		drop_inactive(config.data);
+
+		let ubus = libubus.connect();
+		let data = ubus.call("hostapd", "config_get_macaddr_list", { phy: phydev.name, radio: phydev.radio ?? -1 });
+		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_name}\n`);
+			exit(1);
+		}
+
+		print(macaddr + "\n");
+	},
+};
+
+if (!phy_name || !command | !commands[command])
+	usage();
+
+let phy_split = split(phy_name, ":");
+phydev = phy_open(phy_split[0], phy_split[1]);
+phy = phydev.phy;
+if (!phydev) {
+	warn(`PHY ${phy_name} does not exist\n`);
+	exit(1);
+}
+
+commands[command](ARGV);
diff --git a/package/network/config/wifi-scripts/files/usr/share/hostap/wifi-detect.uc b/package/network/config/wifi-scripts/files/usr/share/hostap/wifi-detect.uc
new file mode 100644
index 0000000..db862d4
--- /dev/null
+++ b/package/network/config/wifi-scripts/files/usr/share/hostap/wifi-detect.uc
@@ -0,0 +1,260 @@
+#!/usr/bin/env ucode
+'use strict';
+import { readfile, writefile, realpath, glob, basename, unlink, open, rename } from "fs";
+import { is_equal } from "/usr/share/hostap/common.uc";
+let nl = require("nl80211");
+
+let board_file = "/etc/board.json";
+let prev_board_data = json(readfile(board_file));
+let board_data = json(readfile(board_file));
+
+function phy_idx(name) {
+	return +rtrim(readfile(`/sys/class/ieee80211/${name}/index`));
+}
+
+function phy_path(name) {
+	let devpath = realpath(`/sys/class/ieee80211/${name}/device`);
+
+	devpath = replace(devpath, /^\/sys\/devices\//, "");
+	if (match(devpath, /^platform\/.*\/pci/))
+		devpath = replace(devpath, /^platform\//, "");
+	let dev_phys = map(glob(`/sys/class/ieee80211/${name}/device/ieee80211/*`), basename);
+	sort(dev_phys, (a, b) => phy_idx(a) - phy_idx(b));
+
+	let ofs = index(dev_phys, name);
+	if (ofs > 0)
+		devpath += `+${ofs}`;
+
+	return devpath;
+}
+
+function cleanup() {
+	let wlan = board_data.wlan;
+
+	for (let name in wlan)
+		if (substr(name, 0, 3) == "phy")
+			delete wlan[name];
+		else
+			delete wlan[name].info;
+}
+
+function wiphy_get_entry(phy, path) {
+	board_data.wlan ??= {};
+
+	let wlan = board_data.wlan;
+	for (let name in wlan)
+		if (wlan[name].path == path)
+			return wlan[name];
+
+	wlan[phy] = {
+		path: path
+	};
+
+	return wlan[phy];
+}
+
+function freq_to_channel(freq) {
+	if (freq < 1000)
+		return 0;
+	if (freq == 2484)
+		return 14;
+	if (freq == 5935)
+		return 2;
+	if (freq < 2484)
+		return (freq - 2407) / 5;
+	if (freq >= 4910 && freq <= 4980)
+		return (freq - 4000) / 5;
+	if (freq < 5950)
+		return (freq - 5000) / 5;
+	if (freq <= 45000)
+		return (freq - 5950) / 5;
+	if (freq >= 58320 && freq <= 70200)
+		return (freq - 56160) / 2160;
+	return 0;
+}
+
+function freq_range_match(ranges, freq) {
+	freq *= 1000;
+	for (let range in ranges) {
+		if (freq >= range[0] && freq <= range[1])
+			return true;
+	}
+	return false;
+}
+
+function wiphy_detect() {
+	let phys = nl.request(nl.const.NL80211_CMD_GET_WIPHY, nl.const.NLM_F_DUMP, { split_wiphy_dump: true });
+	if (!phys)
+		return;
+
+	for (let phy in phys) {
+		if (!phy)
+			continue;
+
+		let name = phy.wiphy_name;
+		let path = phy_path(name);
+		let info = {
+			antenna_rx: phy.wiphy_antenna_avail_rx,
+			antenna_tx: phy.wiphy_antenna_avail_tx,
+			bands: {},
+			radios: []
+		};
+
+		for (let radio in phy.radios) {
+			// S1G is not supported yet
+			radio.freq_ranges = filter(radio.freq_ranges,
+				(range) => range.end > 2000000
+			);
+
+			if (!length(radio.freq_ranges))
+				continue;
+
+			push(info.radios, {
+				index: radio.index,
+				freq_ranges: map(radio.freq_ranges,
+					(range) => [ range.start, range.end ]
+				),
+				bands: {}
+			});
+		}
+
+		let bands = info.bands;
+		for (let band in phy.wiphy_bands) {
+			if (!band || !band.freqs)
+				continue;
+			let freq = band.freqs[0].freq;
+			let band_info = {};
+			let band_name;
+			if (freq > 50000)
+				band_name = "60G";
+			else if (freq > 5900)
+				band_name = "6G";
+			else if (freq > 4000)
+				band_name = "5G";
+			else if (freq > 2000)
+				band_name = "2G";
+			else
+				continue;
+			bands[band_name] = band_info;
+			if (band.ht_capa > 0)
+				band_info.ht = true;
+			if (band.vht_capa > 0)
+				band_info.vht = true;
+			let he_phy_cap = 0;
+			let eht_phy_cap = 0;
+
+			for (let ift in band.iftype_data) {
+				if (!ift.he_cap_phy)
+					continue;
+
+				band_info.he = true;
+				he_phy_cap |= ift.he_cap_phy[0];
+
+				if (!ift.eht_cap_phy)
+					continue;
+
+				band_info.eht = true;
+				eht_phy_cap |= ift.eht_cap_phy[0];
+			}
+
+			if (band_name != "2G" &&
+			    (he_phy_cap & 0x18) || ((band.vht_capa >> 2) & 0x3))
+				band_info.max_width = 160;
+			else if (band_name != "2G" &&
+			         (he_phy_cap & 4) || band.vht_capa > 0)
+				band_info.max_width = 80;
+			else if ((band.ht_capa & 0x2) || (he_phy_cap & 0x2))
+				band_info.max_width = 40;
+			else
+				band_info.max_width = 20;
+
+			let modes = band_info.modes = [ "NOHT" ];
+			if (band_info.ht)
+				push(modes, "HT20");
+			if (band_info.vht)
+				push(modes, "VHT20");
+			if (band_info.he)
+				push(modes, "HE20");
+			if (band_info.eht)
+				push(modes, "EHT20");
+			if (band.ht_capa & 0x2) {
+				push(modes, "HT40");
+				if (band_info.vht)
+					push(modes, "VHT40")
+			}
+			if (he_phy_cap & 2)
+				push(modes, "HE40");
+
+			if (eht_phy_cap && he_phy_cap & 2)
+				push(modes, "EHT40");
+
+			for (let radio in info.radios) {
+				let freq_match = filter(band.freqs,
+					(freq) => freq_range_match(radio.freq_ranges, freq.freq)
+				);
+				if (!length(freq_match))
+					continue;
+
+				let radio_band = {};
+				radio.bands[band_name] = radio_band;
+
+				freq_match = filter(freq_match,
+					(freq) => !freq.disabled
+				);
+
+				let freq = freq_match[0];
+				if (freq)
+					radio_band.default_channel = freq_to_channel(freq.freq);
+			}
+
+			for (let freq in band.freqs) {
+				if (freq.disabled)
+					continue;
+				let chan = freq_to_channel(freq.freq);
+				if (!chan)
+					continue;
+				band_info.default_channel = chan;
+				break;
+			}
+
+			if (band_name == "2G")
+				continue;
+
+			if (he_phy_cap & 4)
+				push(modes, "HE40");
+			if (eht_phy_cap && he_phy_cap & 4)
+				push(modes, "EHT40");
+			if (band_info.vht)
+				push(modes, "VHT80");
+			if (he_phy_cap & 4)
+				push(modes, "HE80");
+			if (eht_phy_cap && he_phy_cap & 4)
+				push(modes, "EHT80");
+			if ((band.vht_capa >> 2) & 0x3)
+				push(modes, "VHT160");
+			if (he_phy_cap & 0x18)
+				push(modes, "HE160");
+			if (eht_phy_cap && he_phy_cap & 0x18)
+				push(modes, "EHT160");
+
+			if (eht_phy_cap & 2)
+				push(modes, "EHT320");
+		}
+
+		let entry = wiphy_get_entry(name, path);
+		entry.info = info;
+	}
+}
+
+cleanup();
+wiphy_detect();
+if (!is_equal(prev_board_data, board_data)) {
+	let new_file = board_file + ".new";
+	unlink(new_file);
+	let f = open(new_file, "wx");
+	if (!f)
+		exit(1);
+	f.write(sprintf("%.J\n", board_data));
+	f.close();
+	rename(new_file, board_file);
+}