diff options
Diffstat (limited to 'modules-available/rebootcontrol/hooks')
-rw-r--r-- | modules-available/rebootcontrol/hooks/cron.inc.php | 223 |
1 files changed, 223 insertions, 0 deletions
diff --git a/modules-available/rebootcontrol/hooks/cron.inc.php b/modules-available/rebootcontrol/hooks/cron.inc.php new file mode 100644 index 00000000..79a7ec2a --- /dev/null +++ b/modules-available/rebootcontrol/hooks/cron.inc.php @@ -0,0 +1,223 @@ +<?php + +if (/* mt_rand(1, 2) !== 1 || */ Property::get(RebootControl::KEY_AUTOSCAN_DISABLED)) + return; + +class Stuff +{ + public static $subnets; +} + +function destSawPw($destTask, $destMachine, $passwd) +{ + return strpos($destTask['data']['result'][$destMachine['machineuuid']]['stdout'], "passwd=$passwd") !== false; +} + +function spawnDestinationListener($dstid, &$destMachine, &$destTask, &$destDeadline) +{ + $destMachines = Stuff::$subnets[$dstid]; + cron_log(count($destMachines) . ' potential destination machines for subnet ' . $dstid); + shuffle($destMachines); + $destMachines = array_slice($destMachines, 0, 3); + $destTask = $destMachine = false; + $destDeadline = 0; + foreach ($destMachines as $machine) { + cron_log("Trying to use {$machine['clientip']} as listener for " . long2ip($machine['bcast'])); + $destTask = RebootControl::runScript([$machine], "echo 'Running-MARK'\nbusybox timeout -t 8 jawol -v -l", 10); + Taskmanager::release($destTask); + $destDeadline = time() + 10; + if (!Taskmanager::isRunning($destTask)) + continue; + sleep(2); // Wait a bit and re-check job is running; only then proceed with this host + $destTask = Taskmanager::status($destTask); + cron_log("....is {$destTask['statusCode']} {$machine['machineuuid']}"); + if (Taskmanager::isRunning($destTask) + && strpos($destTask['data']['result'][$machine['machineuuid']]['stdout'], 'Running-MARK') !== false) { + $destMachine = $machine; + break; // GOOD TO GO + } + cron_log(print_r($destTask, true)); + cron_log("Dest isn't running or didn't have MARK in output, trying another one..."); + } +} + +function testClientToClient($srcid, $dstid) +{ + $sourceMachines = Stuff::$subnets[$srcid]; + // Start listener on destination + spawnDestinationListener($dstid, $destMachine, $destTask, $destDeadline); + if ($destMachine === false || !Taskmanager::isRunning($destTask)) + return false; // No suitable dest-host found + // Find a source host + $passwd = sprintf('%02x:%02x:%02x:%02x:%02x:%02x', mt_rand(0, 255), mt_rand(0, 255), + mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255)); + shuffle($sourceMachines); + $sourceMachines = array_slice($sourceMachines, 0, 3); + cron_log("Running sending task on " + . implode(', ', array_map(function($item) { return $item['clientip']; }, $sourceMachines))); + $sourceTask = RebootControl::wakeViaClient($sourceMachines, $destMachine['macaddr'], $destMachine['bcast'], $passwd); + Taskmanager::release($sourceTask); + if (!Taskmanager::isRunning($sourceTask)) { + cron_log('Failed to launch task for source hosts...'); + return false; + } + cron_log('Waiting for testing tasks to finish...'); + // Loop as long as destination task and source task is running and we didn't see the pw at destination yet + while (Taskmanager::isRunning($destTask) && Taskmanager::isRunning($sourceTask) + && !destSawPw($destTask, $destMachine, $passwd) && $destDeadline > time()) { + $sourceTask = Taskmanager::status($sourceTask); + usleep(250000); + $destTask = Taskmanager::status($destTask); + } + cron_log($destTask['data']['result'][$destMachine['machineuuid']]['stdout']); + // Final moment: did dest see the packets from src? Determine this by looking for the generated password + if (destSawPw($destTask, $destMachine, $passwd)) + return 1; // Found pw + return 0; // Nothing :-( +} + +function testServerToClient($dstid) +{ + spawnDestinationListener($dstid, $destMachine, $destTask, $destDeadline); + if ($destMachine === false || !Taskmanager::isRunning($destTask)) + return false; // No suitable dest-host found + $passwd = sprintf('%02x:%02x:%02x:%02x:%02x:%02x', mt_rand(0, 255), mt_rand(0, 255), + mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255), mt_rand(0, 255)); + cron_log('Sending WOL packets from Sat Server...'); + $task = RebootControl::wakeDirectly($destMachine['macaddr'], $destMachine['bcast'], $passwd); + usleep(200000); + $destTask = Taskmanager::status($destTask); + if (!destSawPw($destTask, $destMachine, $passwd) && !Taskmanager::isTask($task)) + return false; + cron_log('Waiting for receive on destination...'); + $task = Taskmanager::status($task); + if (!destSawPw($destTask, $destMachine, $passwd)) { + $task = Taskmanager::waitComplete($task, 2000); + $destTask = Taskmanager::status($destTask); + } + cron_log($destTask['data']['result'][$destMachine['machineuuid']]['stdout']); + if (destSawPw($destTask, $destMachine, $passwd)) + return 1; + return 0; +} + +/** + * Take test result, turn into "next check" timestamp + */ +function resultToTime($result) +{ + if ($result === false) { + // Temporary failure -- couldn't run at least one destination and one source task + $next = 7200; // 2 hours + } elseif ($result === 0) { + // Test finished, subnet not reachable + $next = 86400 * 7; // a week + } else { + // Test finished, reachable + $next = 86400 * 30; // a month + } + return time() + round($next * mt_rand(90, 133) / 100); +} + +/* + * + */ + +// First, cleanup: delete orphaned subnets that don't exist anymore, or don't have any clients using our server +$cutoff = strtotime('-180 days'); +Database::exec('DELETE FROM reboot_subnet WHERE fixed = 0 AND lastseen < :cutoff', ['cutoff' => $cutoff]); + +// Get machines running, group by subnet +$cutoff = time() - 301; // Really only the ones that didn't miss the most recent update +$res = Database::simpleQuery("SELECT s.subnetid, s.end AS bcast, m.machineuuid, m.clientip, m.macaddr + FROM reboot_subnet s + INNER JOIN machine m ON ( + (m.state = 'IDLE' OR m.state = 'OCCUPIED') + AND + (m.lastseen >= $cutoff) + AND + (INET_ATON(m.clientip) BETWEEN s.start AND s.end) + )"); + +//cron_log('Machine: ' . $res->rowCount()); + +if ($res->rowCount() === 0) + return; + +Stuff::$subnets = []; +while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + if (!isset(Stuff::$subnets[$row['subnetid']])) { + Stuff::$subnets[$row['subnetid']] = []; + } + Stuff::$subnets[$row['subnetid']][] = $row; +} + +$task = Taskmanager::submit('DummyTask', []); +$task = Taskmanager::waitComplete($task, 4000); +if (!Taskmanager::isFinished($task)) { + cron_log('Task manager down. Doing nothing.'); + return; // No :-( +} +unset($task); + +/* + * Try server to client + */ + +$res = Database::simpleQuery("SELECT subnetid FROM reboot_subnet + WHERE subnetid IN (:active) AND nextdirectcheck < UNIX_TIMESTAMP() AND fixed = 0 + ORDER BY nextdirectcheck ASC LIMIT 10", ['active' => array_keys(Stuff::$subnets)]); +cron_log('Direct checks: ' . $res->rowCount() . ' (' . implode(', ', array_keys(Stuff::$subnets)) . ')'); +while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $dst = (int)$row['subnetid']; + cron_log('Direct check for subnetid ' . $dst); + $result = testServerToClient($dst); + $next = resultToTime($result); + if ($result === false) { + Database::exec('UPDATE reboot_subnet + SET nextdirectcheck = :nextcheck + WHERE subnetid = :dst', ['nextcheck' => $next, 'dst' => $dst]); + } else { + Database::exec('UPDATE reboot_subnet + SET nextdirectcheck = :nextcheck, isdirect = :isdirect + WHERE subnetid = :dst', ['nextcheck' => $next, 'isdirect' => $result, 'dst' => $dst]); + } +} + +/* + * Try client to client + */ + +// Query all possible combos +$combos = []; +foreach (Stuff::$subnets as $src => $_) { + $src = (int)$src; + foreach (Stuff::$subnets as $dst => $_) { + $dst = (int)$dst; + if ($src !== $dst) { + $combos[] = [$src, $dst]; + } + } +} + +// Check subnet to subnet +if (count($combos) > 0) { + $res = Database::simpleQuery("SELECT ss.subnetid AS srcid, sd.subnetid AS dstid + FROM reboot_subnet ss + INNER JOIN reboot_subnet sd ON ((ss.subnetid, sd.subnetid) IN (:combos) AND sd.fixed = 0) + LEFT JOIN reboot_subnet_x_subnet sxs ON (ss.subnetid = sxs.srcid AND sd.subnetid = sxs.dstid) + WHERE sxs.nextcheck < UNIX_TIMESTAMP() OR sxs.nextcheck IS NULL + ORDER BY sxs.nextcheck ASC + LIMIT 10", ['combos' => $combos]); + cron_log('C2C checks: ' . $res->rowCount()); + while ($row = $res->fetch(PDO::FETCH_ASSOC)) { + $src = (int)$row['srcid']; + $dst = (int)$row['dstid']; + $result = testClientToClient($src, $dst); + $next = resultToTime($result); + Database::exec('INSERT INTO reboot_subnet_x_subnet (srcid, dstid, reachable, nextcheck) + VALUES (:srcid, :dstid, :reachable, :nextcheck) + ON DUPLICATE KEY UPDATE ' . ($result === false ? '' : 'reachable = VALUES(reachable),') . ' nextcheck = VALUES(nextcheck)', + ['srcid' => $src, 'dstid' => $dst, 'reachable' => (int)$result, 'nextcheck' => $next]); + } +} |