[PATCH 11/15] OCE: Transmit Power and IP Subnet Identifier attributes

Andrei-Alexandru Bleortu me at andrei-z.com
Sun Oct 4 03:42:04 PDT 2026


Wi-Fi Optimized Connectivity 3.17 requires an OCE AP to include the
Transmit Power attribute, the power used to transmit the frame, in the
MBO-OCE element of its Beacon and Probe Response frames, and, when
configured, the IP Subnet Identifier attribute, which tells a STA that
it can keep its IP configuration when it moves between APs that share
the identifier.

Add a get_tx_power() driver operation, which nl80211 implements with
the interface's NL80211_ATTR_WIPHY_TX_POWER_LEVEL, read it whenever the
Beacon frame is built, and include the Transmit Power attribute with it
when it is known. Add oce_ip_subnet_id to configure the IP Subnet
Identifier attribute.

Assisted-by: LLM
Signed-off-by: Andrei-Alexandru Bleortu <me at andrei-z.com>
---
 hostapd/config_file.c        |  9 +++++++++
 hostapd/hostapd.conf         | 12 ++++++++++++
 src/ap/ap_config.h           |  2 ++
 src/ap/ap_drv_ops.h          |  8 ++++++++
 src/ap/beacon.c              |  9 +++++++++
 src/ap/hostapd.h             |  3 +++
 src/ap/ieee802_11_shared.c   | 19 ++++++++++++++++++-
 src/drivers/driver.h         |  8 ++++++++
 src/drivers/driver_nl80211.c | 27 +++++++++++++++++++++++++++
 tests/hwsim/test_oce.py      | 29 +++++++++++++++++++++++++++++
 10 files changed, 125 insertions(+), 1 deletion(-)

diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index b93822f8c..34d58a517 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -4479,6 +4479,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 		bss->mbo_cell_data_conn_pref = atoi(pos);
 	} else if (os_strcmp(buf, "oce") == 0) {
 		bss->oce = atoi(pos);
+	} else if (os_strcmp(buf, "oce_ip_subnet_id") == 0) {
+		if (hexstr2bin(pos, bss->oce_ip_subnet_id, ETH_ALEN) ||
+		    pos[2 * ETH_ALEN] != '\0') {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: invalid oce_ip_subnet_id '%s'",
+				   line, pos);
+			return 1;
+		}
+		bss->oce_ip_subnet_id_set = true;
 #endif /* CONFIG_MBO */
 #ifdef CONFIG_TESTING_OPTIONS
 #define PARSE_TEST_PROBABILITY(_val)				\
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index b8c269263..3bc416393 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -3486,8 +3486,20 @@ own_ip_addr=127.0.0.1
 # Set BIT(1) (= 2) to enable OCE in STA-CFON mode
 # Set BIT(2) (= 4) to enable OCE in AP mode
 # Default is 0 = OCE disabled
+# An OCE AP needs a driver that handles Probe Request frames as OCE requires,
+# or one that leaves them to hostapd (no Probe Response offload). Wi-Fi
+# Optimized Connectivity also expects, among others, no 802.11b rates on 2.4 GHz
+# (basic_rates and supported_rates without 10/20/55/110), the BSS Load element
+# (bss_load_update_period) and the Estimated Service Parameters element (esp).
 #oce=0
 
