Use DHCP leases for LAN client wake support

This commit is contained in:
seo
2026-07-21 10:13:47 +09:00
parent 2d79ee3de4
commit de96ddcda7
3 changed files with 165 additions and 9 deletions
+117 -4
View File
@@ -2732,17 +2732,54 @@ function lan_link_speed_label(string $iface = 'eth1'): string
return $mbps . ' Mbps';
}
function lan_client_rows(array $leases, array $aliases, string $iface = 'eth1'): array
function observed_lan_macs(array $leases): array
{
if ($leases === []) {
return [];
}
try {
$stmt = db()->query("
SELECT mac
FROM wifi_observed_sessions
WHERE band = 'LAN'
");
} catch (Throwable) {
return [];
}
$macs = [];
foreach ($stmt->fetchAll() as $row) {
$mac = strtolower((string)($row['mac'] ?? ''));
if (wifi_valid_mac($mac) && isset($leases[$mac])) {
$macs[$mac] = true;
}
}
return array_keys($macs);
}
function lan_client_rows(array $leases, array $aliases, array $wifiMacs = [], string $iface = 'eth1'): array
{
$rows = [];
$linkSpeed = lan_link_speed_label($iface);
$lanFdbList = lan_fdb_macs($iface);
$lanFdbMacs = array_fill_keys($lanFdbList, true);
$wifiMacs = array_fill_keys(array_map('strtolower', $wifiMacs), true);
$lanMacs = array_values(array_unique(array_merge($lanFdbList, observed_lan_macs($leases))));
foreach ($lanMacs as $mac) {
$mac = strtolower((string)$mac);
if (!wifi_valid_mac($mac) || isset($wifiMacs[$mac])) {
continue;
}
foreach (lan_fdb_macs($iface) as $mac) {
$lease = $leases[$mac] ?? [];
$leaseHostname = (string)($lease['hostname'] ?? 'N/A');
$aliasHostname = $aliases[$mac] ?? null;
$hostname = $aliasHostname ?: $leaseHostname;
$hostnameSource = $aliasHostname ? 'alias' : ($leaseHostname === 'N/A' ? 'generated' : 'lease');
$isFdbVisible = isset($lanFdbMacs[$mac]);
$rows[] = [
'band' => 'LAN',
@@ -2758,8 +2795,10 @@ function lan_client_rows(array $leases, array $aliases, string $iface = 'eth1'):
'signal_source' => 'lan',
'tx_bitrate_source' => 'lan_link',
'rx_bitrate_source' => 'lan',
'connected_time' => 'N/A',
'connected_time' => $isFdbVisible ? 'N/A' : '-',
'inactive_time' => '-',
'presence' => $isFdbVisible ? 'fdb' : 'lease',
'wol_available' => true,
'rx_bytes' => 0,
'tx_bytes' => 0,
'rx_packets' => 0,
@@ -2771,6 +2810,53 @@ function lan_client_rows(array $leases, array $aliases, string $iface = 'eth1'):
return $rows;
}
function send_wake_on_lan(string $mac, string $broadcast = '192.168.50.255', int $port = 9): array
{
$mac = strtolower(trim($mac));
if (!wifi_valid_mac($mac)) {
throw new InvalidArgumentException('bad_mac');
}
$leases = dnsmasq_leases();
if (!isset($leases[$mac])) {
throw new InvalidArgumentException('unknown_dhcp_client');
}
$lanMacs = array_fill_keys(array_merge(lan_fdb_macs(), observed_lan_macs($leases)), true);
if (!isset($lanMacs[$mac])) {
throw new InvalidArgumentException('not_lan_client');
}
$macBytes = hex2bin(str_replace(':', '', $mac));
if ($macBytes === false || strlen($macBytes) !== 6) {
throw new InvalidArgumentException('bad_mac');
}
$packet = str_repeat("\xFF", 6) . str_repeat($macBytes, 16);
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
if ($socket === false) {
throw new RuntimeException('socket_create_failed');
}
try {
socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);
$sent = socket_sendto($socket, $packet, strlen($packet), 0, $broadcast, $port);
if ($sent === false || $sent !== strlen($packet)) {
throw new RuntimeException('wake_packet_failed');
}
} finally {
socket_close($socket);
}
return [
'mac' => $mac,
'ip' => $leases[$mac]['ip'] ?? null,
'hostname' => $leases[$mac]['hostname'] ?? null,
'broadcast' => $broadcast,
'port' => $port,
];
}
function wifi_time_ms(string $value): int
{
if (preg_match('/(\d+)/', $value, $m)) {
@@ -3155,7 +3241,14 @@ function wifi_data(): array
parse_live_wifi_rows($iface, $band, iw_station_dump($iface), $leases, $aliases)
);
}
$clients = array_merge($clients, lan_client_rows($leases, $aliases));
$wifiMacs = array_values(array_unique(array_filter(
array_map(
static fn(array $client): string => strtolower((string)($client['mac'] ?? '')),
$clients
),
static fn(string $mac): bool => wifi_valid_mac($mac)
)));
$clients = array_merge($clients, lan_client_rows($leases, $aliases, $wifiMacs));
$clients = dedupe_wifi_clients($clients);
$clients = apply_wifi_estimates($clients);
@@ -3721,6 +3814,26 @@ function control_api_dispatch(): void
]);
}
if ($action === 'wake_lan') {
$wake = send_wake_on_lan((string)($_POST['mac'] ?? ''));
add_fan_action(
'wake_lan',
null,
null,
'WOL packet sent to ' . $wake['mac'] . ' ' . ($wake['hostname'] ?? ''),
true
);
json_out([
'ok' => true,
'data' => [
'wake' => $wake,
'status' => collect_snapshot(false),
],
]);
}
if ($action === 'collect') {
json_out([
'ok' => true,