Browse Source

hostapd: add DPP ucode API for external frame handling

Add a ucode API to hostapd and wpa_supplicant for external DPP frame
handling. This allows an external controller to intercept DPP frames
and handle the DPP protocol externally.

The API provides:
- RX callbacks (dpp_rx_action, dpp_rx_gas) called when DPP frames are
  received, allowing external handling before internal processing
- TX methods (dpp_send_action, dpp_send_gas_resp/dpp_send_gas_req) for
  transmitting DPP frames
- A ubus channel-based API (dpp_channel) for bidirectional communication
  with exclusive hook registration per interface
- CCE control for hostapd (set_cce method)

The wpa_supplicant API mirrors hostapd but adapted for STA role:
- Uses tx_gas_req instead of tx_gas_resp
- GAS RX provides full frame instead of parsed query
- No CCE control (AP-only feature)

Both implementations include:
- Timeout handling with automatic channel disconnect after 3 failures
- Hook cleanup on interface removal
- Last-caller-wins semantics for hook registration

Signed-off-by: Felix Fietkau <[email protected]>
Felix Fietkau 1 tuần trước cách đây
mục cha
commit
09de759506

+ 129 - 0
package/network/services/hostapd/files/hostapd.uc

@@ -60,6 +60,7 @@ hostapd.data.bss_info_fields = {
 };
 
 hostapd.data.mld = {};
+hostapd.data.dpp_hooks = {};
 
 function iface_remove(cfg)
 {
@@ -1128,6 +1129,103 @@ function mld_set_config(config)
 		mld_reload_interface(name);
 }
 