+# OCE IP Subnet Identifier (Wi-Fi Optimized Connectivity v2.0, 4.2.8)
+# A 6-octet identifier, unique to the set of APs that give access to the same
+# IP subnet(s), so that a STA moving between them can keep its IP
+# configuration. It should not reveal the subnet, e.g., use the BSSID of one of
+# the APs.
+#oce_ip_subnet_id=020000000100
+
 # RSSI-based association rejection
 #
 # Reject STA association if RSSI is below given threshold (in dBm)
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 32dad4602..8b91a9d3f 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -764,6 +764,8 @@ struct hostapd_bss_config {
 	 */
 	unsigned int oce;
 	int mbo_cell_data_conn_pref;
+	u8 oce_ip_subnet_id[ETH_ALEN];
+	bool oce_ip_subnet_id_set;
 #endif /* CONFIG_MBO */
 
 	int ftm_responder;
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index cd86fb762..f4821531b 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -291,6 +291,14 @@ static inline int hostapd_drv_get_survey(struct hostapd_data *hapd,
 	return hapd->driver->get_survey(hapd->drv_priv, freq);
 }
 
+static inline int hostapd_drv_get_tx_power(struct hostapd_data *hapd,
+					   int *tx_power)
+{
+	if (!hapd->driver || !hapd->driver->get_tx_power)
+		return -1;
+	return hapd->driver->get_tx_power(hapd->drv_priv, tx_power);
+}
+
 static inline int hostapd_get_country(struct hostapd_data *hapd, char *alpha2)
 {
 	if (hapd->driver == NULL || hapd->driver->get_country == NULL)
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 1e523ea56..3f5a44ce3 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -2373,6 +2373,15 @@ int ieee802_11_build_ap_params(struct hostapd_data *hapd,
 
 	os_memset(params, 0, sizeof(*params));
 
+#ifdef CONFIG_MBO
+	/* For the Transmit Power attribute of Beacon and Probe Response
+	 * frames */
+	if (OCE_AP_ENABLED(hapd))
+		hapd->iface->tx_power_known =
+			hostapd_drv_get_tx_power(hapd,
+						 &hapd->iface->tx_power) == 0;
+#endif /* CONFIG_MBO */
+
 #ifdef NEED_AP_MLME
 #define BEACON_HEAD_BUF_SIZE 256
 #define BEACON_TAIL_BUF_SIZE 1500
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index e349ef2df..b5d6532f7 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -713,6 +713,9 @@ struct hostapd_iface {
 	u8 channel_utilization;
 	/* Share of the channel time the radio itself transmitted (0..255) */
 	u8 channel_utilization_tx;
+	/* Transmit power in dBm as last read from the driver */
+	int tx_power;
+	bool tx_power_known;
 
 	unsigned int chan_util_samples_sum;
 	unsigned int chan_util_num_sample_periods;
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index 7f1f34c8d..d99f5d012 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -809,7 +809,7 @@ u8 * hostapd_eid_mbo_rssi_assoc_rej(struct hostapd_data *hapd, u8 *eid,
 
 u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len)
 {
-	u8 mbo[9], *mbo_pos = mbo;
+	u8 mbo[20], *mbo_pos = mbo;
 	u8 *pos = eid;
 
 	if (!hapd->conf->mbo_enabled &&
@@ -841,6 +841,19 @@ u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len)
 		*mbo_pos++ = ctrl;
 	}
 
+	if (OCE_AP_ENABLED(hapd) && hapd->iface->tx_power_known) {
+		*mbo_pos++ = OCE_ATTR_ID_TX_POWER;
+		*mbo_pos++ = 1;
+		*mbo_pos++ = (u8) (s8) hapd->iface->tx_power;
+	}
+
+	if (OCE_AP_ENABLED(hapd) && hapd->conf->oce_ip_subnet_id_set) {
+		*mbo_pos++ = OCE_ATTR_ID_IP_SUBNET_ID;
+		*mbo_pos++ = ETH_ALEN;
+		os_memcpy(mbo_pos, hapd->conf->oce_ip_subnet_id, ETH_ALEN);
+		mbo_pos += ETH_ALEN;
+	}
+
 	pos += mbo_add_ie(pos, len, mbo, mbo_pos - mbo);
 
 	return pos;
@@ -867,6 +880,10 @@ u8 hostapd_mbo_ie_len(struct hostapd_data *hapd)
 	if (OCE_STA_CFON_ENABLED(hapd) || OCE_AP_ENABLED(hapd))
 		len += 3;
 
+	/* Transmit Power (3) and IP Subnet Identifier (8) attributes */
+	if (OCE_AP_ENABLED(hapd))
+		len += 3 + (hapd->conf->oce_ip_subnet_id_set ? 8 : 0);
+
 	return len;
 }
 
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index e957a56ae..9485f1856 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -5211,6 +5211,14 @@ struct wpa_driver_ops {
 	 */
 	int (*get_survey)(void *priv, unsigned int freq);
 
+	/**
+	 * get_tx_power - Get the current transmit power of the interface
+	 * @priv: Private driver interface data
+	 * @tx_power: Buffer for the transmit power in dBm
+	 * Returns: 0 on success, -1 on failure (e.g., not known)
+	 */
+	int (*get_tx_power)(void *priv, int *tx_power);
+
 	/**
 	 * status - Get driver interface status information
 	 * @priv: Private driver interface data
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index e3d731f3d..0c983243e 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -909,6 +909,8 @@ struct wiphy_idx_data {
 	u8 use_4addr;
 	u64 wdev_id;
 	bool wdev_id_set;
+	int tx_power; /* mBm */
+	bool tx_power_set;
 };
 
 
@@ -939,6 +941,12 @@ static int netdev_info_handler(struct nl_msg *msg, void *arg)
 		info->wdev_id_set = true;
 	}
 
+	if (tb[NL80211_ATTR_WIPHY_TX_POWER_LEVEL]) {
+		info->tx_power =
+			nla_get_u32(tb[NL80211_ATTR_WIPHY_TX_POWER_LEVEL]);
+		info->tx_power_set = true;
+	}
+
 	return NL_SKIP;
 }
 
@@ -960,6 +968,24 @@ int nl80211_get_wiphy_index(struct i802_bss *bss)
 }
 
 
