🍕 Sync 2026-09-14 23:30:37

This commit is contained in:
github-actions[bot] committed 2026-09-14 23:30:37 +08:00
1 parent 0603b094cf
commit 81fe4cd866
214 files changed
+10806 -2630

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
The overview combines DHCPv4, DHCPv6 and resolved `br-lan` ARP entries by MAC.
IPv6 leases without a MAC can join a client only when their addresses have an
unambiguous MAC mapping in host hints or another lease. Hostnames alone never
merge devices. DUID/IAID identify otherwise unknown lease records and preserve
IPv6 reservation actions.
`luci-client-history` samples leases and ARP every ten seconds without requiring
an open browser. It atomically checkpoints discovered records in
`/tmp/luci-client-history.json`. Records are retained after lease/ARP expiry and
collector/rpcd restarts; a new boot clears them. Collection starts when the
service is installed/enabled, so earlier expired records cannot be reconstructed.
Historical addresses are dimmed and excluded from live rates and reservation
buttons. Totals remain the existing per-MAC conntrack accounting totals.
Run `ucode client-history.uc` on a target with the installed
`luci.client_history` module. The tests cover LAN filtering, expiry, collector
restart, IP reassignment, changing IPv6 addresses and boot-ID reset without
rebooting the device. Run the existing frontend tests with
`node libs/rpcd-mod-luci/tests/traffic-ui.js` from the LuCI repository.
+29
View File
@@ -0,0 +1,29 @@
// Run with ucode after installing luci.client_history on the test target.
import { arp_leases, update_history } from 'luci.client_history';
function check(value, message) {
if (!value) die(message + '\n');
};
const arp = '192.168.0.2 0x1 0x2 aa:bb:cc:dd:ee:ff * br-lan\n' +
'192.168.0.3 0x1 0x0 00:11:22:33:44:66 * br-lan\n' +
'192.168.9.1 0x1 0x2 00:11:22:33:44:77 * eth1\n';
let first = update_history({}, {
dhcp_leases: arp_leases(arp),
dhcp6_leases: [{ macaddr: 'aa:bb:cc:dd:ee:ff', duid: 'uuid', iaid: '1', ip6addrs: ['2001:db8::2/128'] }]
}, 'boot-a');
check(length(first.dhcp_leases) == 1, 'filter non-LAN and unresolved ARP');
let absent = update_history(first, {}, 'boot-a');
check(length(absent.dhcp_leases) == 1 && length(absent.dhcp6_leases) == 1, 'retain expired DHCP and ARP');
let restored = update_history(json(sprintf('%J', absent)), {}, 'boot-a');
check(sprintf('%J', restored) == sprintf('%J', absent), 'restore after collector restart');
let updated = update_history(restored, {
dhcp_leases: [{ macaddr: '00:11:22:33:44:66', ipaddr: '192.168.0.2', interface: 'br-lan' }],
dhcp6_leases: [{ macaddr: 'AA:BB:CC:DD:EE:FF', duid: 'uuid', iaid: '1', ip6addrs: ['2001:db8::3/128'] }]
}, 'boot-a');
check(length(updated.dhcp_leases) == 2, 'preserve former owner after address reuse');
check(length(updated.dhcp6_leases) == 1 && length(updated.dhcp6_leases[0].ip6addrs) == 2, 'retain IPv6 address changes');
let repeat = update_history(updated, { dhcp_leases: arp_leases(arp) }, 'boot-a');
check(length(repeat.dhcp_leases) == 2, 'deduplicate repeated discoveries');
let rebooted = update_history(updated, {}, 'boot-b');
check(!length(rebooted.dhcp_leases) && !length(rebooted.dhcp6_leases), 'new boot resets history');
check(!length(update_history('invalid', {}, 'boot-a').dhcp_leases), 'recover invalid state');
print('client history: LAN filtering, expiry, restart, reassignment, IPv6 changes and boot reset passed\n');