From 3b7ef3e22bb03c4ed65c26132056c58acecc0e5c 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 | 1 + feeds/ipq807x_v5.4/hostapd/files/mpskd.uc | 276 ++++++++++++++++++ .../etc/ucentral/examples/wifi-6e-mpsk.json | 118 ++++++++ 3 files changed, 395 insertions(+) create mode 100644 feeds/ipq807x_v5.4/hostapd/files/mpskd.uc 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..e61178ee9 100644 --- a/feeds/ipq807x_v5.4/hostapd/Makefile +++ b/feeds/ipq807x_v5.4/hostapd/Makefile @@ -612,6 +612,7 @@ 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_DATA) ./files/mpskd.uc $(1)/usr/share/hostap/ endef define Install/supplicant diff --git a/feeds/ipq807x_v5.4/hostapd/files/mpskd.uc b/feeds/ipq807x_v5.4/hostapd/files/mpskd.uc new file mode 100644 index 000000000..b58622dbd --- /dev/null +++ b/feeds/ipq807x_v5.4/hostapd/files/mpskd.uc @@ -0,0 +1,276 @@ +#!/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(ifname, req) { + if (req.type == "coa") + return 0; + + if (req.type == "probe") + return 0; + + let addr = req.data.address; + let iface = interfaces[ifname]; + if (!iface) + return 0; + + let ssid = iface.ssid; + if (!ssid) + return 0; + + let ssid_cache = cache[ssid]; + if (ssid_cache && ssid_cache[addr]) + return 0; + + warn(`Drop ${req.type} on ${ifname} from ${addr}\n`); + return 5; +} + +function create_6g_subscriber() { + for (let cur_sub in sub_6g) + cur_sub.remove(); + sub_6g = []; + + for (let ifname, iface in interfaces) { + if (iface.band != "6g") + continue; + + let obj = "hostapd."+ifname; + let cur_sub = ubus.subscriber((req) => event_cb_6g(ifname, req)); + cur_sub.subscribe(obj); + push(sub_6g, cur_sub); + 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; + } + } + } + warn(sprintf("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; + + warn(`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; + warn(`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(5000, () => { + let msg = { + addr, + disassociation_imminent: false, + neighbors: [ + target_iface.nr + ], + abridged: false, + }; + ubus.call("hostapd."+ifname, "bss_transition_request", msg); + delete cache.timer; + }); + + return cache.data; +} + +function auth_cb(msg) { + let data = msg.data; + + warn(`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 - 1); + case "reload": + netifd_reload(); + reload_timer.set(5000); + break; + } +} + +reload_timer = uloop.timer(-1, () => { netifd_reload(); }); +gc_timer = uloop.timer(1000, () => { gc_timer.set(30 * 1000); cache_gc(); }); +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/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..5893fa03a --- /dev/null +++ b/feeds/ucentral/ucentral-schema/files/etc/ucentral/examples/wifi-6e-mpsk.json @@ -0,0 +1,118 @@ +{ + "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" + }, + "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 + } + } +}