1 import * as struct from "struct";
2 let ubus, uloop, global, timer;
12 VENDOR_WPS: 0x0050f204,
15 const ie_parser_proto = {
20 parseAt: function(offset) {
21 let hdr = substr(this.buffer, offset, 2);
25 let data = this.hdr.unpack(hdr);
26 if (length(data != 2))
33 if (data[0] == 221 && len >= 4) {
34 hdr = substr(this.buffer, offset, 4);
38 let val = this.vendor_hdr.unpack(hdr);
39 if (length(val) != 1 || val[0] < 0x200)
45 } else if (data[0] == 255 && len >= 1) {
46 hdr = substr(this.buffer, offset, 2);
49 data[0] = 0x100 + this.hdr.unpack(hdr)[0];
55 data[1] = substr(this.buffer, offset, len);
56 if (length(data[1]) != len)
63 let data = this.parseAt(this.offset);
67 this.offset += data[2];
71 foreach: function(cb) {
75 while ((data = this.parseAt(offset)) != null) {
78 if (type(ret) == "boolean" && !ret)
84 function ie_parser(data) {
88 hdr: struct.new("BB"),
89 vendor_hdr: struct.new(">I"),
92 proto(parser, ie_parser_proto);
97 function format_fn(unpack_str, format)
100 data = struct.unpack(unpack_str, data);
105 return sprintf(format, data)
111 u8: format_fn("B", "%02x"),
112 le16: format_fn("<H", "%04x"),
113 le32: format_fn("<I", "%08x"),
114 bytes: (data) => join("", map(split(data, ""), unpack.u8)),
116 let fingerprint_order = [
117 "htcap", "htagg", "htmcs", "vhtcap", "vhtrxmcs", "vhttxmcs",
118 "txpow", "extcap", "wps", "hemac", "hephy"
121 function format_wps_ie(data) {
123 let len = length(data);
124 let s = struct.new("<HH");
126 while (offset + 4 <= len) {
127 let hdr = s.unpack(substr(data, offset, 4));
128 let val = substr(data, offset + 4, hdr[1]);
130 offset += 4 + hdr[1];
131 if (hdr[0] != 0x1023)
134 if (length(val) != hdr[1])
137 return replace(val, /[^A-Za-z0-9]/, "_");
143 function ie_fingerprint_str(id) {
145 return sprintf("221(%08x)", id);
147 return sprintf("255(%d)", id - 0x100);
148 return sprintf("%d", id);
151 let vendor_ie_filter = [
152 0x0050f2, // Microsoft WNN
155 0x001018, // Broadcom
159 function ie_fingerprint(data, mode) {
164 let parser = ie_parser(data);
166 parser.foreach(function(ie) {
171 caps.htcap = unpack.le16(substr(val, 0, 2));
172 caps.htagg = unpack.u8(substr(val, 2, 1));
173 caps.htmcs = unpack.le32(substr(val, 3, 4));
175 case ie_tags.VHT_CAP:
176 caps.vhtcap = unpack.le32(substr(val, 0, 4));
177 caps.vhtrxmcs = unpack.le32(substr(val, 4, 4));
178 caps.vhttxmcs = unpack.le32(substr(val, 8, 4));
180 case ie_tags.EXT_CAPAB:
181 caps.extcap = unpack.bytes(val);
183 case ie_tags.PWR_CAPABILITY:
184 caps.txpow = unpack.le16(val);
186 case ie_tags.VENDOR_WPS:
187 caps.wps = format_wps_ie(val);
190 if (mode != "wifi6") {
195 unpack.le16(substr(val, 4, 2)) +
196 unpack.le32(substr(val, 0, 4));
198 unpack.le16(substr(val, 15, 2)) +
199 unpack.le32(substr(val, 11, 4)) +
200 unpack.le32(substr(val, 7, 4));
204 let vendor = ie[0] >> 8;
205 if (!(vendor in vendor_ie_filter))
206 caps.vendor_list[sprintf("%06x", vendor)] = 1;
209 push(caps.tags, ie[0]);
215 if (mode == "wifi6" && !caps.hemac)
218 case "wifi-vendor-oui":
219 return caps.vendor_list;
224 let tags = map(caps.tags, ie_fingerprint_str);
226 join(",", tags) + "," +
228 filter(fingerprint_order, (key) => !!caps[key]),
229 (key) => `${key}:${caps[key]}`
233 function fingerprint(mac, mode, ies) {
239 let assoc = ie_fingerprint(ies.assoc_ie, mode);
243 global.device_add_data(mac, `${mode}|${assoc}`);
245 case "wifi-vendor-oui":
246 let list = ie_fingerprint(ies.assoc_ie, mode);
247 for (let oui in list) {
248 global.device_add_data(mac, `${mode}-${oui}|1`);
253 let val = ie_fingerprint(ies.assoc_ie, mode);
257 global.device_add_data(mac, `${mode}|${val}`);
262 const fingerprint_modes = [ "wifi4", "wifi6", "wifi-vendor-oui" ];
264 function client_refresh(ap, mac, prev_cache)
266 let ies = ubus.call(ap, "get_sta_ies", { address: mac });
267 if (type(ies) != "object" || !ies.assoc_ie)
270 ies.assoc_ie = b64dec(ies.assoc_ie);
272 ies.probe_ie = b64dec(ies.probe_ie);
274 for (let mode in fingerprint_modes)
275 fingerprint(mac, mode, ies);
282 let ap_objs = filter(ubus.list(), (name) => match(name, /^hostapd\./));
283 let prev_cache = ap_cache;
286 timer.set(30 * 1000);
287 for (let ap in ap_objs) {
290 let prev_ap_cache = prev_cache[ap] ?? {};
292 ap_cache[ap] = cur_cache;
294 let clients = ubus.call(ap, "get_clients").clients;
295 for (let client in clients) {
296 let client_cache = prev_ap_cache[client];
297 if (!client_cache || client_cache.assoc_ie || !client_cache.probe_ie)
298 client_cache = client_refresh(ap, client);
299 global.device_refresh(client);
314 "wifi-vendor-oui": 2.0
317 timer = uloop.timer(1000, refresh);
320 return { init, refresh };