+static int nl80211_get_tx_power(void *priv, int *tx_power)
+{
+	struct i802_bss *bss = priv;
+	struct nl_msg *msg;
+	struct wiphy_idx_data data = {
+		.wiphy_idx = -1,
+	};
+
+	msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE);
+	if (!msg ||
+	    send_and_recv_resp(bss->drv, msg, netdev_info_handler, &data) ||
+	    !data.tx_power_set)
+		return -1;
+	*tx_power = data.tx_power / 100;
+	return 0;
+}
+
+
 static enum nl80211_iftype nl80211_get_ifmode(struct i802_bss *bss)
 {
 	struct nl_msg *msg;
@@ -16903,6 +16929,7 @@ const struct wpa_driver_ops wpa_driver_nl80211_ops = {
 	.update_dh_ie = nl80211_update_dh_ie,
 	.get_mac_addr = wpa_driver_nl80211_get_macaddr,
 	.get_survey = wpa_driver_nl80211_get_survey,
+	.get_tx_power = nl80211_get_tx_power,
 	.status = wpa_driver_nl80211_status,
 	.switch_channel = nl80211_switch_channel,
 #ifdef CONFIG_IEEE80211AX
diff --git a/tests/hwsim/test_oce.py b/tests/hwsim/test_oce.py
index 75ce3a6fc..55df8af3b 100644
--- a/tests/hwsim/test_oce.py
+++ b/tests/hwsim/test_oce.py
@@ -272,3 +272,32 @@ def test_oce_ap_probe_max_channel_time(dev, apdev, params):
     da = oce_probe_resp_da(dev[0], hapd, params, "ff0302000a")
     if dev[0].own_addr() not in da:
         raise Exception("No Probe Response frame with a short Max Channel Time")
+
+def oce_ap_attrs(dev, bssid):
+    ie = bytes.fromhex(dev.get_bss(bssid)['ie'])
+    attrs = {}
+    while len(ie) >= 2:
+        eid, elen, data = ie[0], ie[1], ie[2:2 + ie[1]]
+        ie = ie[2 + elen:]
+        if eid == 221 and data[0:4] == b'\x50\x6f\x9a\x16':
+            buf = data[4:]
+            while len(buf) >= 2:
+                attrs[buf[0]] = buf[2:2 + buf[1]]
+                buf = buf[2 + buf[1]:]
+    return attrs
+
+def test_oce_ap_tx_power_ip_subnet(dev, apdev):
+    """OCE AP Transmit Power and IP Subnet Identifier attributes"""
+    params = oce_ap_params("test-oce")
+    params["oce_ip_subnet_id"] = "020000000100"
+    hapd = hostapd.add_ap(apdev[0], params)
+    dev[0].scan_for_bss(hapd.own_addr(), freq="2412")
+    attrs = oce_ap_attrs(dev[0], hapd.own_addr())
+    if 107 not in attrs or len(attrs[107]) != 1:
+        raise Exception("No Transmit Power attribute")
+    tx_power = struct.unpack('b', attrs[107])[0]
+    logger.info("Transmit Power: %d dBm" % tx_power)
+    if tx_power <= 0:
+        raise Exception("Unexpected Transmit Power: %d dBm" % tx_power)
+    if attrs.get(108) != bytes.fromhex("020000000100"):
+        raise Exception("No IP Subnet Identifier attribute")
-- 
2.47.3





More information about the Hostap mailing list