summaryrefslogtreecommitdiffstats
path: root/library/Director/Filter/CidrExpression.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/Filter/CidrExpression.php')
-rw-r--r--library/Director/Filter/CidrExpression.php89
1 files changed, 89 insertions, 0 deletions
diff --git a/library/Director/Filter/CidrExpression.php b/library/Director/Filter/CidrExpression.php
new file mode 100644
index 0000000..169ddce
--- /dev/null
+++ b/library/Director/Filter/CidrExpression.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Icinga\Module\Director\Filter;
+
+use Icinga\Data\Filter\FilterExpression;
+use InvalidArgumentException;
+
+use function array_map;
+use function filter_var;
+use function inet_pton;
+use function pack;
+use function preg_match;
+use function str_pad;
+use function str_split;
+
+class CidrExpression extends FilterExpression
+{
+ protected $networkAddress;
+ protected $broadcastAddress;
+
+ public function __construct($column, $sign, $expression)
+ {
+ if ($parts = static::splitOptionalCidrString($expression)) {
+ list($this->networkAddress, $this->broadcastAddress) = $parts;
+ } else {
+ throw new InvalidArgumentException("'$expression' isn't valid CIDR notation");
+ }
+
+ parent::__construct($column, $sign, $expression);
+ }
+
+ public static function isCidrFormat(string $string): bool
+ {
+ return static::splitOptionalCidrString($string) !== null;
+ }
+
+ protected static function splitOptionalCidrString(string $string): ?array
+ {
+ if (preg_match('#^(.+?)/(\d{1,3})$#', $string, $match)) {
+ $address = $match[1];
+ $mask = (int) $match[2];
+
+ if (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && $mask <= 32) {
+ $bits = 32;
+ } elseif (filter_var($address, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && $mask <= 128) {
+ $bits = 128;
+ } else {
+ return null;
+ }
+
+ $binaryAddress = inet_pton($address);
+ $broadcast = $binaryAddress | static::bitmaskToInverseBinaryMask($mask, $bits);
+
+ return [$binaryAddress, $broadcast];
+ }
+
+ return null;
+ }
+
+ public function matches($row): bool
+ {
+ if (! isset($row->{$this->column})) {
+ return false;
+ }
+ $value = inet_pton((string) $row->{$this->column});
+
+ return $value >= $this->networkAddress && $value <= $this->broadcastAddress;
+ }
+
+ public static function fromExpression(FilterExpression $filter): CidrExpression
+ {
+ $sign = $filter->getSign();
+ if ($sign !== '=') {
+ throw new InvalidArgumentException("'$sign' cannot be applied to CIDR notation");
+ }
+ return new CidrExpression($filter->getColumn(), $sign, $filter->getExpression());
+ }
+
+ protected static function bitmaskToInverseBinaryMask($mask, $maxLen): string
+ {
+ $binary = str_pad(str_pad('', $mask, '0'), $maxLen, '1');
+ $address = '';
+ foreach (array_map('bindec', str_split($binary, 8)) as $char) {
+ $address .= pack('C*', $char);
+ }
+
+ return $address;
+ }
+}