+function dpp_find_bss(ifname)
+{
+	for (let phy, bss_list in hostapd.bss) {
+		if (bss_list[ifname])
+			return bss_list[ifname];
+	}
+	return null;
+}
+
+function dpp_channel_handle_request(channel, req)
+{
+	let data = req.args ?? {};
+	let bss;
+
+	switch (req.type) {
+	case "start":
+		if (!data.ifname)
+			return libubus.STATUS_INVALID_ARGUMENT;
+		let old_hook = hostapd.data.dpp_hooks[data.ifname];
+		if (old_hook && old_hook.channel != channel)
+			old_hook.channel.disconnect();
+		hostapd.data.dpp_hooks[data.ifname] = {
+			channel: channel,
+			timeout_count: 0,
+		};
+		return 0;
+
+	case "stop":
+		if (!data.ifname)
+			return libubus.STATUS_INVALID_ARGUMENT;
+		let hook = hostapd.data.dpp_hooks[data.ifname];
+		if (hook && hook.channel == channel)
+			delete hostapd.data.dpp_hooks[data.ifname];
+		return 0;
+
+	case "tx_action":
+		bss = dpp_find_bss(data.ifname);
+		if (!bss)
+			return libubus.STATUS_NOT_FOUND;
+		if (!bss.dpp_send_action(data.dst, data.freq ?? 0, data.frame))
+			return libubus.STATUS_UNKNOWN_ERROR;
+		return 0;
+
+	case "tx_gas_resp":
+		bss = dpp_find_bss(data.ifname);
+		if (!bss)
+			return libubus.STATUS_NOT_FOUND;
+		if (!bss.dpp_send_gas_resp(data.dst, data.dialog_token, data.data, data.freq ?? 0))
+			return libubus.STATUS_UNKNOWN_ERROR;
+		return 0;
+
+	case "set_cce":
+		bss = dpp_find_bss(data.ifname);
+		if (!bss)
+			return libubus.STATUS_NOT_FOUND;
+		let val = data.enable ? "dd04506f9a1e" : "";
+		bss.ctrl("SET vendor_elements " + val);
+		bss.ctrl("UPDATE_BEACON");
+		return 0;
+
+	default:
+		return libubus.STATUS_METHOD_NOT_FOUND;
+	}
+}
+
+function dpp_channel_handle_disconnect(channel)
+{
+	for (let ifname, hook in hostapd.data.dpp_hooks) {
+		if (hook.channel == channel)
+			delete hostapd.data.dpp_hooks[ifname];
+	}
+}
+
+function dpp_rx_via_channel(ifname, method, data)
+{
+	let hook = hostapd.data.dpp_hooks[ifname];
+	if (!hook)
+		return null;
+
+	let response = hook.channel.request({
+		method: method,
+		data: data,
+	});
+	if (hook.channel.error(true) == libubus.STATUS_TIMEOUT) {
+		hook.timeout_count++;
+		if (hook.timeout_count >= 3) {
+			hostapd.printf(`DPP channel timeout for ${ifname}, disconnecting`);
+			hook.channel.disconnect();
+			delete hostapd.data.dpp_hooks[ifname];
+		}
+		return null;
+	}
+
+	hook.timeout_count = 0;
+	return response;
+}
+
 let main_obj = {
 	reload: {
 		args: {
@@ -1411,6 +1509,20 @@ let main_obj = {
 			return { interfaces };
 		}
 	},
+	dpp_channel: {
+		args: {},
+		call: function(req) {
+			let channel;
+			let on_request = (chan_req) => dpp_channel_handle_request(channel, chan_req);
+			let on_disconnect = () => dpp_channel_handle_disconnect(channel);
+
+			channel = req.new_channel(on_request, on_disconnect, 1);
+			if (!channel)
+				return libubus.STATUS_UNKNOWN_ERROR;
+
+			return 0;
+		}
+	},
 };
 
 hostapd.data.ubus = ubus;
@@ -1452,6 +1564,7 @@ return {
 		bss_event("reload", name, { reconf: reconf != 0 });
 	},
 	bss_remove: function(phy, name, obj) {
+		delete hostapd.data.dpp_hooks[name];
 		bss_event("remove", name);
 	},
 	sta_auth: function(iface, sta) {
@@ -1474,4 +1587,20 @@ return {
 			hostapd.data.auth_obj.notify("sta_connected", msg, data_cb, null, null, 1000);
 		return ret;
 	},
+	dpp_rx_action: function(iface, src, frame_type, freq, frame) {
+		let response = dpp_rx_via_channel(iface, "rx_action", {
+			ifname: iface, src, frame_type, freq, frame,
+		});
+		if (response && response.handled)
+			return true;
+		return false;
+	},
+	dpp_rx_gas: function(iface, src, dialog_token, query, freq) {
+		let response = dpp_rx_via_channel(iface, "rx_gas", {
+			ifname: iface, src, dialog_token, query, freq,
+		});
+		if (response && response.response)
+			return response.response;
+		return null;
+	},
 };

+ 159 - 1
package/network/services/hostapd/files/wpa_supplicant.uc

@@ -19,6 +19,7 @@ wpas.data.config = {};
 wpas.data.iface_phy = {};
 wpas.data.iface_ubus = {};
 wpas.data.macaddr_list = {};
+wpas.data.dpp_hooks = {};
 
 function iface_stop(iface)
 {
@@ -388,6 +389,132 @@ function iface_status_fill_radio(mld, radio, msg, status)
 		iface_status_fill_radio_link(mld, radio, msg, status);
 }
 
+function dpp_find_iface(ifname)
+{
+	return wpas.interfaces[ifname];
+}
+
+function dpp_channel_handle_request(channel, req)
+{
+	let data = req.args ?? {};
+	let iface;
+
+	switch (req.type) {
+	case "start":
+		if (!data.ifname)
+			return libubus.STATUS_INVALID_ARGUMENT;
+		if (!wpas.interfaces[data.ifname])
+			return libubus.STATUS_NOT_FOUND;
+		let old_hook = wpas.data.dpp_hooks[data.ifname];
+		if (old_hook && old_hook.channel != channel)
+			old_hook.channel.disconnect();
+		wpas.data.dpp_hooks[data.ifname] = {
+			channel: channel,
+			timeout_count: 0,
+		};
+		return 0;
+
+	case "stop":
+		if (!data.ifname)
+			return libubus.STATUS_INVALID_ARGUMENT;
+		let hook = wpas.data.dpp_hooks[data.ifname];
+		if (hook && hook.channel == channel)
+			delete wpas.data.dpp_hooks[data.ifname];
+		return 0;
+
+	case "tx_action":
+		iface = dpp_find_iface(data.ifname);
+		if (!iface)
+			return libubus.STATUS_NOT_FOUND;
+		if (!iface.dpp_send_action(data.dst, data.freq ?? 0, data.frame))
+			return libubus.STATUS_UNKNOWN_ERROR;
+		return 0;
+
+	case "tx_gas_req":
+		iface = dpp_find_iface(data.ifname);
+		if (!iface)
+			return libubus.STATUS_NOT_FOUND;
+		if (!iface.dpp_send_gas_req(data.dst, data.freq ?? 0, data.data, data.dialog_token ?? 0))
+			return libubus.STATUS_UNKNOWN_ERROR;
+		return 0;
+
+	case "dpp_bootstrap_gen":
+		iface = dpp_find_iface(data.ifname);
+		if (!iface)
+			return libubus.STATUS_NOT_FOUND;
+		let gen_cmd = "DPP_BOOTSTRAP_GEN type=qrcode";
+		if (data.key)
+			gen_cmd += " key=" + data.key;
+		if (data.curve)
+			gen_cmd += " curve=" + data.curve;
+		let gen_result = iface.ctrl(gen_cmd);
+		if (!gen_result || gen_result == "FAIL")
+			return libubus.STATUS_UNKNOWN_ERROR;
+		return { id: +gen_result };
+
+	case "dpp_bootstrap_remove":
+		iface = dpp_find_iface(data.ifname);
+		if (!iface)
+			return libubus.STATUS_NOT_FOUND;
+		let remove_result = iface.ctrl("DPP_BOOTSTRAP_REMOVE " + (data.id ?? "*"));
+		return (remove_result == "OK") ? 0 : libubus.STATUS_UNKNOWN_ERROR;
+
+	case "dpp_chirp":
+		iface = dpp_find_iface(data.ifname);
+		if (!iface)
+			return libubus.STATUS_NOT_FOUND;
+		let chirp_cmd = "DPP_CHIRP own=" + data.id;
+		if (data.iter)
+			chirp_cmd += " iter=" + data.iter;
+		if (data.scan_interval)
+			chirp_cmd += " listen=" + data.scan_interval;
+		let chirp_result = iface.ctrl(chirp_cmd);
+		return (chirp_result == "OK") ? 0 : libubus.STATUS_UNKNOWN_ERROR;
+
+	case "dpp_stop_chirp":
+		iface = dpp_find_iface(data.ifname);
+		if (!iface)
+			return libubus.STATUS_NOT_FOUND;
+		iface.ctrl("DPP_STOP_CHIRP");
+		return 0;
+
+	default:
+		return libubus.STATUS_METHOD_NOT_FOUND;
+	}
+}
+
+function dpp_channel_handle_disconnect(channel)
+{
+	for (let ifname, hook in wpas.data.dpp_hooks) {
+		if (hook.channel == channel)
+			delete wpas.data.dpp_hooks[ifname];
+	}
+}
+
+function dpp_rx_via_channel(ifname, method, data)
+{
+	let hook = wpas.data.dpp_hooks[ifname];
+	if (!hook)
+		return null;
+
+	let response = hook.channel.request({
+		method: method,
+		data: data,
+	});
+	if (hook.channel.error(true) == libubus.STATUS_TIMEOUT) {
+		hook.timeout_count++;
+		if (hook.timeout_count >= 3) {
+			wpas.printf(`DPP channel timeout for ${ifname}, disconnecting`);
+			hook.channel.disconnect();
+			delete wpas.data.dpp_hooks[ifname];
+		}
+		return null;
+	}
+
+	hook.timeout_count = 0;
+	return response;
+}
+
 let main_obj = {
 	phy_set_state: {
 		args: {
@@ -673,6 +800,20 @@ let main_obj = {
 			return { interfaces };
 		}
 	},
+	dpp_channel: {
+		args: {},
+		call: function(req) {
+			let channel;
+			let on_request = (chan_req) => dpp_channel_handle_request(channel, chan_req);
+			let on_disconnect = () => dpp_channel_handle_disconnect(channel);
+
+			channel = req.new_channel(on_request, on_disconnect, 1);
+			if (!channel)
+				return libubus.STATUS_UNKNOWN_ERROR;
+
+			return 0;
+		}
+	},
 };
 
 wpas.data.ubus = ubus;
@@ -877,6 +1018,7 @@ return {
 	iface_remove: function(name, obj) {
 		iface_event("remove", name);
 		iface_ubus_remove(name);
+		delete wpas.data.dpp_hooks[name];
 	},
 	ctrl_event: function(name, iface, ev) {
 		iface_ubus_notify(name, ev);
@@ -919,5 +1061,21 @@ return {
 	wps_credentials: function(ifname, iface, cred) {
 		cred.ifname = ifname;
 		ubus.event("wps_credentials", cred);
-	}
+	},
+	dpp_rx_action: function(iface, src, frame_type, freq, frame) {
+		let response = dpp_rx_via_channel(iface, "rx_action", {
+			ifname: iface, src, frame_type, freq, frame,
+		});
+		if (response && response.handled)
+			return true;
+		return false;
+	},
+	dpp_rx_gas: function(iface, src, freq, gas_frame) {
+		let response = dpp_rx_via_channel(iface, "rx_gas", {
+			ifname: iface, src, freq, gas_frame,
+		});
+		if (response && response.handled)
+			return true;
+		return false;
+	},
 };

+ 26 - 0
package/network/services/hostapd/patches/161-wpa_supplicant-avoid-stopping-DPP-chirp-when-offchan.patch

@@ -0,0 +1,26 @@
+From: Felix Fietkau <[email protected]>
+Date: Sat, 7 Feb 2026 09:20:30 +0000
+Subject: [PATCH] wpa_supplicant: avoid stopping DPP chirp when offchannel tx
+ fails
+
+Offchannel tx failure can happen when the scan finds an AP on a DFS channel.
+Make chirping more reliable by immediately switching to the next channel
+when that happens.
+
+Signed-off-by: Felix Fietkau <[email protected]>
+---
+
+--- a/wpa_supplicant/dpp_supplicant.c
++++ b/wpa_supplicant/dpp_supplicant.c
+@@ -5131,7 +5131,10 @@ static void wpas_dpp_chirp_start(struct
+ 		    wpa_s->own_addr, broadcast,
+ 		    wpabuf_head(msg), wpabuf_len(msg),
+ 		    2000, wpas_dpp_chirp_tx_status, 0) < 0)
+-		wpas_dpp_chirp_stop(wpa_s);
++		wpas_dpp_chirp_tx_status(wpa_s, wpa_s->dpp_chirp_freq,
++					 broadcast, wpa_s->own_addr, broadcast,
++					 wpabuf_head(msg), wpabuf_len(msg),
++					 OFFCHANNEL_SEND_ACTION_FAILED);
+ 
+ 	wpabuf_free(announce);
+ }

+ 100 - 1
package/network/services/hostapd/patches/601-ucode_support.patch

@@ -988,7 +988,21 @@ as adding/removing interfaces.
  				       MULTI_AP_FRONTHAUL_BSS);
  	wpa_s->multi_ap_ie = 1;
  }
