Skip to content

Commit 508124d

Browse files
authored
Merge pull request #3 from jeyserver/2-support-ipv6
2 support ipv6
2 parents 4b3f8ab + e43b35d commit 508124d

15 files changed

+404
-336
lines changed

bootstrap/app.php

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
$container = Container::getInstance();
1414

1515
$container->singleton(InotifyProxy::class);
16-
$container->singleton(GoogleBotDetector::class);
1716
$container->singleton(LogAnalyzer::class);
1817
$container->singleton(LogsWatcher::class);
1918
$container->singleton(NginxBlocker::class);
@@ -22,15 +21,28 @@
2221
$container->singleton(WhitelistManager::class);
2322
$container->singleton(SelfIPsList::class);
2423
$container->singleton(CsfAllowList::class);
24+
$container->singleton(Logger::class);
25+
$container->singleton(LoggerInterface::class, Logger::class);
26+
$container->singleton(GoogleBotDetector::class, function (Container $container): GoogleBotDetector {
27+
/** @var LoggerInterface */
28+
$logger = $container->make(LoggerInterface::class);
29+
/** @var Config */
30+
$config = $container->make(Config::class);
31+
32+
/** @var array<string,array{path:string,url:string}> */
33+
$files = $config->get(GoogleBotDetector::class.'.options.files');
34+
35+
return new GoogleBotDetector($logger, $config, $files ?: []);
36+
});
2537
$container->singleton(Rules\GoogleBotRule::class);
2638
$container->singleton(Rules\ServerErrorRule::class);
2739
$container->singleton(Rules\StaticFileRule::class);
2840
$container->singleton(Rules\WhiteListedRule::class);
2941
$container->singleton(Rules\AlreadyBlockedRule::class);
30-
$container->singleton(Logger::class);
31-
$container->singleton(LoggerInterface::class, Logger::class);
32-
$container->singleton(Rules\BadBotsRule::class, function ($container) {
42+
$container->singleton(Rules\BadBotsRule::class, function (Container $container): Rules\BadBotsRule {
43+
/** @var LoggerInterface */
3344
$logger = $container->make(LoggerInterface::class);
45+
/** @var Config */
3446
$config = $container->make(Config::class);
3547
$filePath = $config->getFilePath(Rules\BadBotsRule::class.'.options.list');
3648
if (!$filePath) {
@@ -40,27 +52,32 @@
4052

4153
return new Rules\BadBotsRule($logger, $list);
4254
});
43-
$container->singleton(Rules\BruteForceRule::class, function ($container) {
55+
$container->singleton(Rules\BruteForceRule::class, function (Container $container): Rules\BruteForceRule {
56+
/** @var Config */
4457
$config = $container->make(Config::class);
58+
59+
/** @var array{maxRequests?:int,period?:int} */
4560
$options = $config->getOptionsFor(Rules\BruteForceRule::class);
4661

4762
return new Rules\BruteForceRule($options['maxRequests'] ?? 100, $options['period'] ?? 120);
4863
});
49-
$container->singleton(Rules\WPBruteForceRule::class, function ($container) {
64+
$container->singleton(Rules\WPBruteForceRule::class, function (Container $container): Rules\WPBruteForceRule {
65+
/** @var Config */
5066
$config = $container->make(Config::class);
67+
/** @var array{maxRequests?:int,period?:int} */
5168
$options = $config->getOptionsFor(Rules\WPBruteForceRule::class);
5269

5370
return new Rules\WPBruteForceRule($options['maxRequests'] ?? 10, $options['period'] ?? 60);
5471
});
55-
$container->singleton(Application::class, function ($container) {
72+
$container->singleton(Application::class, function (Container $container): Application {
5673
$app = new Application();
5774
$app->add(new Commands\Start($container));
5875
$app->add(new Commands\Install($container));
5976

6077
return $app;
6178
});
6279

63-
$container->singleton('bin-path', function () {
80+
$container->singleton('bin-path', function (): string {
6481
$pharPath = \Phar::running(false);
6582
if ($pharPath) {
6683
return $pharPath;
@@ -69,7 +86,7 @@
6986
return dirname(__DIR__).'/bin/bot-blocker';
7087
});
7188

72-
$container->singleton('bin-dir-path', function () {
89+
$container->singleton('bin-dir-path', function (): string {
7390
$pharPath = \Phar::running(false);
7491
if ($pharPath) {
7592
return dirname($pharPath);

composer.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
"php": "^7.4|^8.0",
88
"dnj/local-filesystem": "^1.0",
99
"dnj/log": "^1.1",
10+
"dnj/tmp-filesystem": "^1.1",
11+
"guzzlehttp/guzzle": "^7.4",
12+
"illuminate/container": "^5.4",
1013
"kassner/log-parser": "^2.1",
11-
"league/mime-type-detection": "^1.9",
1214
"krowinski/php-inotify": "^2.0",
13-
"illuminate/container": "^5.4",
14-
"guzzlehttp/guzzle": "^7.4",
15-
"dnj/tmp-filesystem": "^1.1",
15+
"league/mime-type-detection": "^1.9",
16+
"mlocati/ip-lib": "^1.18",
1617
"symfony/console": "^5.0",
1718
"webignition/php-path-resolver": "^0.4.0"
1819
},
@@ -45,6 +46,7 @@
4546
"minimum-stability": "dev",
4647
"prefer-stable": true,
4748
"config": {
48-
"allow-plugins": false
49+
"allow-plugins": false,
50+
"sort-packages": true
4951
}
5052
}

composer.lock

Lines changed: 72 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@
2727
}
2828
}
2929
],
30+
"google-bot-detector": {
31+
"name": "Arad\\BotBlocker\\GoogleBotDetector",
32+
"options": {
33+
"files": {
34+
"goog.json": {
35+
"path": "google-bot-detector/goog.json",
36+
"url": "https://www.gstatic.com/ipranges/goog.json"
37+
},
38+
"googlebot.json": {
39+
"path": "google-bot-detector/googlebot.json",
40+
"url": "https://developers.google.com/static/search/apis/ipranges/googlebot.json"
41+
},
42+
"special-crawlers.json": {
43+
"path": "google-bot-detector/special-crawlers.json",
44+
"url": "https://developers.google.com/static/search/apis/ipranges/special-crawlers.json"
45+
},
46+
"user-triggered-fetchers.json": {
47+
"path": "google-bot-detector/user-triggered-fetchers.json",
48+
"url": "https://developers.google.com/static/search/apis/ipranges/user-triggered-fetchers.json"
49+
}
50+
}
51+
}
52+
},
3053
"logging": {
3154
"file": "/var/log/bot-blocker.log",
3255
"quiet": true,

src/Config.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class Config
1414
public static function parseFromFile(File $file): self
1515
{
1616
$config = json_decode($file->read(), true, 512, JSON_THROW_ON_ERROR);
17+
self::setClassOptions($config, 'google-bot-detector');
1718
self::setClassOptions($config, 'defense-system');
1819
self::setClassOptions($config, 'monitor-system');
1920
if (isset($config['rules']) and is_array($config['rules'])) {

src/CsfAllowList.php

Lines changed: 30 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use dnj\Filesystem\Local\File;
66
use Exception;
7+
use IPLib\Address\AddressInterface;
8+
use IPLib\Factory as IPLibFactory;
9+
use IPLib\Range\RangeInterface;
710
use Psr\Log\LoggerAwareInterface;
811
use Psr\Log\LoggerInterface;
912

@@ -12,17 +15,14 @@
1215
*/
1316
class CsfAllowList implements LoggerAwareInterface
1417
{
15-
/**
16-
* @var SortedList<int>
17-
*/
18-
protected SortedList $ips;
18+
/** @var RangeInterface[] */
19+
protected array $ipRanges = [];
1920

2021
protected LoggerInterface $logger;
2122

2223
public function __construct(LoggerInterface $logger)
2324
{
2425
$this->logger = $logger;
25-
$this->ips = new SortedList();
2626
}
2727

2828
public function setLogger(LoggerInterface $logger): void
@@ -37,7 +37,7 @@ public function getLogger(): LoggerInterface
3737

3838
public function reload(): void
3939
{
40-
$this->ips = new SortedList();
40+
$this->ipRanges = [];
4141
$files = [
4242
new File('/etc/csf/csf.allow'),
4343
new File('/etc/csf/csf.ignore'),
@@ -62,27 +62,37 @@ public function addFromFile(File $file): void
6262
}
6363
}
6464

65-
public function has(string $ip): bool
65+
public function has(AddressInterface $ip): bool
6666
{
67-
$ip = ip2long($ip);
68-
if (false === $ip) {
69-
return false;
67+
foreach ($this->ipRanges as $ipRange) {
68+
if ($ipRange->contains($ip)) {
69+
return true;
70+
}
7071
}
7172

72-
return $this->ips->has($ip);
73+
return false;
7374
}
7475

7576
/**
76-
* @return \Generator<string>
77+
* @return \Generator<AddressInterface>
7778
*/
7879
public function getIPs(): \Generator
7980
{
80-
foreach ($this->ips as $ip) {
81-
$ip = long2ip($ip);
82-
if (false === $ip) {
83-
continue;
81+
foreach ($this->ipRanges as $ipRange) {
82+
yield from $this->getIpsFromRange($ipRange);
83+
}
84+
}
85+
86+
/**
87+
* @return \Generator<AddressInterface>
88+
*/
89+
protected function getIpsFromRange(RangeInterface $range): \Generator
90+
{
91+
for ($x = 0; $x < $range->getSize(); ++$x) {
92+
$ip = $range->getAddressAtOffset($x);
93+
if ($ip) {
94+
yield $ip;
8495
}
85-
yield $ip;
8696
}
8797
}
8898

@@ -101,42 +111,10 @@ protected function processLine(string $line): void
101111
if (false !== strpos($line, '|')) {
102112
return;
103113
}
104-
$subnet = $this->getSubnet($line);
105-
$this->addSubnet($subnet);
106-
}
107114

108-
/**
109-
* @param Subnet $subnet
110-
*/
111-
protected function addSubnet(array $subnet): void
112-
{
113-
for ($x = $subnet[0]; $x < $subnet[1]; ++$x) {
114-
$this->ips->add($x);
115+
$range = IPLibFactory::parseRangeString($line);
116+
if ($range) {
117+
$this->ipRanges[] = $range;
115118
}
116119
}
117-
118-
/**
119-
* @return Subnet
120-
*/
121-
protected function getSubnet(string $subnet): array
122-
{
123-
if (!preg_match("/^([\d\.]+)(?:\/(\d+))?$/", $subnet, $matches)) {
124-
throw new Exception("'{$subnet}' is not valid subnet");
125-
}
126-
$network = $matches[1];
127-
$netmask = isset($matches[2]) ? intval($matches[2]) : 32;
128-
if (!filter_var($network, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
129-
throw new Exception("'{$subnet}' is not valid subnet");
130-
}
131-
if ($netmask > 32) {
132-
throw new Exception("'{$subnet}' is not valid subnet");
133-
}
134-
$start = ip2long($network);
135-
if (false === $start) {
136-
throw new Exception();
137-
}
138-
$end = $start + pow(2, 32 - $netmask);
139-
140-
return [$start, $end];
141-
}
142120
}

0 commit comments

Comments
 (0)