mirror of
https://github.com/rekryt/iplist.git
synced 2025-10-13 00:49:36 +03:00
Initial commit
This commit is contained in:
69
src/Domain/Helper/DNSHelper.php
Normal file
69
src/Domain/Helper/DNSHelper.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Domain\Helper;
|
||||
|
||||
use Amp\Dns\DnsConfig;
|
||||
use Amp\Dns\DnsConfigLoader;
|
||||
use Amp\Dns\DnsException;
|
||||
use Amp\Dns\DnsRecord;
|
||||
use Amp\Dns\HostLoader;
|
||||
use Amp\Dns\Rfc1035StubDnsResolver;
|
||||
|
||||
use OpenCCK\Infrastructure\API\App;
|
||||
use function Amp\Dns\dnsResolver;
|
||||
use function Amp\Dns\resolve;
|
||||
|
||||
readonly class DNSHelper {
|
||||
public function __construct(private array $dnsServers = []) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $dnsServers
|
||||
* @return void
|
||||
*/
|
||||
private function setResolver(array $dnsServers): void {
|
||||
dnsResolver(
|
||||
new Rfc1035StubDnsResolver(
|
||||
null,
|
||||
new class ($dnsServers) implements DnsConfigLoader {
|
||||
public function __construct(private readonly array $dnsServers = []) {
|
||||
}
|
||||
|
||||
public function loadConfig(): DnsConfig {
|
||||
return new DnsConfig($this->dnsServers, (new HostLoader())->loadHosts());
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $domain
|
||||
* @return array[]
|
||||
*/
|
||||
public function resolve(string $domain): array {
|
||||
$ipv4 = [];
|
||||
$ipv6 = [];
|
||||
foreach ($this->dnsServers as $server) {
|
||||
$this->setResolver([$server]);
|
||||
try {
|
||||
$ipv4 = array_merge(
|
||||
$ipv4,
|
||||
array_map(fn(DnsRecord $record) => $record->getValue(), resolve($domain, DnsRecord::A))
|
||||
);
|
||||
} catch (DnsException $e) {
|
||||
App::getLogger()->error($e->getMessage(), [$server]);
|
||||
}
|
||||
try {
|
||||
$ipv6 = array_merge(
|
||||
$ipv6,
|
||||
array_map(fn(DnsRecord $record) => $record->getValue(), resolve($domain, DnsRecord::AAAA))
|
||||
);
|
||||
} catch (DnsException $e) {
|
||||
App::getLogger()->error($e->getMessage(), [$server]);
|
||||
}
|
||||
}
|
||||
App::getLogger()->debug('resolve: ' . $domain, [count($ipv4), count($ipv6)]);
|
||||
return [$ipv4, $ipv6];
|
||||
}
|
||||
}
|
144
src/Domain/Helper/IP4Helper.php
Normal file
144
src/Domain/Helper/IP4Helper.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Domain\Helper;
|
||||
|
||||
use OpenCCK\Infrastructure\API\App;
|
||||
use OpenCCK\Infrastructure\Storage\CIDRStorage;
|
||||
|
||||
use function Amp\async;
|
||||
use function Amp\delay;
|
||||
|
||||
class IP4Helper {
|
||||
public static function processCIDR(array $ips, $results = []): array {
|
||||
$count = count($ips);
|
||||
foreach ($ips as $i => $ip) {
|
||||
if ($ip === '127.0.0.1') {
|
||||
continue;
|
||||
}
|
||||
async(function () use ($ip, $i, $count, &$results) {
|
||||
if (CIDRStorage::getInstance()->has($ip)) {
|
||||
$searchArray = CIDRStorage::getInstance()->get($ip);
|
||||
$results = array_merge($results, self::trimCIDRs($searchArray));
|
||||
|
||||
App::getLogger()->debug($ip . ' -> ' . json_encode($searchArray), [$i + 1 . '/' . $count]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::isInRange($ip, $results)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$search = null;
|
||||
$result = shell_exec('whois ' . $ip . ' | grep CIDR');
|
||||
if ($result) {
|
||||
preg_match('/^CIDR:\s*(.*)$/m', $result, $matches);
|
||||
$search = $matches[1] ?? null;
|
||||
}
|
||||
|
||||
if (!$search) {
|
||||
$search = shell_exec(
|
||||
implode(' | ', [
|
||||
'whois -a ' . $ip,
|
||||
'grep inetnum',
|
||||
'head -n 1',
|
||||
"awk '{print $2\"-\"$4}'",
|
||||
'sed "s/-$//"',
|
||||
'xargs ipcalc',
|
||||
"grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$'",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (!$search) {
|
||||
$search = shell_exec(
|
||||
implode(' | ', [
|
||||
'whois -a ' . $ip,
|
||||
'grep IPv4',
|
||||
'grep " - "',
|
||||
'head -n 1',
|
||||
"awk '{print $3\"-\"$5}'",
|
||||
'xargs ipcalc',
|
||||
"grep -oE '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$'",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$search = strtr($search, ["\n" => ' ', ', ' => ' ']);
|
||||
$searchArray = array_filter(
|
||||
explode(' ', strtr($search, ' ', '')),
|
||||
fn(string $cidr) => strlen($cidr) > 0
|
||||
);
|
||||
CIDRStorage::getInstance()->set($ip, $searchArray);
|
||||
$results = array_merge($results, self::trimCIDRs($searchArray));
|
||||
|
||||
App::getLogger()->debug($ip . ' -> ' . json_encode($searchArray), [$i + 1 . '/' . $count]);
|
||||
} else {
|
||||
App::getLogger()->error($ip . ' -> CIDR not found', [$i + 1 . '/' . $count]);
|
||||
}
|
||||
delay(0.001);
|
||||
})->await();
|
||||
}
|
||||
|
||||
return self::minimizeSubnets($results);
|
||||
}
|
||||
|
||||
public static function trimCIDRs(array $searchArray): array {
|
||||
$subnets = [];
|
||||
foreach ($searchArray as $search) {
|
||||
foreach (explode(' ', $search) as $cidr) {
|
||||
if (str_contains($cidr, '/')) {
|
||||
$subnets[] = trim($cidr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $subnets;
|
||||
}
|
||||
|
||||
public static function sortSubnets(array $subnets): array {
|
||||
usort($subnets, function ($a, $b) {
|
||||
return (int) explode('/', $a)[1] - (int) explode('/', $b)[1];
|
||||
});
|
||||
usort($subnets, function ($a, $b) {
|
||||
return ip2long(explode('/', $a)[0]) - ip2long(explode('/', $b)[0]);
|
||||
});
|
||||
return $subnets;
|
||||
}
|
||||
|
||||
public static function minimizeSubnets(array $subnets): array {
|
||||
$result = [];
|
||||
foreach (self::sortSubnets(array_filter($subnets, fn(string $subnet) => !!$subnet)) as $subnet) {
|
||||
$include = true;
|
||||
[$ip /*, $mask*/] = explode('/', $subnet);
|
||||
$ipLong = ip2long($ip);
|
||||
// $maskLong = ~((1 << 32 - (int) $mask) - 1);
|
||||
|
||||
foreach ($result as $resSubnet) {
|
||||
[$resIp, $resMask] = explode('/', $resSubnet);
|
||||
$resIpLong = ip2long($resIp);
|
||||
$resMaskLong = ~((1 << 32 - (int) $resMask) - 1);
|
||||
|
||||
if (($ipLong & $resMaskLong) === ($resIpLong & $resMaskLong)) {
|
||||
$include = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($include) {
|
||||
$result[] = $subnet;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function isInRange(string $ip, array $cidrs): bool {
|
||||
foreach ($cidrs as $cidr) {
|
||||
[$subnet, $mask] = explode('/', $cidr);
|
||||
if ((ip2long($ip) & ~((1 << 32 - $mask) - 1)) === ip2long($subnet)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
176
src/Domain/Helper/IP6Helper.php
Normal file
176
src/Domain/Helper/IP6Helper.php
Normal file
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
namespace OpenCCK\Domain\Helper;
|
||||
|
||||
use OpenCCK\Infrastructure\API\App;
|
||||
use OpenCCK\Infrastructure\Storage\CIDRStorage;
|
||||
|
||||
use function Amp\async;
|
||||
use function Amp\delay;
|
||||
|
||||
class IP6Helper {
|
||||
public static function processCIDR(array $ips, $results = []): array {
|
||||
$count = count($ips);
|
||||
foreach ($ips as $i => $ip) {
|
||||
if ($ip === '::1') {
|
||||
continue;
|
||||
}
|
||||
async(function () use ($ip, $i, $count, &$results) {
|
||||
if (CIDRStorage::getInstance()->has($ip)) {
|
||||
$searchArray = CIDRStorage::getInstance()->get($ip);
|
||||
$results = array_merge($results, self::trimCIDRs($searchArray));
|
||||
|
||||
App::getLogger()->debug($ip . ' -> ' . json_encode($searchArray), [$i + 1 . '/' . $count]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::isInRange($ip, $results)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$search = shell_exec(
|
||||
implode(' | ', [
|
||||
'whois -a ' . $ip,
|
||||
'grep inet6num',
|
||||
'grep -v "/0"',
|
||||
'head -n 1',
|
||||
"awk '{print $2}'",
|
||||
])
|
||||
);
|
||||
|
||||
if (!$search) {
|
||||
$search = shell_exec(
|
||||
implode(' | ', [
|
||||
'whois -a ' . $ip,
|
||||
'grep route6',
|
||||
'grep -v "/0"',
|
||||
'head -n 1',
|
||||
"awk '{print $2}'",
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if ($search) {
|
||||
$search = strtr($search, ["\n" => ' ', ', ' => ' ']);
|
||||
$searchArray = array_filter(
|
||||
explode(' ', strtr($search, ' ', '')),
|
||||
fn(string $cidr) => strlen($cidr) > 0
|
||||
);
|
||||
CIDRStorage::getInstance()->set($ip, $searchArray);
|
||||
$results = array_merge($results, self::trimCIDRs($searchArray));
|
||||
|
||||
App::getLogger()->debug($ip . ' -> ' . json_encode($searchArray), [$i + 1 . '/' . $count]);
|
||||
} else {
|
||||
App::getLogger()->error($ip . ' -> CIDR not found', [$i + 1 . '/' . $count]);
|
||||
}
|
||||
delay(0.001);
|
||||
})->await();
|
||||
}
|
||||
|
||||
//return self::minimizeSubnets($results);
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $searchArray
|
||||
* @return array
|
||||
*/
|
||||
public static function trimCIDRs(array $searchArray): array {
|
||||
$subnets = [];
|
||||
foreach ($searchArray as $search) {
|
||||
foreach (explode(' ', $search) as $cidr) {
|
||||
if (str_contains($cidr, '/')) {
|
||||
[$address, $prefix] = explode('/', trim($cidr));
|
||||
$subnets[] = $address . '/' . max($prefix, 64);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $subnets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $subnets
|
||||
* @return array
|
||||
*/
|
||||
public static function sortSubnets(array $subnets): array {
|
||||
usort($subnets, function ($a, $b) {
|
||||
return (int) explode('/', $a)[1] - (int) explode('/', $b)[1];
|
||||
});
|
||||
|
||||
usort($subnets, function ($a, $b) {
|
||||
$ipA = inet_pton(explode('/', $a)[0]);
|
||||
$ipB = inet_pton(explode('/', $b)[0]);
|
||||
|
||||
return strcmp($ipA, $ipB);
|
||||
});
|
||||
|
||||
return $subnets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $subnets
|
||||
* @return array
|
||||
*/
|
||||
public static function minimizeSubnets(array $subnets): array {
|
||||
$result = [];
|
||||
foreach (self::sortSubnets(array_filter($subnets, fn(string $subnet) => !!$subnet)) as $subnet) {
|
||||
if (!$subnet) {
|
||||
continue;
|
||||
}
|
||||
[$address, $prefix] = explode('/', $subnet);
|
||||
|
||||
$addressNum = inet_pton($address);
|
||||
$addressNum = unpack('J', $addressNum)[1];
|
||||
|
||||
$isUnique = true;
|
||||
foreach ($result as $existingCidr) {
|
||||
[$existingAddress, $existingPrefix] = explode('/', $existingCidr);
|
||||
$existingAddressNum = inet_pton($existingAddress);
|
||||
$existingAddressNum = unpack('J', $existingAddressNum)[1];
|
||||
|
||||
$mask = (1 << 128) - (1 << 128 - $prefix);
|
||||
$existingMask = (1 << 128) - (1 << 128 - $existingPrefix);
|
||||
if (($addressNum & $mask) === ($existingAddressNum & $existingMask)) {
|
||||
$isUnique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isUnique) {
|
||||
$result[] = $subnet;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function isInRange(string $ip, array $cidrs): bool {
|
||||
$ip = inet_pton($ip);
|
||||
|
||||
foreach ($cidrs as $cidr) {
|
||||
[$subnet, $mask] = explode('/', $cidr);
|
||||
$subnet = inet_pton($subnet);
|
||||
|
||||
$mask = intval($mask);
|
||||
$binaryMask = str_repeat('f', $mask >> 2);
|
||||
switch ($mask % 4) {
|
||||
case 1:
|
||||
$binaryMask .= '8';
|
||||
break;
|
||||
case 2:
|
||||
$binaryMask .= 'c';
|
||||
break;
|
||||
case 3:
|
||||
$binaryMask .= 'e';
|
||||
break;
|
||||
}
|
||||
$binaryMask = str_pad($binaryMask, 32, '0');
|
||||
$mask = pack('H*', $binaryMask);
|
||||
|
||||
if (($ip & $mask) === ($subnet & $mask)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user