added DNS resolver
This commit is contained in:
25
src/Classes/GeoIp.php
Normal file
25
src/Classes/GeoIp.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
class GeoIp
|
||||
{
|
||||
|
||||
|
||||
public static function get(string $ipAddress): PromiseInterface
|
||||
{
|
||||
return new Promise(function ($resolve) use ($ipAddress) {
|
||||
$reader = \XBotControl\Config::getinstance()->geoipreader;
|
||||
if ($reader) {
|
||||
$resolve($reader->get($ipAddress));
|
||||
} else {
|
||||
$resolve([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
namespace XBotControl\Classes;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Clue\React\SQLite\DatabaseInterface;
|
||||
@@ -10,7 +10,7 @@ use Clue\React\SQLite\Result;
|
||||
use React\Promise\PromiseInterface;
|
||||
use React\Promise\Promise;
|
||||
|
||||
class IPMatch
|
||||
class IPTools
|
||||
|
||||
{
|
||||
/**
|
||||
@@ -184,4 +184,90 @@ class IPMatch
|
||||
$resolve(false); // Resolve with false if no matches are found
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public static function checkIfIpBelongsToNetwork(string $ip): PromiseInterface
|
||||
{
|
||||
return self::detectIpVersion($ip)->then(function ($ipVersion) use ($ip) {
|
||||
return self::whitelistRetrieve($ip, $ipVersion)->then(function ($networks) use ($ip, $ipVersion) {
|
||||
if ($ipVersion === 4) {
|
||||
return self::compareIpv4($ip, $networks);
|
||||
} else {
|
||||
return self::compareIpv6($ip, $networks);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private static function detectIpVersion(string $ip): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$deferred->resolve(4);
|
||||
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$deferred->resolve(6);
|
||||
} else {
|
||||
$deferred->reject("Invalid IP address");
|
||||
}
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
private static function whitelistRetrieve(string $ip, int $ipVersion, string $source = '%'): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
$pdo = new \PDO("mysql:host=localhost;dbname=your_database_name", "username", "password");
|
||||
$sql = "SELECT data FROM networkwhitelist WHERE v = :version";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->bindParam(':version', $ipVersion, \PDO::PARAM_INT);
|
||||
$stmt->execute();
|
||||
|
||||
$networks = $stmt->fetchAll(\PDO::FETCH_COLUMN);
|
||||
$deferred->resolve($networks);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
private static function compareIpv4(string $ip, array $networks): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
foreach ($networks as $network) {
|
||||
[$networkIp, $mask] = explode('/', $network);
|
||||
$networkLong = ip2long($networkIp);
|
||||
$maskLong = ~((1 << (32 - $mask)) - 1);
|
||||
$ipLong = ip2long($ip);
|
||||
|
||||
if (($ipLong & $maskLong) === ($networkLong & $maskLong)) {
|
||||
$deferred->resolve(true);
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
$deferred->resolve(false);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
|
||||
private static function compareIpv6(string $ip, array $networks): PromiseInterface
|
||||
{
|
||||
$deferred = new Deferred();
|
||||
|
||||
foreach ($networks as $network) {
|
||||
[$networkIp, $prefixLength] = explode('/', $network);
|
||||
$networkBin = inet_pton($networkIp);
|
||||
$ipBin = inet_pton($ip);
|
||||
$prefixBytes = intval($prefixLength / 8);
|
||||
$remainingBits = $prefixLength % 8;
|
||||
|
||||
if (strncmp($networkBin, $ipBin, $prefixBytes) === 0) {
|
||||
if ($remainingBits === 0 || ord($networkBin[$prefixBytes]) >> (8 - $remainingBits) === ord($ipBin[$prefixBytes]) >> (8 - $remainingBits)) {
|
||||
$deferred->resolve(true);
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
}
|
||||
$deferred->resolve(false);
|
||||
|
||||
return $deferred->promise();
|
||||
}
|
||||
}
|
||||
84
src/Classes/LogReader.php
Normal file
84
src/Classes/LogReader.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
|
||||
function parseLogFile(&$q, $startTime = 0)
|
||||
{
|
||||
$handle = fopen('/home/upw/clients/kpopping/kpopping_access.log', 'r');
|
||||
if ($handle) {
|
||||
while (($line = fgets($handle)) !== false) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parts = explode('; ', $line);
|
||||
|
||||
$ip = trim($parts[0]);
|
||||
$dateString = substr($parts[1], 0, 20);
|
||||
$timestamp = DateTime::createFromFormat('d/M/Y:H:i:s', $dateString)->getTimestamp();
|
||||
// var_dump( $timestamp);
|
||||
$requestParts = explode(' ', $parts[2]);
|
||||
$path = trim($requestParts[1] ?? '');
|
||||
|
||||
if ($timestamp < $startTime || strlen($ip) < 3 || str_contains($path, '.')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$logEntry = [
|
||||
'method' => $parts[1],
|
||||
'ip' => $ip,
|
||||
'path' => $path,
|
||||
'timestamp' => $timestamp
|
||||
];
|
||||
React\Async\await($q($logEntry));
|
||||
// process($logEntry, $counter);
|
||||
|
||||
}
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function process($logEntry, &$counter)
|
||||
{
|
||||
$request = new React\Http\Message\ServerRequest(
|
||||
$logEntry['method'],
|
||||
'https://kpopping.com' . $logEntry['path'],
|
||||
[
|
||||
'X-Forwarded-For' => $logEntry['ip'],
|
||||
'Host' => 'kpopping.com',
|
||||
],
|
||||
'',
|
||||
'1.1'
|
||||
|
||||
);
|
||||
|
||||
return XBotControl\Request::save($request->withAttribute('original_uri', $logEntry['path']), $logEntry['timestamp']);
|
||||
}
|
||||
|
||||
|
||||
\React\Async\await(XBotControl\InitTables::create());
|
||||
|
||||
$q1 = new Clue\React\Mq\Queue(600, null, function ($logEntry) {
|
||||
$request = new React\Http\Message\ServerRequest(
|
||||
'GET',
|
||||
$logEntry['path'],
|
||||
['X-Forwarded-For' => $logEntry['ip']],
|
||||
'',
|
||||
'1.1',
|
||||
);
|
||||
return XBotControl\Request::save($request, $logEntry['timestamp']);;
|
||||
});
|
||||
$q = function ($logEntry) {
|
||||
$request = new React\Http\Message\ServerRequest(
|
||||
'GET',
|
||||
$logEntry['path'],
|
||||
['X-Forwarded-For' => $logEntry['ip']],
|
||||
'',
|
||||
'1.1',
|
||||
);
|
||||
return XBotControl\Request::save($request, $logEntry['timestamp']);;
|
||||
};
|
||||
//parseLogFile($q, 1734379562);
|
||||
@@ -61,7 +61,7 @@ class Report
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "id", "field" => "id", "visible" => false],
|
||||
["title" => "ip", "field" => "ip"],
|
||||
["title" => "ip", "field" => "ip", 'formatter'=> 'ipFormatter'],
|
||||
["title" => "domain", "field" => "domain", "visible" => false],
|
||||
["title" => "path", "field" => "path"],
|
||||
["title" => "useragent", "field" => "useragent"],
|
||||
@@ -97,7 +97,7 @@ class Report
|
||||
public static function count_requests_by_ip(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "ip_address"],
|
||||
["title" => "ip", "field" => "ip_address", 'formatter'=> 'ipFormatter'],
|
||||
["title" => "request_count", "field" => "request_count"],
|
||||
]);
|
||||
|
||||
@@ -157,7 +157,7 @@ class Report
|
||||
public static function top_ip_ua_path(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "ip"],
|
||||
["title" => "ip", "field" => "ip", 'formatter'=> 'ipFormatter'],
|
||||
["title" => "useragent", "field" => "user_agent"],
|
||||
["title" => "path", "field" => "path"],
|
||||
["title" => "count", "field" => "count"],
|
||||
@@ -192,7 +192,7 @@ class Report
|
||||
public static function top_ip_by_load(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "data"],
|
||||
["title" => "ip", "field" => "data", 'formatter'=> 'ipFormatter'],
|
||||
["title" => "avg_load", "field" => "avg_load"],
|
||||
["title" => "request_count", "field" => "request_count"],
|
||||
]);
|
||||
@@ -229,7 +229,7 @@ class Report
|
||||
public static function top_ip_by_rps(ServerRequestInterface $request): PromiseInterface
|
||||
{
|
||||
$columnsDefinition = self::generateColumns([
|
||||
["title" => "ip", "field" => "ip_address"],
|
||||
["title" => "ip", "field" => "ip_address", 'formatter'=> 'ipFormatter'],
|
||||
["title" => "avg_request_per_second", "field" => "avg_request_per_second"],
|
||||
]);
|
||||
|
||||
|
||||
33
src/Classes/ReverseDNS.php
Normal file
33
src/Classes/ReverseDNS.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace XBotControl\Classes;
|
||||
use React\Promise\PromiseInterface;
|
||||
|
||||
class ReverseDNS
|
||||
{
|
||||
|
||||
public static function resolve($ip): PromiseInterface
|
||||
{
|
||||
$reverseName = self::getReverseName($ip);
|
||||
return \XBotControl\Config::getInstance()->dnsResolver->resolveAll($reverseName, \React\Dns\Model\Message::TYPE_PTR)->then(function ($ips) {
|
||||
return $ips;
|
||||
}, function () use ($ip){
|
||||
return $ip;
|
||||
});
|
||||
}
|
||||
|
||||
private static function getReverseName($ip)
|
||||
{
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
return implode('.', array_reverse(explode('.', $ip))) . '.in-addr.arpa';
|
||||
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$unpackedIp = unpack('H*', inet_pton($ip))[1];
|
||||
$reverseIp = implode('.', array_reverse(str_split($unpackedIp))) . '.ip6.arpa';
|
||||
return $reverseIp;
|
||||
} else {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace XBotControl;
|
||||
|
||||
use MaxMind\Db\Reader;
|
||||
|
||||
class Config
|
||||
{
|
||||
@@ -14,8 +15,10 @@ class Config
|
||||
protected static $instance;
|
||||
public $db;
|
||||
public $smarty;
|
||||
public $geoipreader;
|
||||
public $dnsResolver;
|
||||
|
||||
public function __construct()
|
||||
private function __construct()
|
||||
{
|
||||
|
||||
$this->smarty = new \Smarty\Smarty();
|
||||
@@ -28,7 +31,15 @@ class Config
|
||||
'baseURI' => $_ENV['BASEURI'],
|
||||
]);
|
||||
$this->smarty->compile_check = 1;
|
||||
/* $this->db = (new \Clue\React\SQLite\Factory())->openLazy($_ENV['APP_DIR'] . '/bots.db');
|
||||
if (isset($_ENV['GEOIP_DB_FILE_PATH'])) {
|
||||
$this->geoipreader = new Reader($_ENV['APP_DIR'].'/'.$_ENV['GEOIP_DB_FILE']);
|
||||
}
|
||||
$dnsConfig = \React\Dns\Config\Config::loadSystemConfigBlocking();
|
||||
$dnsConfig->nameservers[] = '8.8.8.8';
|
||||
$dnsConfig->nameservers[] = '8.8.4.4';
|
||||
$this->dnsResolver = (new \React\Dns\Resolver\Factory())->createCached($dnsConfig);
|
||||
|
||||
/* $this->db = (new \Clue\React\SQLite\Factory())->openLazy($_ENV['APP_DIR'] . '/bots.db');
|
||||
'uaCache' => new React\Cache\ArrayCache(1000),
|
||||
'ipCache' => new React\Cache\ArrayCache(1000),
|
||||
'headerCache' => new React\Cache\ArrayCache(1000),
|
||||
@@ -73,6 +84,4 @@ class Config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,18 @@ class APIController
|
||||
return call_user_func([\XBotControl\Classes\Report::class, $request->getAttribute('resource')], $request)->then(function ($result) {
|
||||
return \React\Http\Message\Response::json($result);
|
||||
});
|
||||
case 'ipinfo':
|
||||
$ipAddress = $request->getAttribute('resource');
|
||||
return call_user_func([\XBotControl\Classes\GeoIp::class, 'get'], $ipAddress)
|
||||
->then(function ($geoResult) use ($ipAddress) {
|
||||
return \XBotControl\Classes\ReverseDNS::resolve($ipAddress) // Replace SomeClass with the correct class name
|
||||
->then(function ($reverseDns) use ($geoResult) {
|
||||
return \React\Http\Message\Response::json([
|
||||
'geo' => $geoResult,
|
||||
'reverse_dns' => $reverseDns
|
||||
]);
|
||||
});
|
||||
});
|
||||
default:
|
||||
return \React\Http\Message\Response::json(
|
||||
['empty_response']
|
||||
|
||||
@@ -23,7 +23,7 @@ class InitTables
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS headers ( data TEXT UNIQUE NOT NULL) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec("CREATE TABLE IF NOT EXISTS networkwhitelist ( data TEXT UNIQUE NOT NULL CHECK (data LIKE '%/%'), source TEXT NULL , CONSTRAINT valid_network CHECK ( data LIKE '%.%/%' OR data LIKE '%:%/%' )) STRICT ;");
|
||||
return $db->exec("CREATE TABLE IF NOT EXISTS networkwhitelist ( data TEXT UNIQUE NOT NULL CHECK (data LIKE '%/%'), source TEXT NULL , v INT NOT NULL , CONSTRAINT valid_network CHECK ( data LIKE '%.%/%' OR data LIKE '%:%/%' )) STRICT ;");
|
||||
})->then(function () use ($db) {
|
||||
return $db->exec('CREATE TABLE IF NOT EXISTS request ( id_ip INTEGER NOT NULL, id_method INTEGER NOT NULL, id_domain INTEGER NOT NULL, id_path INTEGER NOT NULL, id_useragent INTEGER NOT NULL, id_headers INTEGER NOT NULL, timestamp INTEGER NOT NULL, FOREIGN KEY (id_ip) REFERENCES ip(rowid), FOREIGN KEY (id_domain) REFERENCES domain(rowid), FOREIGN KEY (id_path) REFERENCES path(rowid), FOREIGN KEY (id_useragent) REFERENCES useragent(rowid), FOREIGN KEY (id_headers) REFERENCES headers(rowid) ) STRICT ;');
|
||||
})->then(function () use ($db) {
|
||||
|
||||
Reference in New Issue
Block a user