+++ /dev/null
-#!/usr/bin/env ucode
-'use strict';
-import * as uloop from "uloop";
-import * as libubus from "ubus";
-import * as unetmsg from "unetmsg.client";
-import { readfile, glob, basename } from "fs";
-let uht = require("uht");
-push(REQUIRE_SEARCH_PATH, "/usr/share/ufp/*.uc");
-
-uloop.init();
-let ubus = libubus.connect();
-let unet = unetmsg.open(ubus);
-let fingerprints = {};
-let fingerprint_ht = [];
-let devices = {};
-let gc_timer;
-let weight = {
- "mac-oui": 3.0,
-};
-
-function get_weight(type) {
- let w = weight[type];
- if (w)
- return w;
- type = split(type, "-");
- if (length(type) < 2)
- return null;
- pop(type);
- type = join("-", type);
-
- return weight[type];
-}
-
-function match_fingerprint(key)
-{
- let fp = [];
-
- for (let ht in fingerprint_ht) {
- let cur_fp = ht.get(null, key);
- if (!cur_fp)
- continue;
- push(fp, ...cur_fp);
- }
-
- let user_fp = fingerprints[key];
- if (user_fp)
- push(fp, ...user_fp);
-
- return fp;
-}
-
-unet.publish("ufp", (req) => {
- let data = req.args;
- switch (data.type) {
- case "get_data":
- let mac = data.macaddr;
- if (mac)
- return { data: devices[mac] };
- return { data: devices };
- }
-});
-unet.subscribe("ufp");
-
-function dev_timestamp_cmp(a, b)
-{
- return a[1].timestamp - b[1].timestamp;
-}
-
-function network_devices() {
- let device_lists = [
- devices
- ];
-
- unet.request("ufp", "get_data", {}, (msg) => {
- push(device_lists, msg.data);
- });
-
- let cur_devices = [];
- for (let list in device_lists)
- for (let mac, dev in list)
- push(cur_devices, [ mac, dev ]);
-
- let ret = {};
- sort(cur_devices, dev_timestamp_cmp);
- for (let entry in cur_devices) {
- let mac = entry[0];
- let data = entry[1];
- if (!ret[mac]) {
- ret[mac] = data;
- continue;
- }
-
- let new_data = { ...data };
- new_data.data = { ...ret[mac].data, ...data.data };
- new_data.meta = { ...ret[mac].meta, ...data.meta };
- ret[mac] = new_data;
- }
-
- return ret;
-}
-
-let global = {
- uloop: uloop,
- ubus: ubus,
- weight: weight,
- devices: devices,
- fingerprints: fingerprints,
- plugins: [],
-
- load_fingerprint_json: function(file) {
- let data = json(readfile(file));
- fingerprints = data;
- },
-
- get_weight: get_weight,
-
- add_weight: function(data) {
- for (let entry in data)
- weight[entry] = data[entry];
- },
-
- device_refresh: function(mac) {
- mac = lc(mac);
- let dev = devices[mac];
- if (!dev)
- return;
-
- dev.timestamp = time();
- },
-
- device_add_data: function(mac, line) {
- mac = lc(mac);
- let dev = devices[mac];
- if (!dev) {
- dev = devices[mac] = {
- data: {},
- meta: {},
- timestamp: time()
- };
- let oui = "mac-oui-" + join("", slice(split(mac, ":"), 0, 3));
- dev.data[oui] = `${oui}|1`;
- }
-
- if (substr(line, 0, 1) == "%") {
- line = substr(line, 1);
- let meta = split(line, "|", 3);
- if (!meta[2])
- return;
-
- dev.meta[meta[0]] ??= {};
- if (!get_weight(meta[1]))
- return;
-
- dev.meta[meta[0]][meta[1]] = meta[2];
- return;
- }
-
- let fp = split(line, "|", 2);
- if (!fp[1])
- return;
-
- dev.data[fp[0]] = line;
- }
-};
-
-function load_plugins()
-{
- let plugins = glob("/usr/share/ufp/plugin_*.uc");
- for (let name in plugins) {
- name = substr(basename(name), 0, -3);
- try {
- let plugin = require(name);
- plugin.init(global);
- push(global.plugins, plugin);
- } catch (e) {
- warn(`Failed to load plugin ${name}: ${e}\n${e.stacktrace[0].context}\n`);
- }
- }
-}
-
-function refresh_plugins()
-{
- for (let plugin in global.plugins) {
- if (!plugin.refresh)
- continue;
-
- try {
- plugin.refresh();
- } catch (e) {
- warn(`Failed to refresh plugin: ${e}\n${e.stacktrace[0].context}\n`);
- }
- }
-}
-
-function device_gc()
-{
- gc_timer.set(60 * 60 * 1000);
- let timeout = time() - 60 * 60 * 24;
-
- for (let mac in devices) {
- if (devices[mac].timestamp < timeout)
- delete devices[mac];
- }
-}
-
-// returns: { "<meta>": { "<val>": [ <weight>, [ <fingerprints> ] ] } }
-function __device_match_list(mac, devices)
-{
- let dev = devices[mac];
- if (!dev || !length(dev))
- return null;
-
- let ret = {};
- let data = dev.data;
- let match_devs = [];
-
- for (let fp in data) {
- let match = match_fingerprint(data[fp]);
- for (let match_cur in match)
- push(match_devs, [ match_cur, global.get_weight(fp), fp ]);
- }
-
- for (let meta in dev.meta) {
- let meta_cur = dev.meta[meta];
- for (let type in meta_cur) {
- let match = {};
- match[meta] = meta_cur[type];
- push(match_devs, [ match, global.get_weight(type), type ]);
- }
- }
-
- for (let i = 0; i < length(match_devs); i++) {
- let match = match_devs[i];
- let match_data = match[0];
- let match_weight = match[1];
- let match_fp = [ match[2] ];
- let meta_entry = {};
-
- for (let j = 0; j < length(match_devs); j++) {
- if (j == i)
- continue;
-
- let cur = match_devs[j];
- let cur_data = cur[0];
- for (let key in cur_data) {
- if (lc(match_data[key]) == lc(cur_data[key])) {
- match_weight += cur[1];
- push(match_fp, cur[2]);
- break;
- }
- }
- }
-
- for (let key in match_data) {
- let val = match_data[key];
- ret[key] ??= {};
- let ret_key = ret[key];
-
- ret_key[val] ??= [ 0.0, {} ];
- let ret_val = ret_key[val];
-
- ret_val[0] += match_weight;
- for (let fp in match_fp)
- ret_val[1][fp]++;
- }
- }
-
- for (let key in ret) {
- let ret_key = ret[key];
- for (let val in ret_key) {
- let ret_val = ret_key[val];
- ret_val[1] = keys(ret_val[1]);
- }
- }
-
- return ret;
-}
-
-function device_match_list(mac, devices)
-{
- let match = __device_match_list(mac, devices);
-
- for (let meta in match) {
- let match_meta = match[meta];
- let meta_list = keys(match_meta);
- sort(meta_list, (a, b) => match_meta[b][0] - match_meta[a][0]);
- match[meta] = map(meta_list, (key) => [ key, match_meta[key][0], match_meta[key][1] ]);
- }
-
- return match;
-}
-
-global.ubus_object = {
- load_fingerprints: {
- args: {
- file: "",
- },
- call: function(req) {
- let file = req.args.file;
- if (!file)
- return libubus.STATUS_INVALID_ARGUMENT;
-
- try {
- global.load_fingerprint_json(file);
- } catch (e) {
- warn(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}, file=${file}\n`);
- return libubus.STATUS_INVALID_ARGUMENT;
- }
-
- return 0;
- }
- },
-
- get_data: {
- args: {
- macaddr: "",
- },
- call: function(req) {
- let mac = req.args.macaddr;
-
- refresh_plugins();
-
- if (!mac)
- return devices;
-
- let dev = devices[mac];
- if (!dev)
- return libubus.STATUS_NOT_FOUND;
-
- return dev;
- }
- },
-
- add_data: {
- args: {
- macaddr: "",
- data: []
- },
- call: function(req) {
- let mac = req.args.macaddr;
- let data = req.args.data;
- if (!mac || !data)
- return libubus.STATUS_INVALID_ARGUMENT;
-
- for (let line in data)
- global.device_add_data(mac, line);
-
- return 0;
- }
- },
-
- fingerprint: {
- args: {
- macaddr: "",
- weight: false
- },
- call: function(req) {
- refresh_plugins();
-
- let cur_devices = network_devices();
- let mac_list = req.args.macaddr ? [ req.args.macaddr ] : keys(cur_devices);
- let ret = {};
-
- for (let mac in mac_list) {
- let match_list = device_match_list(mac, cur_devices);
- if (!match_list)
- return libubus.STATUS_NOT_FOUND;
-
- let cur_ret = { };
- if (req.args.weight)
- cur_ret.weight = {};
- ret[mac] = cur_ret;
-
- for (let meta in match_list) {
- let match_meta = match_list[meta];
-
- if (length(match_meta) < 1)
- continue;
-
- match_meta = match_meta[0];
-
- cur_ret[meta] = match_meta[0];
- if (req.args.weight)
- cur_ret.weight[meta] = match_meta[1];
- }
- }
-
- return req.args.macaddr ? ret[req.args.macaddr] : ret;
- }
- },
-
- list: {
- args: {
- macaddr: ""
- },
- call: function(req) {
- refresh_plugins();
-
- let cur_devices = network_devices();
- let mac_list = req.args.macaddr ? [ req.args.macaddr ] : keys(cur_devices);
- let ret = {};
-
- for (let mac in mac_list) {
- let match_list = device_match_list(mac, cur_devices);
- if (!match_list)
- return libubus.STATUS_NOT_FOUND;
-
- let cur_ret = {};
- ret[mac] = cur_ret;
-
- for (let meta in match_list)
- cur_ret[meta] = match_list[meta];
- }
-
- return req.args.macaddr ? ret[req.args.macaddr] : ret;
- }
- },
-};
-
-for (let f in [ "/usr/share/ufp/devices.bin", ...glob("/usr/share/ufp/db/*.bin") ]) {
- let ht;
- try {
- ht = uht.open(f);
- } catch (e) {
- warn(`Failed to load fingerprints: ${e}\n${e.stacktrace[0].context}\n`);
- }
- if (!ht)
- continue;
- push(fingerprint_ht, ht);
-}
-load_plugins();
-ubus.publish("fingerprint", global.ubus_object);
-gc_timer = uloop.timer(1000, device_gc);
-uloop.run();
--- /dev/null
+#!/usr/bin/env ucode
+'use strict';
+import * as uloop from "uloop";
+import * as libubus from "ubus";
+import * as unetmsg from "unetmsg.client";
+import { readfile, glob, basename } from "fs";
+let uht = require("uht");
+push(REQUIRE_SEARCH_PATH, "/usr/share/ufp/*.uc");
+
+uloop.init();
+let ubus = libubus.connect();
+let unet = unetmsg.open(ubus);
+let fingerprints = {};
+let fingerprint_ht = [];
+let devices = {};
+let gc_timer;
+let weight = {
+ "mac-oui": 3.0,
+};
+
+function get_weight(type) {
+ let w = weight[type];
+ if (w)
+ return w;
+ type = split(type, "-");
+ if (length(type) < 2)
+ return null;
+ pop(type);
+ type = join("-", type);
+
+ return weight[type];
+}
+
+function match_fingerprint(key)
+{
+ let fp = [];
+
+ for (let ht in fingerprint_ht) {
+ let cur_fp = ht.get(null, key);
+ if (!cur_fp)
+ continue;
+ push(fp, ...cur_fp);
+ }
+
+ let user_fp = fingerprints[key];
+ if (user_fp)
+ push(fp, ...user_fp);
+
+ return fp;
+}
+
+unet.publish("ufp", (req) => {
+ let data = req.args;
+ switch (data.type) {
+ case "get_data":
+ let mac = data.macaddr;
+ if (mac)
+ return { data: devices[mac] };
+ return { data: devices };
+ }
+});
+unet.subscribe("ufp");
+
+function dev_timestamp_cmp(a, b)
+{
+ return a[1].timestamp - b[1].timestamp;
+}
+
+function network_devices() {
+ let device_lists = [
+ devices
+ ];
+
+ unet.request("ufp", "get_data", {}, (msg) => {
+ push(device_lists, msg.data);
+ });
+
+ let cur_devices = [];
+ for (let list in device_lists)
+ for (let mac, dev in list)
+ push(cur_devices, [ mac, dev ]);
+
+ let ret = {};
+ sort(cur_devices, dev_timestamp_cmp);
+ for (let entry in cur_devices) {
+ let mac = entry[0];
+ let data = entry[1];
+ if (!ret[mac]) {
+ ret[mac] = data;
+ continue;
+ }
+
+ let new_data = { ...data };
+ new_data.data = { ...ret[mac].data, ...data.data };
+ new_data.meta = { ...ret[mac].meta, ...data.meta };
+ ret[mac] = new_data;
+ }
+
+ return ret;
+}
+
+let global = {
+ uloop: uloop,
+ ubus: ubus,
+ weight: weight,
+ devices: devices,
+ fingerprints: fingerprints,
+ plugins: [],
+
+ load_fingerprint_json: function(file) {
+ let data = json(readfile(file));
+ fingerprints = data;
+ },
+
+ get_weight: get_weight,
+
+ add_weight: function(data) {
+ for (let entry in data)
+ weight[entry] = data[entry];
+ },
+
+ device_refresh: function(mac) {
+ mac = lc(mac);
+ let dev = devices[mac];
+ if (!dev)
+ return;
+
+ dev.timestamp = time();
+ },
+
+ device_add_data: function(mac, line) {
+ mac = lc(mac);
+ let dev = devices[mac];
+ if (!dev) {
+ dev = devices[mac] = {
+ data: {},
+ meta: {},
+ timestamp: time()
+ };
+ let oui = "mac-oui-" + join("", slice(split(mac, ":"), 0, 3));
+ dev.data[oui] = `${oui}|1`;
+ }
+
+ if (substr(line, 0, 1) == "%") {
+ line = substr(line, 1);
+ let meta = split(line, "|", 3);
+ if (!meta[2])
+ return;
+
+ dev.meta[meta[0]] ??= {};
+ if (!get_weight(meta[1]))
+ return;
+
+ dev.meta[meta[0]][meta[1]] = meta[2];
+ return;
+ }
+
+ let fp = split(line, "|", 2);
+ if (!fp[1])
+ return;
+
+ dev.data[fp[0]] = line;
+ }
+};
+
+function load_plugins()
+{
+ let plugins = glob("/usr/share/ufp/plugin_*.uc");
+ for (let name in plugins) {
+ name = substr(basename(name), 0, -3);
+ try {
+ let plugin = require(name);
+ plugin.init(global);
+ push(global.plugins, plugin);
+ } catch (e) {
+ warn(`Failed to load plugin ${name}: ${e}\n${e.stacktrace[0].context}\n`);
+ }
+ }
+}
+
+function refresh_plugins()
+{
+ for (let plugin in global.plugins) {
+ if (!plugin.refresh)
+ continue;
+
+ try {
+ plugin.refresh();
+ } catch (e) {
+ warn(`Failed to refresh plugin: ${e}\n${e.stacktrace[0].context}\n`);
+ }
+ }
+}
+
+function device_gc()
+{
+ gc_timer.set(60 * 60 * 1000);
+ let timeout = time() - 60 * 60 * 24;
+
+ for (let mac in devices) {
+ if (devices[mac].timestamp < timeout)
+ delete devices[mac];
+ }
+}
+
+// returns: { "<meta>": { "<val>": [ <weight>, [ <fingerprints> ] ] } }
+function __device_match_list(mac, devices)
+{
+ let dev = devices[mac];
+ if (!dev || !length(dev))
+ return null;
+
+ let ret = {};
+ let data = dev.data;
+ let match_devs = [];
+
+ for (let fp in data) {
+ let match = match_fingerprint(data[fp]);
+ for (let match_cur in match)
+ push(match_devs, [ match_cur, global.get_weight(fp), fp ]);
+ }
+
+ for (let meta in dev.meta) {
+ let meta_cur = dev.meta[meta];
+ for (let type in meta_cur) {
+ let match = {};
+ match[meta] = meta_cur[type];
+ push(match_devs, [ match, global.get_weight(type), type ]);
+ }
+ }
+
+ for (let i = 0; i < length(match_devs); i++) {
+ let match = match_devs[i];
+ let match_data = match[0];
+ let match_weight = match[1];
+ let match_fp = [ match[2] ];
+ let meta_entry = {};
+
+ for (let j = 0; j < length(match_devs); j++) {
+ if (j == i)
+ continue;
+
+ let cur = match_devs[j];
+ let cur_data = cur[0];
+ for (let key in cur_data) {
+ if (lc(match_data[key]) == lc(cur_data[key])) {
+ match_weight += cur[1];
+ push(match_fp, cur[2]);
+ break;
+ }
+ }
+ }
+
+ for (let key in match_data) {
+ let val = match_data[key];
+ ret[key] ??= {};
+ let ret_key = ret[key];
+
+ ret_key[val] ??= [ 0.0, {} ];
+ let ret_val = ret_key[val];
+
+ ret_val[0] += match_weight;
+ for (let fp in match_fp)
+ ret_val[1][fp]++;
+ }
+ }
+
+ for (let key in ret) {
+ let ret_key = ret[key];
+ for (let val in ret_key) {
+ let ret_val = ret_key[val];
+ ret_val[1] = keys(ret_val[1]);
+ }
+ }
+
+ return ret;
+}
+
+function device_match_list(mac, devices)
+{
+ let match = __device_match_list(mac, devices);
+
+ for (let meta in match) {
+ let match_meta = match[meta];
+ let meta_list = keys(match_meta);
+ sort(meta_list, (a, b) => match_meta[b][0] - match_meta[a][0]);
+ match[meta] = map(meta_list, (key) => [ key, match_meta[key][0], match_meta[key][1] ]);
+ }
+
+ return match;
+}
+
+global.ubus_object = {
+ load_fingerprints: {
+ args: {
+ file: "",
+ },
+ call: function(req) {
+ let file = req.args.file;
+ if (!file)
+ return libubus.STATUS_INVALID_ARGUMENT;
+
+ try {
+ global.load_fingerprint_json(file);
+ } catch (e) {
+ warn(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}, file=${file}\n`);
+ return libubus.STATUS_INVALID_ARGUMENT;
+ }
+
+ return 0;
+ }
+ },
+
+ get_data: {
+ args: {
+ macaddr: "",
+ },
+ call: function(req) {
+ let mac = req.args.macaddr;
+
+ refresh_plugins();
+
+ if (!mac)
+ return devices;
+
+ let dev = devices[mac];
+ if (!dev)
+ return libubus.STATUS_NOT_FOUND;
+
+ return dev;
+ }
+ },
+
+ add_data: {
+ args: {
+ macaddr: "",
+ data: []
+ },
+ call: function(req) {
+ let mac = req.args.macaddr;
+ let data = req.args.data;
+ if (!mac || !data)
+ return libubus.STATUS_INVALID_ARGUMENT;
+
+ for (let line in data)
+ global.device_add_data(mac, line);
+
+ return 0;
+ }
+ },
+
+ fingerprint: {
+ args: {
+ macaddr: "",
+ weight: false
+ },
+ call: function(req) {
+ refresh_plugins();
+
+ let cur_devices = network_devices();
+ let mac_list = req.args.macaddr ? [ req.args.macaddr ] : keys(cur_devices);
+ let ret = {};
+
+ for (let mac in mac_list) {
+ let match_list = device_match_list(mac, cur_devices);
+ if (!match_list)
+ return libubus.STATUS_NOT_FOUND;
+
+ let cur_ret = { };
+ if (req.args.weight)
+ cur_ret.weight = {};
+ ret[mac] = cur_ret;
+
+ for (let meta in match_list) {
+ let match_meta = match_list[meta];
+
+ if (length(match_meta) < 1)
+ continue;
+
+ match_meta = match_meta[0];
+
+ cur_ret[meta] = match_meta[0];
+ if (req.args.weight)
+ cur_ret.weight[meta] = match_meta[1];
+ }
+ }
+
+ return req.args.macaddr ? ret[req.args.macaddr] : ret;
+ }
+ },
+
+ list: {
+ args: {
+ macaddr: ""
+ },
+ call: function(req) {
+ refresh_plugins();
+
+ let cur_devices = network_devices();
+ let mac_list = req.args.macaddr ? [ req.args.macaddr ] : keys(cur_devices);
+ let ret = {};
+
+ for (let mac in mac_list) {
+ let match_list = device_match_list(mac, cur_devices);
+ if (!match_list)
+ return libubus.STATUS_NOT_FOUND;
+
+ let cur_ret = {};
+ ret[mac] = cur_ret;
+
+ for (let meta in match_list)
+ cur_ret[meta] = match_list[meta];
+ }
+
+ return req.args.macaddr ? ret[req.args.macaddr] : ret;
+ }
+ },
+};
+
+for (let f in [ "/usr/share/ufp/devices.bin", ...glob("/usr/share/ufp/db/*.bin") ]) {
+ let ht;
+ try {
+ ht = uht.open(f);
+ } catch (e) {
+ warn(`Failed to load fingerprints: ${e}\n${e.stacktrace[0].context}\n`);
+ }
+ if (!ht)
+ continue;
+ push(fingerprint_ht, ht);
+}
+load_plugins();
+ubus.publish("fingerprint", global.ubus_object);
+gc_timer = uloop.timer(1000, device_gc);
+uloop.run();