From 6f19cc4d7887c7c4c718bc746b661f9a9aadcff4 Mon Sep 17 00:00:00 2001
From: Markus Theil <markus.theil@tu-ilmenau.de>
Date: Wed, 10 Jun 2020 10:32:57 +0200
Subject: [PATCH] nl80211: Handle control port TX status events over nl80211

In order to retransmit faster in AP mode, hostapd can handle TX status
notifications. When using nl80211, this is currently only possible with
socket control messages. Add support for receiving such events directly
over nl80211 and detecting, if this feature is supported.

This finally allows for a clean separation between management/control
path (over nl80211) and in-kernel data path.

A follow up commit enables the feature in AP mode.

Control port TX status contains the original frame content for matching
with the current hostapd code. Furthermore, a cookie is included, which
allows for matching against outstanding cookies in the future. This
commit only prints the cookie value for debugging purposes on TX status
receive.

Signed-off-by: Markus Theil <markus.theil@tu-ilmenau.de>
---
 src/drivers/driver.h               |  2 ++
 src/drivers/driver_common.c        |  1 +
 src/drivers/driver_nl80211_capa.c  |  4 ++++
 src/drivers/driver_nl80211_event.c | 36 ++++++++++++++++++++++++++++++
 4 files changed, 43 insertions(+)

diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 0ecda49dd..cb412cbfe 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -1931,6 +1931,8 @@ struct wpa_driver_capa {
 
 /** Driver supports a separate control port RX for EAPOL frames */
 #define WPA_DRIVER_FLAGS2_CONTROL_PORT_RX	0x0000000000000001ULL
+/** Driver supports TX status reports for EAPOL frames through control port */
+#define WPA_DRIVER_FLAGS2_CONTROL_PORT_TX_STATUS 0x0000000000000002ULL
 	u64 flags2;
 
 #define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c
index 23a6a4293..a7ebe9566 100644
--- a/src/drivers/driver_common.c
+++ b/src/drivers/driver_common.c
@@ -328,6 +328,7 @@ const char * driver_flag2_to_string(u64 flag2)
 #define DF2S(x) case WPA_DRIVER_FLAGS2_ ## x: return #x
 	switch (flag2) {
 	DF2S(CONTROL_PORT_RX);
+	DF2S(CONTROL_PORT_TX_STATUS);
 	}
 	return "UNKNOWN";
 #undef DF2S
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 5d2558153..46f61fdbf 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -620,6 +620,10 @@ static void wiphy_info_ext_feature_flags(struct wiphy_info_data *info,
 	if (ext_feature_isset(ext_features, len,
 			      NL80211_EXT_FEATURE_CONTROL_PORT_NO_PREAUTH))
 		capa->flags2 |= WPA_DRIVER_FLAGS2_CONTROL_PORT_RX;
+	if (ext_feature_isset(
+		    ext_features, len,
+		    NL80211_EXT_FEATURE_CONTROL_PORT_OVER_NL80211_TX_STATUS))
+		capa->flags2 |= WPA_DRIVER_FLAGS2_CONTROL_PORT_TX_STATUS;
 
 	if (ext_feature_isset(ext_features, len,
 			      NL80211_EXT_FEATURE_VLAN_OFFLOAD))
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 6a2de1f3c..1873b1826 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -138,6 +138,8 @@ static const char * nl80211_command_to_string(enum nl80211_commands cmd)
 	C2S(NL80211_CMD_CONTROL_PORT_FRAME)
 	C2S(NL80211_CMD_UPDATE_OWE_INFO)
 	C2S(NL80211_CMD_UNPROT_BEACON)
+	C2S(NL80211_CMD_CONTROL_PORT_FRAME_TX_STATUS)
+
 	default:
 		return "NL80211_CMD_UNKNOWN";
 	}
@@ -2557,6 +2559,37 @@ static void nl80211_control_port_frame(struct wpa_driver_nl80211_data *drv,
 }
 
 
+static void
+nl80211_control_port_frame_tx_status(struct wpa_driver_nl80211_data *drv,
+				     struct nlattr **tb)
+{
+	bool acked = tb[NL80211_ATTR_ACK];
+	union wpa_event_data event;
+	const u8 *frame;
+	size_t frame_len;
+
+	if (!tb[NL80211_ATTR_FRAME] || !tb[NL80211_ATTR_COOKIE])
+		return;
+
+	frame = nla_data(tb[NL80211_ATTR_FRAME]);
+	frame_len = nla_len(tb[NL80211_ATTR_FRAME]);
+	if (frame_len < ETH_HLEN)
+		return;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: Control port TX status (ack=%d), cookie=%llu",
+		   acked, (long long unsigned int)
+		   nla_get_u64(tb[NL80211_ATTR_COOKIE]));
+
+	os_memset(&event, 0, sizeof(event));
+	event.eapol_tx_status.dst = frame;
+	event.eapol_tx_status.data = frame + ETH_HLEN;
+	event.eapol_tx_status.data_len = frame_len - ETH_HLEN;
+	event.eapol_tx_status.ack = acked;
+	wpa_supplicant_event(drv->ctx, EVENT_EAPOL_TX_STATUS, &event);
+}
+
+
 static void do_process_drv_event(struct i802_bss *bss, int cmd,
 				 struct nlattr **tb)
 {
@@ -2775,6 +2808,9 @@ static void do_process_drv_event(struct i802_bss *bss, int cmd,
 			mlme_event_unprot_beacon(drv, nla_data(frame),
 						 nla_len(frame));
 		break;
+	case NL80211_CMD_CONTROL_PORT_FRAME_TX_STATUS:
+		nl80211_control_port_frame_tx_status(drv, tb);
+		break;
 	default:
 		wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event "
 			"(cmd=%d)", cmd);