mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-31 22:05:13 +08:00
🍓 Sync 2026-09-15 02:13:06
This commit is contained in:
1 parent
0e5393594c
commit
1dd96ac2bb
146 files changed
+26541
-508
No files matched your search
@@ -3,7 +3,8 @@
|
||||
'use strict';
|
||||
import { readfile, writefile, rename, chmod } from 'fs';
|
||||
import { connect } from 'ubus';
|
||||
import { arp_leases, update_history } from 'luci.client_history';
|
||||
import { netbios_names } from 'luci.client_names';
|
||||
import { arp_leases, update_history, reverse_names } from 'luci.client_history';
|
||||
|
||||
const path = '/tmp/luci-client-history.json';
|
||||
const boot_id = trim(readfile('/proc/sys/kernel/random/boot_id') || '');
|
||||
@@ -13,11 +14,24 @@ let previous = {};
|
||||
try { previous = json(readfile(path) || '{}'); } catch (e) {}
|
||||
const bus = connect();
|
||||
let current = bus ? bus.call('luci-rpc', 'getDHCPLeases', {}) : null;
|
||||
current ||= {};
|
||||
const active = arp_leases(readfile('/proc/net/arp'));
|
||||
const reverse_dns = reverse_names(previous, current, active, boot_id,
|
||||
int(split(readfile('/proc/uptime'), ' ')[0]), function(addrs) {
|
||||
let names = bus ? (bus.call('network.rrdns', 'lookup', { addrs, timeout: 1000, limit: 8 }) || {}) : {};
|
||||
return { ...names, ...netbios_names(filter(addrs, ip => !names[ip])) };
|
||||
});
|
||||
if (bus)
|
||||
bus.disconnect();
|
||||
current ||= {};
|
||||
current.dhcp_leases = [ ...(current.dhcp_leases || []), ...arp_leases(readfile('/proc/net/arp')) ];
|
||||
const next = sprintf('%J', update_history(previous, current, boot_id));
|
||||
// Recheck ownership after the asynchronous DNS query.
|
||||
const owners = {};
|
||||
for (let lease in arp_leases(readfile('/proc/net/arp')))
|
||||
owners[lease.ipaddr] = lease.macaddr;
|
||||
current.dhcp_leases = [ ...(current.dhcp_leases || []),
|
||||
...filter(active, lease => owners[lease.ipaddr] == lease.macaddr) ];
|
||||
const result = update_history(previous, current, boot_id);
|
||||
result.reverse_dns = reverse_dns;
|
||||
const next = sprintf('%J', result);
|
||||
if (next != readfile(path)) {
|
||||
if (writefile(path + '.new', next) != length(next))
|
||||
exit(1);
|
||||
|
||||
@@ -47,3 +47,43 @@ export function update_history(previous, current, boot_id) {
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Cache both successful and unsuccessful PTR lookups. Bind results to MAC + IP
|
||||
// so an address handed to another client cannot inherit the former name.
|
||||
export function reverse_names(previous, current, active, boot_id, now, lookup) {
|
||||
let old = type(previous) == 'object' && previous.boot_id == boot_id &&
|
||||
type(previous.reverse_dns) == 'object' ? previous.reverse_dns : {};
|
||||
let cache = {}, named = {}, pending = [];
|
||||
for (let family in ['dhcp_leases', 'dhcp6_leases'])
|
||||
for (let lease in current[family] || [])
|
||||
if (type(lease.hostname) == 'string' && length(lease.hostname) &&
|
||||
lease.hostname != '*' && lease.hostname != lease.ipaddr && lease.macaddr)
|
||||
named[uc(lease.macaddr)] = true;
|
||||
for (let lease in active) {
|
||||
if (named[lease.macaddr])
|
||||
continue;
|
||||
let key = lease.macaddr + '@' + lease.ipaddr;
|
||||
let entry = type(old[key]) == 'object' ? old[key] : {};
|
||||
cache[key] = entry;
|
||||
if ((!entry.next || entry.next <= now) && length(pending) < 32) {
|
||||
push(pending, lease.ipaddr);
|
||||
entry.next = now + 300;
|
||||
}
|
||||
}
|
||||
let answers = length(pending) ? lookup(pending) : {};
|
||||
for (let lease in active) {
|
||||
let entry = cache[lease.macaddr + '@' + lease.ipaddr];
|
||||
if (!entry)
|
||||
continue;
|
||||
let name = answers?.[lease.ipaddr];
|
||||
if (type(name) == 'string') {
|
||||
name = replace(name, /\.$/, '');
|
||||
if (length(name) <= 253 && match(name, /^[a-z0-9_][a-z0-9_.-]*$/i) &&
|
||||
!match(name, /^[0-9.]+$/))
|
||||
entry.hostname = name;
|
||||
}
|
||||
if (entry.hostname)
|
||||
lease.hostname = entry.hostname;
|
||||
}
|
||||
return cache;
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// RFC 1002 NBSTAT: unicast queries only, to known LAN clients.
|
||||
import { create, poll, AF_INET, SOCK_DGRAM, POLLIN, MSG_DONTWAIT } from 'socket';
|
||||
|
||||
function byte(data, offset) { return ord(substr(data, offset, 1)); };
|
||||
function word(data, offset) { return byte(data, offset) * 256 + byte(data, offset + 1); };
|
||||
function skip_name(data, offset) {
|
||||
while (offset < length(data)) {
|
||||
let size = byte(data, offset++);
|
||||
if (!size) return offset;
|
||||
if ((size & 0xc0) == 0xc0)
|
||||
return offset < length(data) ? offset + 1 : -1;
|
||||
if (size > 63 || offset + size > length(data)) return -1;
|
||||
offset += size;
|
||||
}
|
||||
return -1;
|
||||
};
|
||||
|
||||
export function nbstat_name(data, id) {
|
||||
if (type(data) != 'string' || length(data) < 12 || word(data, 0) != id ||
|
||||
(word(data, 2) & 0xfa0f) != 0x8000 || word(data, 6) != 1)
|
||||
return null;
|
||||
let offset = 12;
|
||||
for (let i = 0; i < word(data, 4); i++) {
|
||||
offset = skip_name(data, offset);
|
||||
if (offset < 0 || offset + 4 > length(data)) return null;
|
||||
offset += 4;
|
||||
}
|
||||
offset = skip_name(data, offset);
|
||||
if (offset < 0 || offset + 11 > length(data) || word(data, offset) != 0x21 ||
|
||||
word(data, offset + 2) != 1) return null;
|
||||
let size = word(data, offset + 8);
|
||||
offset += 10;
|
||||
if (size < 1 || offset + size > length(data)) return null;
|
||||
let count = byte(data, offset++), server = null;
|
||||
if (1 + count * 18 > size) return null;
|
||||
for (let i = 0; i < count; i++, offset += 18) {
|
||||
let suffix = byte(data, offset + 15), flags = word(data, offset + 16);
|
||||
let name = trim(substr(data, offset, 15));
|
||||
if ((flags & 0x8000) || !match(name, /^[a-z0-9_][a-z0-9_.-]*$/i)) continue;
|
||||
if (suffix == 0) return name;
|
||||
if (suffix == 0x20) server = name;
|
||||
}
|
||||
return server;
|
||||
};
|
||||
|
||||
export function netbios_names(addresses) {
|
||||
let jobs = [], result = {};
|
||||
for (let ip in addresses) {
|
||||
if (length(jobs) >= 32) break;
|
||||
let sock = create(AF_INET, SOCK_DGRAM);
|
||||
if (!sock) continue;
|
||||
let id = (int(time()) + length(jobs)) & 65535;
|
||||
let query = chr(id >> 8) + chr(id & 255) + '\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00' +
|
||||
'\x20CKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x21\x00\x01';
|
||||
if (!sock.connect({ family: AF_INET, address: ip, port: 137 }) || sock.send(query) != length(query)) {
|
||||
sock.close();
|
||||
continue;
|
||||
}
|
||||
push(jobs, [sock, POLLIN, ip, id]);
|
||||
}
|
||||
// Batch all clients; never wait one second per address.
|
||||
for (let round = 0; round < 4 && length(jobs); round++) {
|
||||
let events = poll(250, ...jobs) || [];
|
||||
for (let event in events) {
|
||||
if (!event[1]) continue;
|
||||
let name = nbstat_name(event[0].recv(4096, MSG_DONTWAIT), event[3]);
|
||||
if (name) result[event[2]] = name;
|
||||
event[0].close();
|
||||
jobs = filter(jobs, job => job[2] != event[2]);
|
||||
}
|
||||
}
|
||||
for (let job in jobs) job[0].close();
|
||||
return result;
|
||||
};
|
||||
Reference in new issue
Block a user