74 lines
2.5 KiB
PHP
74 lines
2.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Domovoy\Services\Discovery;
|
|
|
|
class TcpPortScanner
|
|
{
|
|
/**
|
|
* Scan a list of TCP ports on a host.
|
|
*
|
|
* @param int[] $ports
|
|
* @param int $timeoutMs connection timeout per port
|
|
* @param int $maxConcurrency max parallel connections
|
|
* @return int[] list of open ports
|
|
*/
|
|
public function scan(string $ip, array $ports, int $timeoutMs = 200, int $maxConcurrency = 50): array
|
|
{
|
|
$openPorts = [];
|
|
$timeoutSec = $timeoutMs / 1000;
|
|
|
|
if (function_exists('curl_multi_init')) {
|
|
// Parallel scan with curl_multi
|
|
$chunks = array_chunk($ports, $maxConcurrency);
|
|
foreach ($chunks as $chunk) {
|
|
$multiHandle = curl_multi_init();
|
|
$handles = [];
|
|
foreach ($chunk as $port) {
|
|
$ch = curl_init();
|
|
curl_setopt($ch, CURLOPT_URL, "http://{$ip}:{$port}");
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS, $timeoutMs);
|
|
curl_setopt($ch, CURLOPT_TIMEOUT_MS, $timeoutMs);
|
|
curl_setopt($ch, CURLOPT_NOBODY, true);
|
|
curl_multi_add_handle($multiHandle, $ch);
|
|
$handles[$port] = $ch;
|
|
}
|
|
|
|
// Execute
|
|
$running = null;
|
|
do {
|
|
curl_multi_exec($multiHandle, $running);
|
|
curl_multi_select($multiHandle, 0.1);
|
|
} while ($running > 0);
|
|
|
|
// Check results
|
|
foreach ($handles as $port => $ch) {
|
|
$err = curl_errno($ch);
|
|
// CURLE_COULDNT_CONNECT (7) = port closed, others might mean open or filtered
|
|
if ($err !== CURLE_COULDNT_CONNECT) {
|
|
$openPorts[] = $port;
|
|
}
|
|
curl_multi_remove_handle($multiHandle, $ch);
|
|
curl_close($ch);
|
|
}
|
|
|
|
curl_multi_close($multiHandle);
|
|
}
|
|
} else {
|
|
// Sequential fallback
|
|
foreach ($ports as $port) {
|
|
$fp = @fsockopen($ip, $port, $errno, $errstr, $timeoutSec);
|
|
if ($fp !== false) {
|
|
$openPorts[] = $port;
|
|
fclose($fp);
|
|
}
|
|
}
|
|
}
|
|
|
|
sort($openPorts);
|
|
return $openPorts;
|
|
}
|
|
}
|