From 33f8f22375d2940b2e85598f1ec79381dabf4f33 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Wed, 3 Jul 2024 08:46:15 +0200 Subject: [PATCH] hostapd: add enhanced MPSK support Signed-off-by: John Crispin --- feeds/ipq807x_v5.4/hostapd/Makefile | 4 +- feeds/ipq807x_v5.4/hostapd/files/mpskd | 319 ++++++++++++++++++ feeds/ipq807x_v5.4/hostapd/files/mpskd.init | 13 + .../patches/zzz-deny-6g-probe-resp.patch | 156 +++++++++ feeds/ipq807x_v5.4/hostapd/src/src/ap/ubus.c | 1 + .../etc/ucentral/examples/wifi-6e-mpsk.json | 121 +++++++ 6 files changed, 613 insertions(+), 1 deletion(-) create mode 100644 feeds/ipq807x_v5.4/hostapd/files/mpskd create mode 100644 feeds/ipq807x_v5.4/hostapd/files/mpskd.init create mode 100644 feeds/ipq807x_v5.4/hostapd/patches/zzz-deny-6g-probe-resp.patch create mode 100644 feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/wifi-6e-mpsk.json diff --git a/feeds/ipq807x_v5.4/hostapd/Makefile b/feeds/ipq807x_v5.4/hostapd/Makefile index e3b6dee99..c13252689 100644 --- a/feeds/ipq807x_v5.4/hostapd/Makefile +++ b/feeds/ipq807x_v5.4/hostapd/Makefile @@ -604,7 +604,7 @@ define Package/afcd/install endef define Install/hostapd - $(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/hostap + $(INSTALL_DIR) $(1)/usr/sbin $(1)/usr/share/hostap $(1)/etc/init.d $(INSTALL_DATA) ./files/hostapd.uc $(1)/usr/share/hostap/ $(INSTALL_DIR) $(1)/etc/init.d $(1)/etc/config $(1)/etc/radius ln -sf hostapd $(1)/usr/sbin/hostapd-radius @@ -612,6 +612,8 @@ define Install/hostapd $(INSTALL_DATA) ./files/radius.config $(1)/etc/config/radius $(INSTALL_DATA) ./files/radius.clients $(1)/etc/radius/clients $(INSTALL_DATA) ./files/radius.users $(1)/etc/radius/users + $(INSTALL_BIN) ./files/mpskd $(1)/usr/share/hostap/ + $(INSTALL_BIN) ./files/mpskd.init $(1)/etc/init.d/mpskd endef define Install/supplicant diff --git a/feeds/ipq807x_v5.4/hostapd/files/mpskd b/feeds/ipq807x_v5.4/hostapd/files/mpskd new file mode 100644 index 000000000..cf2bd4e87 --- /dev/null +++ b/feeds/ipq807x_v5.4/hostapd/files/mpskd @@ -0,0 +1,319 @@ +#!/usr/bin/env ucode +'use strict'; +import * as uloop from 'uloop'; +import * as libubus from 'ubus'; + +uloop.init(); +let ubus = libubus.connect(); + +let interfaces = {}; +let ssids = {}; +let cache = {}; +let sub_6g = []; +let sub_6g_obj; +let reload_timer; +let gc_timer; + +let timeout = 48 * 60 * 60; + +function event_cb_6g(req) { + //printf('6g %s %.J\n', req.data, req.type); + if (req.type != 'auth' && req.type != 'probe') + return 0; + + let addr = req.data.address; + let iface = interfaces[req.data.ifname]; + if (!iface) + return 0; + + let ssid = iface.ssid; + if (!ssid || !length(ssids[ssid].keys)) + return 0; + + let ssid_cache = cache[ssid]; + if (ssid_cache && ssid_cache[addr]) + return 0; + + if (req.type == 'probe') { + printf(`Ignore probe ${req.type} on ${req.data.ifname} from ${addr}\n`); + return 1; + } + + printf(`reject ${req.type} on ${req.data.ifname} from ${addr}\n`); + return 5; +} + +function event_cb(req) { + //printf('normal %s %.J\n', req.data, req.type); + if (req.type != 'probe') + return 0; + + let addr = req.data.address; + let iface = interfaces[req.data.ifname]; + if (!iface) + return 0; + + let ssid = iface.ssid; + if (!ssid || !length(ssids[ssid].keys)) + return 0; + + let ssid_cache = cache[ssid]; + if (ssid_cache && ssid_cache[addr]) + return 0; + + printf(`reply to ${req.type} on ${req.data.ifname} from ${addr} without 6G RNR\n`); + return 2; +} + +function create_6g_subscriber() { + for (let cur_sub in sub_6g) + cur_sub.remove(); + sub_6g = []; + + for (let ifname, iface in interfaces) { + let obj = 'hostapd.' + ifname; + let cur_sub; + if (iface.band == '6g') + cur_sub = ubus.subscriber((req) => event_cb_6g(req)); + else + cur_sub = ubus.subscriber((req) => event_cb(req)); + cur_sub.subscribe(obj); + push(sub_6g, cur_sub); + printf(`subscribe ${ifname}\n`); + ubus.call(obj, 'notify_response', { notify_response: 1 }); + } +} + +function cache_gc() { + let ts = time(); + + for (let ssid in keys(cache)) { + if (!ssids[ssid]) { + delete cache[ssid]; + continue; + } + + let ssid_cache = cache[ssid]; + ssid = ssids[ssid]; + + for (let addr in keys(ssid_cache)) { + let sta = ssid_cache[addr]; + let keep = ts < cache.timeout; + + if (keep && !ssid.keys[sta.key]) + keep = false; + if (keep) + sta.keydata = ssid.keys[sta.key]; + if (!keep) + delete cache[addr]; + } + } +} + +function netifd_reload() { + let data = ubus.call('network.wireless', 'status'); + + ssids = {}; + interfaces = {}; + + for (let radio_name, radio in data) { + if (!radio.up) + continue; + + for (let iface in radio.interfaces) { + let config = iface.config; + + if (config.mode != 'ap' || !iface.ifname) + continue; + + let band = radio.config.band; + let nr_data = ubus.call('hostapd.' + iface.ifname, 'rrm_nr_get_own'); + let nr; + if (nr_data && nr_data.value && nr_data.value[2]) + nr = nr_data.value[2]; + interfaces[iface.ifname] = { + band, nr, + ssid: config.ssid, + }; + + ssids[config.ssid] ??= { + interfaces: [], + keys: {}, + bands: {}, + }; + let ssid = ssids[config.ssid]; + + push(ssid.interfaces, iface.ifname); + ssid.bands[band] = iface.ifname; + for (let sta in iface.stations) { + let stacfg = sta.config; + + let key = stacfg.key; + if (!key) + continue; + + let keydata = {}; + let vid = stacfg.vid; + if (vid) + keydata.vlan = +vid; + + ssid.keys[key] = keydata; + } + } + } + printf('New config: %.J\n', { ssids, interfaces }); + cache_gc(); + create_6g_subscriber(); +} + +function iface_ssid(ifname) { + let iface = interfaces[ifname]; + if (!iface) + return; + + return iface.ssid; +} + +function sta_cache_entry_get(ssid, addr) { + let ssid_cache = cache[ssid] ?? {}; + + let entry = ssid_cache[addr]; + if (entry) + entry.timeout = time() + timeout; + + printf(`Get cache entry ssid=${ssid} addr=${addr}: ${entry}\n`); + return entry; +} + +function sta_cache_entry_add(ssid, addr, key) { + cache[ssid] ??= {}; + let ssid_cache = cache[ssid]; + let ssid_data = ssids[ssid]; + let keydata = ssid_data.keys[key]; + + let cache_data = { + timeout: time() + timeout, + ssid, key, + data: keydata ?? {}, + }; + ssid_cache[addr] = cache_data; + printf(`Added cache entry ssid=${ssid} addr=${addr}\n`); + return cache_data; +} + +function ssid_psk(ssid) { + ssid = ssids[ssid]; + if (!ssid) + return []; + + return keys(ssid.keys); +} + +function sta_auth_psk(ifname, addr) { + let ssid = iface_ssid(ifname); + if (!ssid) + return; + + let cache = sta_cache_entry_get(ssid, addr); + if (cache) + return [ cache.key ]; + + return ssid_psk(ssid); +} + +function sta_auth_cache(ifname, addr, idx) { + let ssid = iface_ssid(ifname); + if (!ssid) + return; + + let cache = sta_cache_entry_get(ssid, addr); + if (cache) + return cache.data; + + let psk = ssid_psk(ssid); + if (!psk) + return; + + psk = psk[idx]; + if (!psk) + return; + + cache = sta_cache_entry_add(ssid, addr, psk); + if (!cache) + return; + + let ssid_data = ssids[ssid]; + if (!ssid_data) + return cache.data; + + let target_ifname = ssid_data.bands['6g']; + if (!target_ifname) + return cache.data; + + let target_iface = interfaces[target_ifname]; + if (!target_iface) + return cache.data; + + cache.timer = uloop.timer(30 * 1000, () => { + let msg = { + addr, + disassociation_imminent: false, + neighbors: [ + target_iface.nr + ], + abridged: false, + }; + printf(`ubus call hostapd.${ifname} bss_transition_request '${msg}'\n`); + ubus.call('hostapd.' + ifname, 'bss_transition_request', msg); + delete cache.timer; + }); + + return cache.data; +} + +function auth_cb(msg) { + let data = msg.data; + + printf(`Event ${msg.type}: ${msg.data}\n`); + switch (msg.type) { + case 'sta_auth': + return { + psk: sta_auth_psk(data.iface, data.sta), + force_psk: true, + }; + case 'sta_connected': + if (data.psk_idx == null) + return; + return sta_auth_cache(data.iface, data.sta, data.psk_idx); + case 'reload': + netifd_reload(); + reload_timer.set(5000); + break; + } +} + +let ubus_methods = { + state: { + call: function(req) { + return { + interfaces, + ssids, + cache + }; + }, + args: { + } + }, +}; + +reload_timer = uloop.timer(-1, () => { netifd_reload(); }); +gc_timer = uloop.timer(1000, () => { gc_timer.set(30 * 1000); cache_gc(); }); +ubus.publish('mpsk', ubus_methods); +let sub = ubus.subscriber(auth_cb); +let listener = ubus.listener('ubus.object.add', (event, msg) => { + if (msg.path == 'hostapd-auth') + sub.subscribe(msg.path); +}); +sub.subscribe('hostapd-auth'); +netifd_reload(); +uloop.run(); diff --git a/feeds/ipq807x_v5.4/hostapd/files/mpskd.init b/feeds/ipq807x_v5.4/hostapd/files/mpskd.init new file mode 100644 index 000000000..8d6c37703 --- /dev/null +++ b/feeds/ipq807x_v5.4/hostapd/files/mpskd.init @@ -0,0 +1,13 @@ +#!/bin/sh /etc/rc.common + +START=19 + +USE_PROCD=1 +NAME=mpskd + +start_service() { + procd_open_instance mpskd + procd_set_param command /usr/share/hostap/mpskd + procd_set_param respawn + procd_close_instance +} diff --git a/feeds/ipq807x_v5.4/hostapd/patches/zzz-deny-6g-probe-resp.patch b/feeds/ipq807x_v5.4/hostapd/patches/zzz-deny-6g-probe-resp.patch new file mode 100644 index 000000000..d6c49a7fe --- /dev/null +++ b/feeds/ipq807x_v5.4/hostapd/patches/zzz-deny-6g-probe-resp.patch @@ -0,0 +1,156 @@ +--- a/src/ap/ieee802_11.c ++++ b/src/ap/ieee802_11.c +@@ -7740,7 +7740,7 @@ enum colocation_mode get_colocation_mode + } + + +-size_t hostapd_eid_rnr_len(struct hostapd_data *hapd, u32 type) ++size_t _hostapd_eid_rnr_len(struct hostapd_data *hapd, u32 type, int add_6g) + { + size_t len = 0, current_len = 0; + enum colocation_mode mode = get_colocation_mode(hapd); +@@ -7753,7 +7753,7 @@ size_t hostapd_eid_rnr_len(struct hostap + /* fallthrough */ + + case WLAN_FC_STYPE_PROBE_RESP: +- if (mode == COLOCATED_LOWER_BAND) ++ if (add_6g && mode == COLOCATED_LOWER_BAND) + len += hostapd_eid_rnr_colocation_len(hapd, + ¤t_len); + +@@ -7776,6 +7776,10 @@ size_t hostapd_eid_rnr_len(struct hostap + return len; + } + ++size_t hostapd_eid_rnr_len(struct hostapd_data *hapd, u32 type) ++{ ++ return _hostapd_eid_rnr_len(hapd, type, 1); ++} + + static u8 *hostapd_eid_rnr_iface(struct hostapd_data *hapd, + struct hostapd_data *reporting_hapd, +@@ -7938,7 +7942,7 @@ static u8 *hostapd_eid_neighbor_report_d + } + + +-u8 * hostapd_eid_rnr(struct hostapd_data *hapd, u8 *eid, u32 type) ++u8 * _hostapd_eid_rnr(struct hostapd_data *hapd, u8 *eid, u32 type, int add_6g) + { + u8 *eid_start = eid; + size_t current_len = 0; +@@ -7955,7 +7959,7 @@ u8 * hostapd_eid_rnr(struct hostapd_data + /* fallthrough */ + + case WLAN_FC_STYPE_PROBE_RESP: +- if (mode == COLOCATED_LOWER_BAND) ++ if (add_6g && mode == COLOCATED_LOWER_BAND) + eid = hostapd_eid_rnr_colocation(hapd, eid, + ¤t_len); + +@@ -7981,4 +7985,9 @@ u8 * hostapd_eid_rnr(struct hostapd_data + return eid; + } + ++u8 * hostapd_eid_rnr(struct hostapd_data *hapd, u8 *eid, u32 type) ++{ ++ return _hostapd_eid_rnr(hapd, eid, type, 1); ++} ++ + #endif /* CONFIG_NATIVE_WINDOWS */ +--- a/src/ap/ieee802_11.h ++++ b/src/ap/ieee802_11.h +@@ -135,6 +135,7 @@ u8 * hostapd_eid_time_zone(struct hostap + int hostapd_update_time_adv(struct hostapd_data *hapd); + void hostapd_client_poll_ok(struct hostapd_data *hapd, const u8 *addr); + u8 * hostapd_eid_bss_max_idle_period(struct hostapd_data *hapd, u8 *eid); ++u8 * _hostapd_eid_rnr(struct hostapd_data *hapd, u8 *eid, u32 type, int add_6g); + u8 * hostapd_eid_rnr(struct hostapd_data *hapd, u8 *eid, u32 type); + u8 * hostapd_eid_multiple_bssid(struct hostapd_data *hapd, + struct hostapd_data *req_bss, u8 *eid, u8 *end, +@@ -146,6 +147,7 @@ size_t hostapd_eid_multiple_bssid_len(st + struct hostapd_data *req_bss, u32 type, + const u8 *known_bssids, + u8 known_bssids_len, size_t *rnr_len); ++size_t _hostapd_eid_rnr_len(struct hostapd_data *hapd, u32 type, int add_6g); + size_t hostapd_eid_rnr_len(struct hostapd_data *hapd, u32 type); + int auth_sae_init_committed(struct hostapd_data *hapd, struct sta_info *sta); + #ifdef CONFIG_SAE +--- a/src/ap/beacon.c ++++ b/src/ap/beacon.c +@@ -463,7 +463,8 @@ static u8 * hostapd_eid_supported_op_cla + static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd, + const struct ieee80211_mgmt *req, + int is_p2p, size_t *resp_len, +- const u8 *known_bssids, u8 known_bssids_len) ++ const u8 *known_bssids, u8 known_bssids_len, ++ int add_6g) + { + struct hostapd_data *req_bss = NULL; + struct ieee80211_mgmt *resp; +@@ -523,7 +524,7 @@ static u8 * hostapd_gen_probe_resp(struc + known_bssids, + known_bssids_len, + NULL); +- buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP); ++ buflen += _hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP, add_6g); + + resp = os_zalloc(buflen); + if (resp == NULL) +@@ -706,7 +707,7 @@ static u8 * hostapd_gen_probe_resp(struc + pos = hostapd_eid_mbo(hapd, pos, (u8 *) resp + buflen - pos); + pos = hostapd_eid_owe_trans(hapd, pos, (u8 *) resp + buflen - pos); + pos = hostapd_eid_dpp_cc(hapd, pos, (u8 *) resp + buflen - pos); +- pos = hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_PROBE_RESP); ++ pos = _hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_PROBE_RESP, add_6g); + + if (hapd->conf->vendor_elements) { + os_memcpy(pos, wpabuf_head(hapd->conf->vendor_elements), +@@ -930,6 +931,7 @@ void handle_probe_req(struct hostapd_dat + .ssi_signal = ssi_signal, + .elems = &elems, + }; ++ int ubus_response; + + if (hapd->iconf->rssi_ignore_probe_request && ssi_signal && + ssi_signal < hapd->iconf->rssi_ignore_probe_request) +@@ -1118,7 +1120,12 @@ void handle_probe_req(struct hostapd_dat + } + #endif /* CONFIG_P2P */ + +- if (hostapd_ubus_handle_event(hapd, &req)) { ++ ubus_response = hostapd_ubus_handle_event(hapd, &req); ++ ++ if (ubus_response == 2) { ++ wpa_printf(MSG_DEBUG, "Probe request for " MACSTR " without 6G RNR.\n", ++ MAC2STR(mgmt->sa)); ++ } else if (ubus_response) { + wpa_printf(MSG_DEBUG, "Probe request for " MACSTR " rejected by ubus handler.\n", + MAC2STR(mgmt->sa)); + return; +@@ -1170,7 +1177,7 @@ void handle_probe_req(struct hostapd_dat + + resp = hostapd_gen_probe_resp(hapd, mgmt, elems.p2p != NULL, + &resp_len, elems.known_bssids, +- elems.known_bssids_len); ++ elems.known_bssids_len, ubus_response == 2 ? 0 : 1); + if (resp == NULL) + return; + +@@ -1239,7 +1246,7 @@ static u8 * hostapd_probe_resp_offloads( + "this"); + + /* Generate a Probe Response template for the non-P2P case */ +- return hostapd_gen_probe_resp(hapd, NULL, 0, resp_len, NULL, 0); ++ return hostapd_gen_probe_resp(hapd, NULL, 0, resp_len, NULL, 0, 1); + } + + #endif /* NEED_AP_MLME */ +@@ -1269,7 +1276,7 @@ static u8 * hostapd_unsol_bcast_probe_re + + return hostapd_gen_probe_resp(hapd, NULL, 0, + ¶ms->unsol_bcast_probe_resp_tmpl_len, +- NULL, 0); ++ NULL, 0, 1); + } + #endif /* CONFIG_IEEE80211AX */ + diff --git a/feeds/ipq807x_v5.4/hostapd/src/src/ap/ubus.c b/feeds/ipq807x_v5.4/hostapd/src/src/ap/ubus.c index 1483d23cc..48a262550 100644 --- a/feeds/ipq807x_v5.4/hostapd/src/src/ap/ubus.c +++ b/feeds/ipq807x_v5.4/hostapd/src/src/ap/ubus.c @@ -1864,6 +1864,7 @@ int hostapd_ubus_handle_event(struct hostapd_data *hapd, struct hostapd_ubus_req blob_buf_init(&b, 0); blobmsg_add_macaddr(&b, "address", addr); + blobmsg_add_string(&b, "ifname", hapd->conf->iface); if (req->mgmt_frame) blobmsg_add_macaddr(&b, "target", req->mgmt_frame->da); if (req->ssi_signal) diff --git a/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/wifi-6e-mpsk.json b/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/wifi-6e-mpsk.json new file mode 100644 index 000000000..94b527c28 --- /dev/null +++ b/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/wifi-6e-mpsk.json @@ -0,0 +1,121 @@ +{ + "uuid": 2, + "radios": [ + { + "band": "2G", + "country": "US", + "channel-mode": "HE", + "channel-width": 20, + "channel": "auto" + }, { + "band": "5G", + "country": "US", + "channel-mode": "HE", + "channel-width": 80, + "channel": 36 + }, { + "band": "6G", + "country": "US", + "channel-mode": "HE", + "channel-width": 80, + "channel": 33 + } + ], + + "interfaces": [ + { + "name": "WAN", + "role": "upstream", + "services": [ "lldp" ], + "ethernet": [ + { + "select-ports": [ + "WAN*" + ] + } + ], + "ipv4": { + "addressing": "dynamic" + }, + "ssids": [ + { + "name": "OpenWifi-roam", + "wifi-bands": [ + "2G", "5G" + ], + "bss-mode": "ap", + "encryption": { + "proto": "psk2", + "key": "OpenWifi", + "ieee80211w": "optional" + }, + "rrm": { + "reduced-neighbor-reporting": true + }, + "multi-psk": [ + { + "key": "aaaaaaaa" + }, { + "key": "bbbbbbbb" + } + ], + "roaming": true + }, { + "name": "OpenWifi-roam", + "wifi-bands": [ + "6G" + ], + "bss-mode": "ap", + "encryption": { + "proto": "sae", + "key": "OpenWifi", + "ieee80211w": "required" + }, + "rrm": { + "reduced-neighbor-reporting": true + }, + "roaming": true + } + ] + }, + { + "name": "LAN", + "role": "downstream", + "services": [ "ssh", "lldp" ], + "ethernet": [ + { + "select-ports": [ + "LAN*" + ] + } + ], + "ipv4": { + "addressing": "static", + "subnet": "192.168.1.1/24", + "dhcp": { + "lease-first": 10, + "lease-count": 100, + "lease-time": "6h" + } + } + } + ], + "metrics": { + "statistics": { + "interval": 120, + "types": [ "ssids", "lldp", "clients" ] + }, + "health": { + "interval": 120 + } + }, + "services": { + "lldp": { + "describe": "uCentral", + "location": "universe" + }, + "ssh": { + "port": 22 + } + } +}