diff --git a/src/inc/api/APISendProgress.php b/src/inc/api/APISendProgress.php index 0af0c5a15..34eac55ca 100644 --- a/src/inc/api/APISendProgress.php +++ b/src/inc/api/APISendProgress.php @@ -495,6 +495,11 @@ public function execute(array $QUERY = array()): void { // the chunk has finished (exhausted) $chunk = Factory::getChunkFactory()->mset($chunk, [Chunk::SPEED => 0, Chunk::PROGRESS => 10000, Chunk::CHECKPOINT => $chunk->getSkip() + $chunk->getLength()]); DServerLog::log(DServerLog::TRACE, "Chunk is exhausted (cracker status)", [$this->agent, $chunk]); + + // If we don't make use of static chunks we attempt to tune the duration a chunk takes to calculate + if($task->getStaticChunks() === 0 && SConfig::getInstance()->getVal(DConfig::CHUNK_DURATION_AUTO_TUNE) === 1) { + TaskUtils::tuneChunkDuration($chunk, $task); + } break; case DHashcatStatus::CRACKED: // the chunk has finished (cracked whole hashList) diff --git a/src/inc/defines/DConfig.php b/src/inc/defines/DConfig.php index 51c7b73df..669663357 100644 --- a/src/inc/defines/DConfig.php +++ b/src/inc/defines/DConfig.php @@ -7,25 +7,26 @@ class DConfig { // Section: Cracking/Tasks - const BENCHMARK_TIME = "benchtime"; - const CHUNK_DURATION = "chunktime"; - const CHUNK_TIMEOUT = "chunktimeout"; - const AGENT_TIMEOUT = "agenttimeout"; - const FIELD_SEPARATOR = "fieldseparator"; - const HASHLIST_ALIAS = "hashlistAlias"; - const STATUS_TIMER = "statustimer"; - const BLACKLIST_CHARS = "blacklistChars"; - const DISP_TOLERANCE = "disptolerance"; - const DEFAULT_BENCH = "defaultBenchmark"; - const AGENT_DATA_LIFETIME = "agentDataLifetime"; - const DISABLE_TRIMMING = "disableTrimming"; - const PRIORITY_0_START = "priority0Start"; - const HASHCAT_BRAIN_ENABLE = "hashcatBrainEnable"; - const HASHCAT_BRAIN_HOST = "hashcatBrainHost"; - const HASHCAT_BRAIN_PORT = "hashcatBrainPort"; - const HASHCAT_BRAIN_PASS = "hashcatBrainPass"; - const HASHLIST_IMPORT_CHECK = "hashlistImportCheck"; - const HC_ERROR_IGNORE = "hcErrorIgnore"; + const BENCHMARK_TIME = "benchtime"; + const CHUNK_DURATION = "chunktime"; + const CHUNK_DURATION_AUTO_TUNE = "chunktimeAutoTune"; + const CHUNK_TIMEOUT = "chunktimeout"; + const AGENT_TIMEOUT = "agenttimeout"; + const FIELD_SEPARATOR = "fieldseparator"; + const HASHLIST_ALIAS = "hashlistAlias"; + const STATUS_TIMER = "statustimer"; + const BLACKLIST_CHARS = "blacklistChars"; + const DISP_TOLERANCE = "disptolerance"; + const DEFAULT_BENCH = "defaultBenchmark"; + const AGENT_DATA_LIFETIME = "agentDataLifetime"; + const DISABLE_TRIMMING = "disableTrimming"; + const PRIORITY_0_START = "priority0Start"; + const HASHCAT_BRAIN_ENABLE = "hashcatBrainEnable"; + const HASHCAT_BRAIN_HOST = "hashcatBrainHost"; + const HASHCAT_BRAIN_PORT = "hashcatBrainPort"; + const HASHCAT_BRAIN_PASS = "hashcatBrainPass"; + const HASHLIST_IMPORT_CHECK = "hashlistImportCheck"; + const HC_ERROR_IGNORE = "hcErrorIgnore"; // Section: Yubikey const YUBIKEY_ID = "yubikey_id"; @@ -122,6 +123,7 @@ public static function getConfigType(string $config): string { return match ($config) { DConfig::BENCHMARK_TIME => DConfigType::NUMBER_INPUT, DConfig::CHUNK_DURATION => DConfigType::NUMBER_INPUT, + DConfig::CHUNK_DURATION_AUTO_TUNE => DConfigType::TICKBOX, DConfig::CHUNK_TIMEOUT => DConfigType::NUMBER_INPUT, DConfig::AGENT_TIMEOUT => DConfigType::NUMBER_INPUT, DConfig::HASHES_PAGE_SIZE => DConfigType::NUMBER_INPUT, @@ -190,6 +192,7 @@ public static function getConfigDescription(string $config): string { return match ($config) { DConfig::BENCHMARK_TIME => "Time in seconds an agent should benchmark a task.", DConfig::CHUNK_DURATION => "Time in seconds a client should be working on a single chunk.", + DConfig::CHUNK_DURATION_AUTO_TUNE => "Automatically adjust chunk sizes to get the actual chunk working time for a client as close as possible to the configured chunk duration.", DConfig::CHUNK_TIMEOUT => "Time in seconds the server will consider an issued chunk as inactive or timed out and will reallocate to another client.", DConfig::AGENT_TIMEOUT => "Time in seconds the server will consider a client inactive or timed out.", DConfig::HASHES_PAGE_SIZE => "Number of hashes shown on each page of the hashes view.", diff --git a/src/inc/utils/AgentUtils.php b/src/inc/utils/AgentUtils.php index 8fb9b7d0d..01de85ee8 100644 --- a/src/inc/utils/AgentUtils.php +++ b/src/inc/utils/AgentUtils.php @@ -71,7 +71,7 @@ public static function getDeviceUtilStatusColor(?AgentStat $deviceUtil, Agent $a return "#800000"; } } - + /** * @param ?AgentStat $deviceTemp * @param Agent $agent @@ -104,7 +104,7 @@ public static function getDeviceTempStatusColor(?AgentStat $deviceTemp, Agent $a return "#800000"; } } - + /** * @param ?AgentStat $cpuUtil * @param Agent $agent @@ -138,7 +138,7 @@ public static function getCpuUtilStatusColor(?AgentStat $cpuUtil, Agent $agent): return "#800000"; } } - + /** * @param ?AgentStat $deviceUtil * @return string @@ -159,7 +159,7 @@ public static function getDeviceUtilStatusValue(?AgentStat $deviceUtil): string $avg = $sum / sizeof($deviceUtil); return round($avg, 1) . "%"; } - + /** * @param ?AgentStat $deviceTemp * @return string @@ -179,7 +179,7 @@ public static function getDeviceTempStatusValue(?AgentStat $deviceTemp): string } return $max . "°"; } - + /** * @param ?AgentStat $cpuUtil * @return string @@ -200,7 +200,7 @@ public static function getCpuUtilStatusValue(?AgentStat $cpuUtil): string { $avg = $sum / sizeof($cpuUtil); return round($avg, 1) . "%"; } - + /** * @param Agent $agent * @param mixed $types @@ -212,7 +212,7 @@ public static function getGraphData(Agent $agent, mixed $types): array { if ($limit <= 0) { $limit = 100; } - + $qF1 = new ContainFilter(AgentStat::STAT_TYPE, $types); $qF2 = new QueryFilter(AgentStat::AGENT_ID, $agent->getId(), "="); $oF1 = new OrderFilter(AgentStat::TIME, "DESC"); @@ -274,7 +274,7 @@ public static function getGraphData(Agent $agent, mixed $types): array { } return ["xlabels" => $xlabels, "sets" => $datasets, "axes" => $axes]; } - + /** * @param int $agentId * @param boolean $isCpuOnly @@ -286,7 +286,7 @@ public static function setAgentCpu(int $agentId, bool $isCpuOnly, User $user): v $agent = AgentUtils::getAgent($agentId, $user); Factory::getAgentFactory()->set($agent, Agent::CPU_ONLY, ($isCpuOnly) ? 1 : 0); } - + /** * @param int $agentId * @param User $user @@ -295,11 +295,11 @@ public static function setAgentCpu(int $agentId, bool $isCpuOnly, User $user): v */ public static function clearErrors(int $agentId, User $user): void { $agent = AgentUtils::getAgent($agentId, $user); - + $qF = new QueryFilter(AgentError::AGENT_ID, $agent->getId(), "="); Factory::getAgentErrorFactory()->massDeletion([Factory::FILTER => $qF]); } - + /** * @param int $agentId * @param string $newname @@ -315,7 +315,7 @@ public static function rename(int $agentId, string $newname, User $user): void { } Factory::getAgentFactory()->set($agent, Agent::AGENT_NAME, $name); } - + /** * @param int $agentId * @param ?User $user @@ -324,13 +324,13 @@ public static function rename(int $agentId, string $newname, User $user): void { */ public static function delete(int $agentId, ?User $user): void { $agent = AgentUtils::getAgent($agentId, $user); - + Factory::getAgentFactory()->getDB()->beginTransaction(); $name = $agent->getAgentName(); - + $payload = new DataSet(array(DPayloadKeys::AGENT => $agent)); NotificationHandler::checkNotifications(DNotificationType::DELETE_AGENT, $payload); - + if (AgentUtils::deleteDependencies($agent)) { Factory::getAgentFactory()->getDB()->commit(); Util::createLogEntry("User", (($user == null) ? 0 : ($user->getId())), DLogEntry::INFO, "Agent " . $name . " got deleted."); @@ -340,7 +340,7 @@ public static function delete(int $agentId, ?User $user): void { throw new HTException("Error occured on deletion of agent!"); } } - + /** * @param Agent $agent * @return boolean @@ -358,34 +358,34 @@ public static function deleteDependencies(Agent $agent): bool { } $qF = new QueryFilter(AgentError::AGENT_ID, $agent->getId(), "="); Factory::getAgentErrorFactory()->massDeletion([Factory::FILTER => $qF]); - + $qF = new QueryFilter(AgentStat::AGENT_ID, $agent->getId(), "="); Factory::getAgentStatFactory()->massDeletion([Factory::FILTER => $qF]); - + $qF = new QueryFilter(AgentZap::AGENT_ID, $agent->getId(), "="); Factory::getAgentZapFactory()->massDeletion([Factory::FILTER => $qF]); - + $qF = new QueryFilter(HealthCheckAgent::AGENT_ID, $agent->getId(), "="); Factory::getHealthCheckAgentFactory()->massDeletion([Factory::FILTER => $qF]); - + $qF = new QueryFilter(Speed::AGENT_ID, $agent->getId(), "="); Factory::getSpeedFactory()->massDeletion([Factory::FILTER => $qF]); - + $qF = new QueryFilter(Zap::AGENT_ID, $agent->getId(), "="); $uS = new UpdateSet(Zap::AGENT_ID, null); Factory::getZapFactory()->massUpdate([Factory::FILTER => $qF, Factory::UPDATE => $uS]); - + $qF = new QueryFilter(AccessGroupAgent::AGENT_ID, $agent->getId(), "="); Factory::getAccessGroupAgentFactory()->massDeletion([Factory::FILTER => $qF]); - + $qF = new QueryFilter(Chunk::AGENT_ID, $agent->getId(), "="); $uS = new UpdateSet(Chunk::AGENT_ID, null); Factory::getChunkFactory()->massUpdate([Factory::FILTER => $qF, Factory::UPDATE => $uS]); - + Factory::getAgentFactory()->delete($agent); return true; } - + /** * @param int $agentId * @param int $taskId @@ -397,7 +397,7 @@ public static function deleteDependencies(Agent $agent): bool { */ public static function assign(int $agentId, int $taskId, User $user): ?Assignment { $agent = AgentUtils::getAgent($agentId, $user); - + if ($taskId == 0) { // unassign $qF = new QueryFilter(Agent::AGENT_ID, $agent->getId(), "="); Factory::getAssignmentFactory()->massDeletion([Factory::FILTER => $qF]); @@ -407,7 +407,7 @@ public static function assign(int $agentId, int $taskId, User $user): ?Assignmen } return null; } - + $task = Factory::getTaskFactory()->get($taskId); if ($task == null) { throw new HttpError("Invalid task!"); @@ -415,21 +415,21 @@ public static function assign(int $agentId, int $taskId, User $user): ?Assignmen else if (!AccessUtils::agentCanAccessTask($agent, $task)) { throw new HttpError("This agent cannot access this task - either group mismatch, or agent is not configured as Trusted to access secret tasks"); } - + $taskWrapper = Factory::getTaskWrapperFactory()->get($task->getTaskWrapperId()); if (!AccessUtils::userCanAccessTask($taskWrapper, $user)) { throw new HttpError("No access to this task!"); } - + $qF = new QueryFilter(Assignment::TASK_ID, $task->getId(), "="); $assignments = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF]); if ($task->getIsSmall() && sizeof($assignments) > 0) { throw new HttpError("You cannot assign agent to this task as the limit of assignments is reached!"); } - + $qF = new QueryFilter(Agent::AGENT_ID, $agent->getId(), "="); $assignments = Factory::getAssignmentFactory()->filter([Factory::FILTER => $qF]); - + $benchmark = 0; if (sizeof($assignments) > 0) { if ($assignments[0]->getTaskId() === $taskId) { @@ -451,7 +451,7 @@ public static function assign(int $agentId, int $taskId, User $user): ?Assignmen } return $assignment; } - + /** * @param int $agentId * @param int $ignoreErrors @@ -466,7 +466,7 @@ public static function changeIgnoreErrors(int $agentId, int $ignoreErrors, User } Factory::getAgentFactory()->set($agent, Agent::IGNORE_ERRORS, $ignoreErrors); } - + /** * @param int $agentId * @param ?User $user @@ -484,7 +484,7 @@ public static function getAgent(int $agentId, ?User $user = null): Agent { } return $agent; } - + /** * @param int $agentId * @param boolean $trusted @@ -496,7 +496,7 @@ public static function setTrusted(int $agentId, bool $trusted, User $user): void $agent = AgentUtils::getAgent($agentId, $user); Factory::getAgentFactory()->set($agent, Agent::IS_TRUSTED, ($trusted) ? 1 : 0); } - + /** * @param int $agentId * @param int|string $ownerId @@ -526,7 +526,7 @@ public static function changeOwner(int $agentId, int|string $ownerId, User $user } Util::createLogEntry(DLogEntryIssuer::USER, $user->getId(), DLogEntry::INFO, "Owner for agent " . $agent->getAgentName() . " was changed to " . $username); } - + /** * @param int $agentId * @param string $cmdParameters @@ -541,7 +541,7 @@ public static function changeCmdParameters(int $agentId, string $cmdParameters, } Factory::getAgentFactory()->set($agent, Agent::CMD_PARS, $cmdParameters); } - + /** * @param int $agentId * @param boolean $active @@ -558,7 +558,7 @@ public static function setActive(int $agentId, bool $active, User $user, bool $t else if (!AccessUtils::userCanAccessAgent($agent, $user)) { throw new HTException("No access to this agent!"); } - + if ($toggle) { $set = ($agent->getIsActive() == 1) ? 0 : 1; } @@ -567,7 +567,7 @@ public static function setActive(int $agentId, bool $active, User $user, bool $t } Factory::getAgentFactory()->set($agent, Agent::IS_ACTIVE, $set); } - + /** * @param string $newVoucher * @return RegVoucher @@ -580,12 +580,12 @@ public static function createVoucher(string $newVoucher): RegVoucher { if ($check != null) { throw new HttpConflict("Same voucher already exists!"); } - + $key = htmlentities($newVoucher, ENT_QUOTES, "UTF-8"); $voucher = new RegVoucher(null, $key, time()); return Factory::getRegVoucherFactory()->save($voucher); } - + /** * @param int|string $voucher * @throws HTException @@ -604,7 +604,7 @@ public static function deleteVoucher(int|string $voucher): void { } Factory::getRegVoucherFactory()->delete($voucher); } - + /** * Get the aggregate cracking time of an agent or * (if task ID is specified) of an agent on a specific task. @@ -627,7 +627,7 @@ public static function getAggregateCrackingTime(int $agentId, ?int $taskId = nul $results = Factory::getChunkFactory()->multicolAggregationFilter([Factory::FILTER => array_filter([$qF1, $qF2, $qF3, $qF4])], [$agg1, $agg2]); return $results[$agg1->getName()] - $results[$agg2->getName()]; } - + /** * Get the aggregate number of hashes cracked by an agent or * (if task ID is specified) by an agent on a specific task. diff --git a/src/inc/utils/TaskUtils.php b/src/inc/utils/TaskUtils.php index d2e59d93e..b60258637 100644 --- a/src/inc/utils/TaskUtils.php +++ b/src/inc/utils/TaskUtils.php @@ -44,6 +44,7 @@ use Hashtopolis\inc\defines\DPrince; use Hashtopolis\inc\defines\DTaskStaticChunking; use Hashtopolis\inc\defines\DTaskTypes; +use Hashtopolis\inc\defines\DServerLog; use Hashtopolis\inc\handlers\NotificationHandler; use Hashtopolis\inc\HTException; use Hashtopolis\inc\SConfig; @@ -1519,4 +1520,38 @@ public static function getCurrentSpeedOfTask(int $taskId, ?int $agentId = null): $agg = new Aggregation(Chunk::SPEED, Aggregation::SUM); return (int)(Factory::getChunkFactory()->multicolAggregationFilter([Factory::FILTER => array_filter([$qF1, $qF2, $qF3, $qF4])], [$agg])[$agg->getName()] ?? 0); } + + /** + * Adjust the benchmark of an agent for a task to ensure the actual time it takes to calculate a chunk + * is within 20% of the configured chunk duration time. + * + * @param Chunk $chunk + * @param Task $task + * @throws Exception + */ + public static function tuneChunkDuration(Chunk $chunk, Task $task): void { + $timeTaken = $chunk->getSolveTime() - $chunk->getDispatchTime(); + if($timeTaken < 0) return; // prevent math & logic errors + + $differenceToChunk = $task->getChunkTime() / $timeTaken; + // Limit how much difference a chunk can have to the previous. + $differenceToChunk = ($differenceToChunk > 1.5) ? 1.5 : $differenceToChunk; + $differenceToChunk = ($differenceToChunk < (2/3)) ? (2/3) : $differenceToChunk; + if ($differenceToChunk < 1.2) return; // if the difference is less than 20% don't make any adjustments, margin of error. We also disregard any + // adjustments that would result in a smaller chunk size (anything < 1.0), since we do not want to perform + // automatic reductions in chunk size. + + $qF1 = new QueryFilter(Assignment::AGENT_ID, $chunk->getAgentId(), "="); + $qF2 = new QueryFilter(Assignment::TASK_ID, $chunk->getTaskId(), "="); + $assignment = Factory::getAssignmentFactory()->filter([Factory::FILTER => [$qF1, $qF2]])[0]; + + $benchmark = $assignment->getBenchmark(); + $benchmarkParts = explode(":", $benchmark); + if($benchmarkParts[0] == 0 || count($benchmarkParts) != 2) return; + $newBenchmark = $differenceToChunk * $benchmarkParts[0]; + $assignment->setBenchmark(round($newBenchmark).":".round($benchmarkParts[1])); + DServerLog::log(DServerLog::INFO, "{$timeTaken}---{$task->getChunkTime}", [$this->agent, $assignment]); + DServerLog::log(DServerLog::INFO, "Multiplied the benchmark of agent by ".round($differenceToChunk,2), [$this->agent, $assignment]); + Factory::getAssignmentFactory()->update($assignment); + } }