-@@ -6293,6 +6300,7 @@ void supplicant_event(void *ctx, enum wp
+@@ -5500,6 +5507,13 @@ static void wpas_event_rx_mgmt_action(st
+ 	}
+ #endif /* CONFIG_WNM */
+ 
++#if defined(CONFIG_GAS) && defined(CONFIG_DPP)
++	if ((mgmt->u.action.category == WLAN_ACTION_PUBLIC ||
++	     mgmt->u.action.category == WLAN_ACTION_PROTECTED_DUAL) &&
++	    wpas_ucode_dpp_gas_rx(wpa_s, mgmt->sa, payload, plen, freq))
++		return;
++#endif /* CONFIG_GAS && CONFIG_DPP */
++
+ #ifdef CONFIG_GAS
+ 	if ((mgmt->u.action.category == WLAN_ACTION_PUBLIC ||
+ 	     mgmt->u.action.category == WLAN_ACTION_PROTECTED_DUAL) &&
+@@ -6293,6 +6307,7 @@ void supplicant_event(void *ctx, enum wp
  		event_to_string(event), event);
  #endif /* CONFIG_NO_STDOUT_DEBUG */
  
@@ -1014,3 +1028,88 @@ as adding/removing interfaces.
  	gpriv = wpa_s->global->ctrl_iface;
  
  	if (type != WPA_MSG_NO_GLOBAL && gpriv &&
+--- a/src/ap/dpp_hostapd.c
++++ b/src/ap/dpp_hostapd.c
+@@ -23,6 +23,7 @@
+ #include "wpa_auth.h"
+ #include "beacon.h"
+ #include "dpp_hostapd.h"
++#include "ucode.h"
+ 
+ 
+ static void hostapd_dpp_reply_wait_timeout(void *eloop_ctx, void *timeout_ctx);
+@@ -3017,6 +3018,9 @@ void hostapd_dpp_rx_action(struct hostap
+ 	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_RX "src=" MACSTR
+ 		" freq=%u type=%d", MAC2STR(src), freq, type);
+ 
++	if (hostapd_ucode_dpp_rx_action(hapd, src, type, freq, hdr, len + 6))
++		return;
++
+ #ifdef CONFIG_DPP2
+ 	if (dpp_relay_rx_action(hapd->iface->interfaces->dpp,
+ 				src, hdr, buf, len, freq, NULL, NULL,
+@@ -3116,13 +3120,19 @@ void hostapd_dpp_rx_action(struct hostap
+ 
+ struct wpabuf *
+ hostapd_dpp_gas_req_handler(struct hostapd_data *hapd, const u8 *sa,
+-			    const u8 *query, size_t query_len,
++			    u8 dialog_token, const u8 *query, size_t query_len,
+ 			    const u8 *data, size_t data_len)
+ {
+ 	struct dpp_authentication *auth = hapd->dpp_auth;
+ 	struct wpabuf *resp;
+ 
+ 	wpa_printf(MSG_DEBUG, "DPP: GAS request from " MACSTR, MAC2STR(sa));
++
++	resp = hostapd_ucode_dpp_gas_req(hapd, sa, dialog_token,
++					 query, query_len);
++	if (resp)
++		return resp;
++
+ 	eloop_cancel_timeout(hostapd_gas_req_wait, hapd, NULL);
+ 	if (!auth || (!auth->auth_success && !auth->reconfig_success) ||
+ 	    !ether_addr_equal(sa, auth->peer_mac_addr)) {
+--- a/src/ap/dpp_hostapd.h
++++ b/src/ap/dpp_hostapd.h
+@@ -25,7 +25,7 @@ void hostapd_dpp_tx_status(struct hostap
+ 			   const u8 *data, size_t data_len, int ok);
+ struct wpabuf *
+ hostapd_dpp_gas_req_handler(struct hostapd_data *hapd, const u8 *sa,
+-			    const u8 *query, size_t query_len,
++			    u8 dialog_token, const u8 *query, size_t query_len,
+ 			    const u8 *data, size_t data_len);
+ void hostapd_dpp_gas_status_handler(struct hostapd_data *hapd, int ok);
+ int hostapd_dpp_configurator_add(struct hostapd_data *hapd, const char *cmd);
+--- a/src/ap/gas_serv.c
++++ b/src/ap/gas_serv.c
+@@ -1402,8 +1402,8 @@ static void gas_serv_rx_gas_initial_req(
+ 	if (dpp) {
+ 		struct wpabuf *msg;
+ 
+-		msg = hostapd_dpp_gas_req_handler(hapd, sa, pos, slen,
+-						  data, len);
++		msg = hostapd_dpp_gas_req_handler(hapd, sa, dialog_token,
++						  pos, slen, data, len);
+ 		if (!msg)
+ 			return;
+ 		gas_serv_req_dpp_processing(hapd, sa, dialog_token, prot, msg,
+--- a/wpa_supplicant/dpp_supplicant.c
++++ b/wpa_supplicant/dpp_supplicant.c
+@@ -29,6 +29,7 @@
+ #include "scan.h"
+ #include "notify.h"
+ #include "dpp_supplicant.h"
++#include "ucode.h"
+ 
+ 
+ static int wpas_dpp_listen_start(struct wpa_supplicant *wpa_s,
+@@ -4118,6 +4119,9 @@ void wpas_dpp_rx_action(struct wpa_suppl
+ 		return;
+ 	}
+ 	wpa_hexdump(MSG_MSGDUMP, "DPP: Received message attributes", buf, len);
++	if (wpas_ucode_dpp_rx_action(wpa_s, src, type, freq, hdr, len + DPP_HDR_LEN))
++		return;
++
+ 	if (dpp_check_attrs(buf, len) < 0) {
+ 		wpa_msg(wpa_s, MSG_INFO, DPP_EVENT_RX "src=" MACSTR
+ 			" freq=%u type=%d ignore=invalid-attributes",

+ 194 - 0
package/network/services/hostapd/src/src/ap/ucode.c

@@ -3,6 +3,7 @@
 #include "utils/includes.h"
 #include "utils/common.h"
 #include "utils/ucode.h"
+#include "utils/base64.h"
 #include "sta_info.h"
 #include "beacon.h"
 #include "hw_features.h"
@@ -11,6 +12,11 @@
 #include "acs.h"
 #include "ieee802_11_auth.h"
 #include "neighbor_db.h"
+#include "gas_serv.h"
+#ifdef CONFIG_DPP
+#include "common/dpp.h"
+#include "common/wpa_ctrl.h"
+#endif /* CONFIG_DPP */
 #include <libubox/uloop.h>
 
 static uc_resource_type_t *global_type, *bss_type, *iface_type;
@@ -942,6 +948,190 @@ uc_wpa_rkh_derive_key(uc_vm_t *vm, size_t nargs)
 #endif
 }
 
+#ifdef CONFIG_DPP
+static uc_value_t *
+uc_hostapd_bss_dpp_send_action(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_data *hapd = uc_fn_thisval("hostapd.bss");
+	uc_value_t *dst_arg = uc_fn_arg(0);
+	uc_value_t *freq_arg = uc_fn_arg(1);
+	uc_value_t *frame_arg = uc_fn_arg(2);
+	struct wpabuf *msg;
+	const char *dst_str, *frame_b64;
+	unsigned char *frame_data;
+	size_t frame_len;
+	u8 dst[ETH_ALEN];
+	unsigned int freq;
+	u8 frame_type;
+	int ret;
+
+	if (!hapd || ucv_type(dst_arg) != UC_STRING ||
+	    ucv_type(frame_arg) != UC_STRING)
+		return NULL;
+
+	dst_str = ucv_string_get(dst_arg);
+	if (hwaddr_aton(dst_str, dst))
+		return NULL;
+
+	freq = ucv_int64_get(freq_arg);
+	if (!freq)
+		freq = hapd->iface->freq;
+
+	frame_b64 = ucv_string_get(frame_arg);
+	frame_data = base64_decode(frame_b64, os_strlen(frame_b64), &frame_len);
+	if (!frame_data)
+		return NULL;
+
+	if (frame_len < 6) {
+		os_free(frame_data);
+		return NULL;
+	}
+
+	frame_type = frame_data[5];
+	msg = dpp_alloc_msg(frame_type, frame_len - 6);
+	if (!msg) {
+		os_free(frame_data);
+		return NULL;
+	}
+	wpabuf_put_data(msg, frame_data + 6, frame_len - 6);
+	os_free(frame_data);
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
+		" freq=%u type=%d", MAC2STR(dst), freq, frame_type);
+	ret = hostapd_drv_send_action(hapd, freq, 0, dst,
+				      wpabuf_head(msg), wpabuf_len(msg));
+	wpabuf_free(msg);
+
+	return ucv_boolean_new(ret == 0);
+}
+
+static uc_value_t *
+uc_hostapd_bss_dpp_send_gas_resp(uc_vm_t *vm, size_t nargs)
+{
+	struct hostapd_data *hapd = uc_fn_thisval("hostapd.bss");
+	uc_value_t *dst_arg = uc_fn_arg(0);
+	uc_value_t *token_arg = uc_fn_arg(1);
+	uc_value_t *data_arg = uc_fn_arg(2);
+	uc_value_t *freq_arg = uc_fn_arg(3);
+	const char *dst_str, *data_b64;
+	unsigned char *data;
+	size_t data_len;
+	struct wpabuf *buf;
+	u8 dst[ETH_ALEN];
+	u8 dialog_token;
+	int freq;
+
+	if (!hapd || ucv_type(dst_arg) != UC_STRING ||
+	    ucv_type(token_arg) != UC_INTEGER ||
+	    ucv_type(data_arg) != UC_STRING)
+		return NULL;
+
+	dst_str = ucv_string_get(dst_arg);
+	if (hwaddr_aton(dst_str, dst))
+		return NULL;
+
+	dialog_token = ucv_int64_get(token_arg);
+	if (dialog_token == 0)
+		return NULL;
+
+	freq = ucv_int64_get(freq_arg);
+	if (!freq)
+		freq = hapd->iface->freq;
+
+	data_b64 = ucv_string_get(data_arg);
+	data = base64_decode(data_b64, os_strlen(data_b64), &data_len);
+	if (!data)
+		return NULL;
+
+	buf = wpabuf_alloc_copy(data, data_len);
+	os_free(data);
+	if (!buf)
+		return NULL;
+
+	gas_serv_req_dpp_processing(hapd, dst, dialog_token, 0, buf, freq);
+
+	return ucv_boolean_new(true);
+}
+
+int hostapd_ucode_dpp_rx_action(struct hostapd_data *hapd, const u8 *src,
+				u8 frame_type, unsigned int freq,
+				const u8 *data, size_t data_len)
+{
+	uc_value_t *val;
+	char addr[18];
+	char *frame_b64;
+	size_t frame_b64_len;
+	int ret = 0;
+
+	if (wpa_ucode_call_prepare("dpp_rx_action"))
+		return 0;
+
+	os_snprintf(addr, sizeof(addr), MACSTR, MAC2STR(src));
+	frame_b64 = base64_encode_no_lf(data, data_len, &frame_b64_len);
+	if (!frame_b64) {
+		ucv_put(wpa_ucode_call(0));
+		return 0;
+	}
+
+	uc_value_push(ucv_string_new(hapd->conf->iface));
+	uc_value_push(ucv_string_new(addr));
+	uc_value_push(ucv_int64_new(frame_type));
+	uc_value_push(ucv_int64_new(freq));
+	uc_value_push(ucv_string_new(frame_b64));
+	os_free(frame_b64);
+
+	val = wpa_ucode_call(5);
+	ret = ucv_is_truish(val);
+	ucv_put(val);
+
+	return ret;
+}
+
+struct wpabuf *hostapd_ucode_dpp_gas_req(struct hostapd_data *hapd,
+					 const u8 *sa, u8 dialog_token,
+					 const u8 *query, size_t query_len)
+{
+	uc_value_t *val;
+	char addr[18];
+	char *query_b64;
+	size_t query_b64_len;
+	struct wpabuf *ret = NULL;
+
+	if (wpa_ucode_call_prepare("dpp_rx_gas"))
+		return NULL;
+
+	os_snprintf(addr, sizeof(addr), MACSTR, MAC2STR(sa));
+	query_b64 = base64_encode_no_lf(query, query_len, &query_b64_len);
+	if (!query_b64) {
+		ucv_put(wpa_ucode_call(0));
+		return NULL;
+	}
+
+	uc_value_push(ucv_string_new(hapd->conf->iface));
+	uc_value_push(ucv_string_new(addr));
+	uc_value_push(ucv_int64_new(dialog_token));
+	uc_value_push(ucv_string_new(query_b64));
+	os_free(query_b64);
+
+	val = wpa_ucode_call(4);
+	if (ucv_type(val) == UC_STRING) {
+		const char *resp_b64 = ucv_string_get(val);
+		size_t resp_len;
+		unsigned char *resp_data;
+
+		resp_data = base64_decode(resp_b64, os_strlen(resp_b64),
+					  &resp_len);
+		if (resp_data) {
+			ret = wpabuf_alloc_copy(resp_data, resp_len);
+			os_free(resp_data);
+		}
+	}
+	ucv_put(val);
+
+	return ret;
+}
+#endif /* CONFIG_DPP */
+
 int hostapd_ucode_init(struct hapd_interfaces *ifaces)
 {
 	static const uc_function_list_t global_fns[] = {
@@ -959,6 +1149,10 @@ int hostapd_ucode_init(struct hapd_interfaces *ifaces)
 		{ "set_config", uc_hostapd_bss_set_config },
 		{ "rename", uc_hostapd_bss_rename },
 		{ "delete", uc_hostapd_bss_delete },
+#ifdef CONFIG_DPP
+		{ "dpp_send_action", uc_hostapd_bss_dpp_send_action },
+		{ "dpp_send_gas_resp", uc_hostapd_bss_dpp_send_gas_resp },
+#endif /* CONFIG_DPP */
 	};
 	static const uc_function_list_t iface_fns[] = {
 		{ "state", uc_hostapd_iface_state },

+ 27 - 0
package/network/services/hostapd/src/src/ap/ucode.h

@@ -32,6 +32,15 @@ void hostapd_ucode_sta_connected(struct hostapd_data *hapd, struct sta_info *sta
 void hostapd_ucode_apup_newpeer(struct hostapd_data *hapd, const char *ifname);
 #endif // def CONFIG_APUP
 
+#ifdef CONFIG_DPP
+int hostapd_ucode_dpp_rx_action(struct hostapd_data *hapd, const u8 *src,
+				u8 frame_type, unsigned int freq,
+				const u8 *data, size_t data_len);
+struct wpabuf *hostapd_ucode_dpp_gas_req(struct hostapd_data *hapd,
+					 const u8 *sa, u8 dialog_token,
+					 const u8 *query, size_t query_len);
+#endif /* CONFIG_DPP */
+
 #else
 
 static inline int hostapd_ucode_init(struct hapd_interfaces *ifaces)
@@ -58,6 +67,24 @@ static inline void hostapd_ucode_free_bss(struct hostapd_data *hapd)
 {
 }
 
+#ifdef CONFIG_DPP
+static inline int hostapd_ucode_dpp_rx_action(struct hostapd_data *hapd,
+					      const u8 *src, u8 frame_type,
+					      unsigned int freq,
+					      const u8 *data, size_t data_len)
+{
+	return 0;
+}
+static inline struct wpabuf *hostapd_ucode_dpp_gas_req(struct hostapd_data *hapd,
+						       const u8 *sa,
+						       u8 dialog_token,
+						       const u8 *query,
+						       size_t query_len)
+{
+	return NULL;
+}
+#endif /* CONFIG_DPP */
+
 #endif
 
 static inline void hostapd_ucode_create_bss(struct hostapd_data *hapd)

+ 194 - 0
package/network/services/hostapd/src/wpa_supplicant/ucode.c

@@ -1,6 +1,7 @@
 #include "utils/includes.h"
 #include "utils/common.h"
 #include "utils/ucode.h"
+#include "utils/base64.h"
 #include "drivers/driver.h"
 #include "ap/hostapd.h"
 #include "wpa_supplicant_i.h"
@@ -9,6 +10,12 @@
 #include "config.h"
 #include "bss.h"
 #include "ucode.h"
+#include "offchannel.h"
+#ifdef CONFIG_DPP
+#include "common/dpp.h"
+#include "common/wpa_ctrl.h"
+#include "common/gas.h"
+#endif /* CONFIG_DPP */
 
 static struct wpa_global *wpa_global;
 static uc_resource_type_t *global_type, *iface_type;
@@ -236,6 +243,189 @@ void wpas_ucode_wps_complete(struct wpa_supplicant *wpa_s,
 #endif /* CONFIG_WPS */
 }
 
+#ifdef CONFIG_DPP
+int wpas_ucode_dpp_rx_action(struct wpa_supplicant *wpa_s, const u8 *src,
+			     u8 frame_type, unsigned int freq,
+			     const u8 *data, size_t data_len)
+{
+	uc_value_t *val;
+	char addr[18];
+	char *frame_b64;
+	size_t frame_b64_len;
+	int ret = 0;
+
+	if (wpa_ucode_call_prepare("dpp_rx_action"))
+		return 0;
+
+	os_snprintf(addr, sizeof(addr), MACSTR, MAC2STR(src));
+	frame_b64 = base64_encode_no_lf(data, data_len, &frame_b64_len);
+	if (!frame_b64) {
+		ucv_put(wpa_ucode_call(0));
+		return 0;
+	}
+
+	uc_value_push(ucv_string_new(wpa_s->ifname));
+	uc_value_push(ucv_string_new(addr));
+	uc_value_push(ucv_int64_new(frame_type));
+	uc_value_push(ucv_int64_new(freq));
+	uc_value_push(ucv_string_new(frame_b64));
+	os_free(frame_b64);
+
+	val = wpa_ucode_call(5);
+	ret = ucv_is_truish(val);
+	ucv_put(val);
+
+	return ret;
+}
+
+int wpas_ucode_dpp_gas_rx(struct wpa_supplicant *wpa_s, const u8 *src,
+			  const u8 *data, size_t data_len, unsigned int freq)
+{
+	uc_value_t *val;
+	char addr[18];
+	char *gas_b64;
+	size_t gas_b64_len;
+	int ret = 0;
+
+	if (data_len < 2)
+		return 0;
+
+	if (wpa_ucode_call_prepare("dpp_rx_gas"))
+		return 0;
+
+	os_snprintf(addr, sizeof(addr), MACSTR, MAC2STR(src));
+	gas_b64 = base64_encode_no_lf(data, data_len, &gas_b64_len);
+	if (!gas_b64) {
+		ucv_put(wpa_ucode_call(0));
+		return 0;
+	}
+
+	uc_value_push(ucv_string_new(wpa_s->ifname));
+	uc_value_push(ucv_string_new(addr));
+	uc_value_push(ucv_int64_new(freq));
+	uc_value_push(ucv_string_new(gas_b64));
+	os_free(gas_b64);
+
+	val = wpa_ucode_call(4);
+	ret = ucv_is_truish(val);
+	ucv_put(val);
+
+	return ret;
+}
+
+static uc_value_t *
+uc_wpas_iface_dpp_send_action(uc_vm_t *vm, size_t nargs)
+{
+	struct wpa_supplicant *wpa_s = uc_fn_thisval("wpas.iface");
+	uc_value_t *dst_arg = uc_fn_arg(0);
+	uc_value_t *freq_arg = uc_fn_arg(1);
+	uc_value_t *frame_arg = uc_fn_arg(2);
+	struct wpabuf *msg;
+	const char *dst_str, *frame_b64;
+	unsigned char *frame_data;
+	size_t frame_len;
+	u8 dst[ETH_ALEN];
+	unsigned int freq;
+	u8 frame_type;
+	int ret;
+
+	if (!wpa_s || ucv_type(dst_arg) != UC_STRING ||
+	    ucv_type(frame_arg) != UC_STRING)
+		return NULL;
+
+	dst_str = ucv_string_get(dst_arg);
+	if (hwaddr_aton(dst_str, dst))
+		return NULL;
+
+	freq = ucv_int64_get(freq_arg);
+	if (!freq)
+		freq = wpa_s->assoc_freq;
+
+	frame_b64 = ucv_string_get(frame_arg);
+	frame_data = base64_decode(frame_b64, os_strlen(frame_b64), &frame_len);
+	if (!frame_data)
+		return NULL;
+
+	if (frame_len < DPP_HDR_LEN) {
+		os_free(frame_data);
+		return NULL;
+	}
+
+	frame_type = frame_data[5];
+	msg = dpp_alloc_msg(frame_type, frame_len - DPP_HDR_LEN);
+	if (!msg) {
+		os_free(frame_data);
+		return NULL;
+	}
+	wpabuf_put_data(msg, frame_data + DPP_HDR_LEN, frame_len - DPP_HDR_LEN);
+	os_free(frame_data);
+
+	wpa_msg(wpa_s, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR " freq=%u type=%d",
+		MAC2STR(dst), freq, frame_type);
+	ret = offchannel_send_action(wpa_s, freq, dst, wpa_s->own_addr,
+				     broadcast_ether_addr,
+				     wpabuf_head(msg), wpabuf_len(msg),
+				     500, NULL, 0);
+	wpabuf_free(msg);
+
+	return ucv_boolean_new(ret == 0);
+}
+
+static uc_value_t *
+uc_wpas_iface_dpp_send_gas_req(uc_vm_t *vm, size_t nargs)
+{
+	struct wpa_supplicant *wpa_s = uc_fn_thisval("wpas.iface");
+	uc_value_t *dst_arg = uc_fn_arg(0);
+	uc_value_t *freq_arg = uc_fn_arg(1);
+	uc_value_t *data_arg = uc_fn_arg(2);
+	uc_value_t *token_arg = uc_fn_arg(3);
+	const char *dst_str, *data_b64;
+	unsigned char *data;
+	size_t data_len;
+	u8 dst[ETH_ALEN];
+	unsigned int freq;
+	u8 dialog_token;
+	struct wpabuf *buf;
+	int ret;
+
+	if (!wpa_s || ucv_type(dst_arg) != UC_STRING ||
+	    ucv_type(data_arg) != UC_STRING)
+		return NULL;
+
+	dst_str = ucv_string_get(dst_arg);
+	if (hwaddr_aton(dst_str, dst))
+		return NULL;
+
+	freq = ucv_int64_get(freq_arg);
+	if (!freq)
+		freq = wpa_s->assoc_freq;
+
+	dialog_token = ucv_int64_get(token_arg);
+	if (!dialog_token)
+		dialog_token = 1;
+
+	data_b64 = ucv_string_get(data_arg);
+	data = base64_decode(data_b64, os_strlen(data_b64), &data_len);
+	if (!data)
+		return NULL;
+
+	buf = gas_build_initial_req(dialog_token, data_len);
+	if (!buf) {
+		os_free(data);
+		return NULL;
+	}
+	wpabuf_put_data(buf, data, data_len);
+	os_free(data);
+
+	ret = offchannel_send_action(wpa_s, freq, dst, wpa_s->own_addr,
+				     broadcast_ether_addr, wpabuf_head(buf),
+				     wpabuf_len(buf), 500, NULL, 0);
+	wpabuf_free(buf);
+
+	return ucv_boolean_new(ret == 0);
+}
+#endif /* CONFIG_DPP */
+
 static const char *obj_stringval(uc_value_t *obj, const char *name)
 {
 	uc_value_t *val = ucv_object_get(obj, name, NULL);
@@ -502,6 +692,10 @@ int wpas_ucode_init(struct wpa_global *gl)
 		{ "status", uc_wpas_iface_status },
 		{ "ctrl", uc_wpas_iface_ctrl },
 		{ "config", uc_wpas_iface_config },
+#ifdef CONFIG_DPP
+		{ "dpp_send_action", uc_wpas_iface_dpp_send_action },
+		{ "dpp_send_gas_req", uc_wpas_iface_dpp_send_gas_req },
+#endif /* CONFIG_DPP */
 	};
 	uc_value_t *data, *proto;
 

+ 37 - 0
package/network/services/hostapd/src/wpa_supplicant/ucode.h

@@ -26,6 +26,28 @@ void wpas_ucode_ctrl_event(struct wpa_supplicant *wpa_s, const char *str, size_t
 bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct wpa_bss *bss);
 void wpas_ucode_wps_complete(struct wpa_supplicant *wpa_s,
 			     const struct wps_credential *cred);
+#ifdef CONFIG_DPP
+int wpas_ucode_dpp_rx_action(struct wpa_supplicant *wpa_s, const u8 *src,
+			     u8 frame_type, unsigned int freq,
+			     const u8 *data, size_t data_len);
+int wpas_ucode_dpp_gas_rx(struct wpa_supplicant *wpa_s, const u8 *src,
+			  const u8 *data, size_t data_len, unsigned int freq);
+#else
+static inline int wpas_ucode_dpp_rx_action(struct wpa_supplicant *wpa_s,
+					   const u8 *src, u8 frame_type,
+					   unsigned int freq, const u8 *data,
+					   size_t data_len)
+{
+	return 0;
+}
+
+static inline int wpas_ucode_dpp_gas_rx(struct wpa_supplicant *wpa_s,
+					const u8 *src, const u8 *data,
+					size_t data_len, unsigned int freq)
+{
+	return 0;
+}
+#endif /* CONFIG_DPP */
 #else
 static inline int wpas_ucode_init(struct wpa_global *gl)
 {
@@ -62,6 +84,21 @@ static inline bool wpas_ucode_bss_allowed(struct wpa_supplicant *wpa_s, struct w
 static inline void wpas_ucode_wps_complete(struct wpa_supplicant *wpa_s, const struct wps_credential *cred)
 {
 }
+
+static inline int wpas_ucode_dpp_rx_action(struct wpa_supplicant *wpa_s,
+					   const u8 *src, u8 frame_type,
+					   unsigned int freq, const u8 *data,
+					   size_t data_len)
+{
+	return 0;
+}
+
+static inline int wpas_ucode_dpp_gas_rx(struct wpa_supplicant *wpa_s,
+					const u8 *src, const u8 *data,
+					size_t data_len, unsigned int freq)
+{
+	return 0;
+}
 #endif
 
 #endif