From 19d19adec3bb88d3ba8f6d1c08063a85ac8b9506 Mon Sep 17 00:00:00 2001 From: flashwave Date: Fri, 16 Mar 2018 03:01:24 +0100 Subject: [PATCH] Rework IP address handling. --- public/auth.php | 4 +- src/Net/CIDR.php | 107 ---------------- src/Net/IP.php | 82 ------------ src/Net/IPAddress.php | 141 ++++++++++++++++++++ src/Net/IPAddressRange.php | 121 ++++++++++++++++++ src/Net/NetAddressTypeException.php | 11 -- src/Net/NetException.php | 11 -- src/Net/NetInvalidAddressException.php | 11 -- src/Users/Session.php | 14 +- src/Users/User.php | 22 ++-- tests/CIDRTest.php | 29 ----- tests/IPAddressTest.php | 170 +++++++++++++++++++++++++ tests/IPTest.php | 77 ----------- 13 files changed, 452 insertions(+), 348 deletions(-) delete mode 100644 src/Net/CIDR.php delete mode 100644 src/Net/IP.php create mode 100644 src/Net/IPAddress.php create mode 100644 src/Net/IPAddressRange.php delete mode 100644 src/Net/NetAddressTypeException.php delete mode 100644 src/Net/NetException.php delete mode 100644 src/Net/NetInvalidAddressException.php delete mode 100644 tests/CIDRTest.php create mode 100644 tests/IPAddressTest.php delete mode 100644 tests/IPTest.php diff --git a/public/auth.php b/public/auth.php index d631c59f..0148df69 100644 --- a/public/auth.php +++ b/public/auth.php @@ -3,7 +3,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\ModelNotFoundException; use Misuzu\Application; use Misuzu\Database; -use Misuzu\Net\IP; +use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\Session; @@ -75,7 +75,7 @@ switch ($mode) { // Temporary key generation for chat login. // Should eventually be replaced with a callback login system. // Also uses different cookies since $httponly is required to be false for these. - $user->last_ip = IP::remote(); + $user->last_ip = IPAddress::remote(); $user->user_chat_key = bin2hex(random_bytes(16)); $user->save(); diff --git a/src/Net/CIDR.php b/src/Net/CIDR.php deleted file mode 100644 index 3063dcbb..00000000 --- a/src/Net/CIDR.php +++ /dev/null @@ -1,107 +0,0 @@ - - */ -class CIDR -{ - /** - * Matches an IP to a CIDR range. - * @param string $ipAddr - * @param string $network - * @param int|null $mask - * @return bool - */ - public static function match(string $ipAddr, string $network, ?int $mask = null): bool - { - if ($mask === null) { - [$network, $mask] = explode('/', $network); - } - - if (empty($mask)) { - throw new InvalidArgumentException('No bitmask supplied.'); - } - - $ipv = IP::version($ipAddr); - $rangev = IP::version($network); - - if (!$ipv || !$rangev || $ipv !== $rangev) { - return false; - } - - switch ($ipv) { - case IP::V6: - return static::matchV6($ipAddr, $network, $mask); - - case IP::V4: - return static::matchV4($ipAddr, $network, $mask); - - default: - throw new InvalidArgumentException('Invalid IP type.'); - } - } - - /** - * Matches an IPv4 to a CIDR range. - * @param string $ipAddr - * @param string $network - * @param int $mask - * @return bool - */ - private static function matchV4(string $ipAddr, string $network, int $mask): bool - { - $ipAddr = ip2long($ipAddr); - $network = ip2long($network); - $mask = -1 << (32 - $mask); - return ($ipAddr & $mask) === ($network & $mask); - } - - /** - * Matches an IPv6 to a CIDR range. - * @param string $ipAddr - * @param string $network - * @param int $mask - * @return bool - */ - private static function matchV6(string $ipAddr, string $network, int $mask): bool - { - $ipAddr = inet_pton($ipAddr); - $network = inet_pton($network); - $mask = static::createV6Mask($mask); - return ($ipAddr & $mask) === ($network & $mask); - } - - /** - * Converts an IPv6 mask to bytes. - * @param int $mask - * @return string - */ - private static function createV6Mask(int $mask): string - { - $range = str_repeat('f', $mask / 4); - - switch ($mask % 4) { - case 1: - $range .= '8'; - break; - - case 2: - $range .= 'c'; - break; - - case 3: - $range .= 'e'; - break; - } - - $range = str_pad($range, 32, '0'); - $range = pack('H*', $range); - - return $range; - } -} diff --git a/src/Net/IP.php b/src/Net/IP.php deleted file mode 100644 index 65977000..00000000 --- a/src/Net/IP.php +++ /dev/null @@ -1,82 +0,0 @@ - - */ -class IP -{ - public const V4 = 4; - public const V6 = 6; - - /** - * Attempts to get the remote ip address, falls back to IPv6 localhost. - * @return string - */ - public static function remote(string $fallback = '::1'): string - { - return $_SERVER['REMOTE_ADDR'] ?? $fallback; - } - - /** - * Detects IP version. - * @param string $ip - * @return int - */ - public static function version(string $ip): int - { - if (!filter_var($ip, FILTER_VALIDATE_IP)) { - return 0; - } - - if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { - return static::V6; - } - - if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { - return static::V4; - } - - return 0; - } - - /** - * Converts a printable IP address into an packed binary string. - * @param string $ip - * @throws NetInvalidAddressException - * @return string - */ - public static function unpack(string $ip): string - { - $ipv = static::version($ip); - - if ($ipv === 6) { - return current(unpack('A16', inet_pton($ip))); - } - - if ($ipv === 4) { - return current(unpack('A4', inet_pton($ip))); - } - - throw new NetInvalidAddressException; - } - - /** - * Converts a binary unpacked IP to a printable unpacked IP. - * @param string $bin - * @throws NetAddressTypeException - * @return string - */ - public static function pack(string $bin): string - { - $len = strlen($bin); - - if ($len !== 4 && $len !== 16) { - throw new NetAddressTypeException; - } - - return inet_ntop(pack("A{$len}", $bin)); - } -} diff --git a/src/Net/IPAddress.php b/src/Net/IPAddress.php new file mode 100644 index 00000000..e8998657 --- /dev/null +++ b/src/Net/IPAddress.php @@ -0,0 +1,141 @@ + + */ +final class IPAddress +{ + private const FALLBACK_ADDRESS = '::1'; + + public const UNKNOWN_VERSION = 0; + public const V4 = 4; + public const V6 = 6; + + public const BYTE_COUNT = [ + self::V4 => 4, + self::V6 => 16, + ]; + + private $ipVersion = self::UNKNOWN_VERSION; + private $ipRaw = null; + + public function getVersion(): int + { + return $this->ipVersion; + } + + public function getRaw(): string + { + return $this->ipRaw; + } + + public function getString(): string + { + return inet_ntop($this->ipRaw); + } + + public function __construct(int $version, string $rawIp) + { + if (!array_key_exists($version, self::BYTE_COUNT)) { + throw new InvalidArgumentException('Invalid IP version provided.'); + } + + if (strlen($rawIp) !== self::BYTE_COUNT[$version]) { + throw new InvalidArgumentException('Binary IP was of invalid length.'); + } + + $this->ipVersion = $version; + $this->ipRaw = $rawIp; + } + + public function compareTo(IPAddress $other): int + { + if ($other->getVersion() !== $this->getVersion()) { + throw new InvalidArgumentException('Both addresses must be of the same version.'); + } + + $parts_this = array_values(unpack('N*', $this->getRaw())); + $parts_other = array_values(unpack('N*', $other->getRaw())); + $size = count($parts_this); + + if ($size !== count($parts_other)) { + throw new InvalidArgumentException('Addresses varied in length. (if you touched $ipRaw, i will fight you)'); + } + + for ($i = 0; $i < $size; $i++) { + $result = $parts_other[$i] <=> $parts_this[$i]; + + if ($result !== 0) { + return $result; + } + } + + return 0; + } + + public static function remote(string $fallbackAddress = self::FALLBACK_ADDRESS): IPAddress + { + try { + return self::fromString($_SERVER['REMOTE_ADDR'] ?? $fallbackAddress); + } catch (InvalidArgumentException $ex) { + return self::fromString($fallbackAddress); + } + } + + public static function fromRaw(string $rawIp): IPAddress + { + $version = self::detectVersionFromRaw($rawIp); + + if ($version === self::UNKNOWN_VERSION) { + throw new InvalidArgumentException('Invalid raw IP address supplied.'); + } + + return new static($version, $rawIp); + } + + public static function fromString(string $ipAddress): IPAddress + { + $version = self::detectVersionFromString($ipAddress); + + if (!array_key_exists($version, self::BYTE_COUNT)) { + throw new InvalidArgumentException('Invalid IP address supplied.'); + } + + return new static($version, inet_pton($ipAddress)); + } + + public static function detectVersionFromRaw(string $rawIp): int + { + $rawLength = strlen($rawIp); + + foreach (self::BYTE_COUNT as $version => $length) { + if ($rawLength === $length) { + return $version; + } + } + + return self::UNKNOWN_VERSION; + } + + public static function detectVersionFromString(string $ipAddress): int + { + if (filter_var($ipAddress, FILTER_VALIDATE_IP) === false) { + return self::UNKNOWN_VERSION; + } + + if (filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { + return self::V6; + } + + if (filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { + return self::V4; + } + + return self::UNKNOWN_VERSION; + } +} diff --git a/src/Net/IPAddressRange.php b/src/Net/IPAddressRange.php new file mode 100644 index 00000000..8871e426 --- /dev/null +++ b/src/Net/IPAddressRange.php @@ -0,0 +1,121 @@ +maskAddress; + } + + public function getCidrLength(): int + { + return $this->cidrLength; + } + + public function __construct(IPAddress $maskAddress, int $cidrLength) + { + if ($cidrLength > IPAddress::BYTE_COUNT[$maskAddress->getVersion()] * 8) { + throw new InvalidArgumentException('CIDR length is out of range.'); + } + + $this->maskAddress = $maskAddress; + $this->cidrLength = $cidrLength; + } + + public function getMaskedString(): string + { + return $this->getMaskAddress()->getString() . '/' . $this->getCidrLength(); + } + + public function match(IPAddress $ipAddress, bool $explicitExceptions = false): bool + { + if ($ipAddress->getVersion() !== $this->getMaskAddress()->getVersion()) { + if ($explicitExceptions) { + throw new InvalidArgumentException('Both addresses must be of the same version.'); + } + + return false; + } + + $ipParts = array_values(unpack('N*', $ipAddress->getRaw())); + $maskParts = array_values(unpack('N*', $this->getMaskAddress()->getRaw())); + $parts = count($ipParts); + + if ($parts !== count($maskParts)) { + if ($explicitExceptions) { + throw new InvalidArgumentException('Both addresses must be of the same version (failed 1).'); + } + + return false; + } + + for ($i = 0; $i < $parts; $i++) { + $ipParts[$i] = $ipParts[$i] & $maskParts[$i]; + } + + return $this->getMaskAddress()->getRaw() === pack('N*', ...$ipParts); + } + + public static function fromMaskedString(string $maskedString): IPAddressRange + { + if (strpos($maskedString, '/') === false) { + throw new InvalidArgumentException('Invalid masked string.'); + } + + [$maskedAddress, $cidrLength] = explode('/', $maskedString, 2); + $maskedAddress = IPAddress::fromString($maskedAddress); + $cidrLength = (int)$cidrLength; + + return new static($maskedAddress, $cidrLength); + } + + // very uncertain about this logic in regards to any ip larger than 32 bits + // if you _do_ know what you're doing, review this and call me an idiot please + public static function fromRangeString(string $rangeString): IPAddressRange + { + if (strpos($rangeString, '-') === false) { + throw new InvalidArgumentException('Invalid range string.'); + } + + [$rangeStart, $rangeEnd] = explode('-', $rangeString, 2); + $rangeStart = IPAddress::fromString($rangeStart); + $rangeEnd = IPAddress::fromString($rangeEnd); + + // implicitly performs a version compare as well, throws an exception if different + if ($rangeStart->compareTo($rangeEnd) < 1) { + throw new InvalidArgumentException('Range start was larger (or equal) to the range end.'); + } + + $partsStart = array_values(unpack('N*', $rangeStart->getRaw())); + $partsEnd = array_values(unpack('N*', $rangeEnd->getRaw())); + $parts = count($partsStart); + + if ($parts !== count($partsEnd)) { + throw new InvalidArgumentException('Range start was larger (or equal) to the range end (failed 1).'); + } + + $bits = $parts * 32; + $mask = array_fill(0, $parts, 0); + + for ($i = 0; $i < $parts; $i++) { + $diffs = $partsStart[$i] ^ $partsEnd[$i]; + + while ($diffs != 0) { + $diffs >>= 1; + $bits -= 1; + $mask[$i] = ($mask[$i] << 1) | 1; + } + + $mask[$i] = $partsStart[$i] & ~$mask[$i]; + } + + $mask = pack('N*', ...$mask); + return new static(new IPAddress($rangeStart->getVersion(), $mask), $bits); + } +} diff --git a/src/Net/NetAddressTypeException.php b/src/Net/NetAddressTypeException.php deleted file mode 100644 index b5093bbf..00000000 --- a/src/Net/NetAddressTypeException.php +++ /dev/null @@ -1,11 +0,0 @@ - - */ -class NetAddressTypeException extends NetException -{ -} diff --git a/src/Net/NetException.php b/src/Net/NetException.php deleted file mode 100644 index 8a286e9a..00000000 --- a/src/Net/NetException.php +++ /dev/null @@ -1,11 +0,0 @@ - - */ -class NetException extends \Exception -{ -} diff --git a/src/Net/NetInvalidAddressException.php b/src/Net/NetInvalidAddressException.php deleted file mode 100644 index 3bd64413..00000000 --- a/src/Net/NetInvalidAddressException.php +++ /dev/null @@ -1,11 +0,0 @@ - - */ -class NetInvalidAddressException extends NetException -{ -} diff --git a/src/Users/Session.php b/src/Users/Session.php index 1ac8f99d..725b84c9 100644 --- a/src/Users/Session.php +++ b/src/Users/Session.php @@ -3,7 +3,7 @@ namespace Misuzu\Users; use Carbon\Carbon; use Misuzu\Model; -use Misuzu\Net\IP; +use Misuzu\Net\IPAddress; class Session extends Model { @@ -14,9 +14,9 @@ class Session extends Model User $user, ?string $userAgent = null, ?Carbon $expires = null, - ?string $ipAddress = null + ?IPAddress $ipAddress = null ): Session { - $ipAddress = $ipAddress ?? IP::remote(); + $ipAddress = $ipAddress ?? IPAddress::remote(); $userAgent = $userAgent ?? 'Misuzu'; $expires = $expires ?? Carbon::now()->addMonth(); @@ -41,14 +41,14 @@ class Session extends Model return $this->expires_on->isPast(); } - public function getSessionIpAttribute(string $ipAddress): string + public function getSessionIpAttribute(string $ipAddress): IPAddress { - return IP::pack($ipAddress); + return IPAddress::fromRaw($ipAddress); } - public function setSessionIpAttribute(string $ipAddress): void + public function setSessionIpAttribute(IPAddress $ipAddress): void { - $this->attributes['session_ip'] = IP::unpack($ipAddress); + $this->attributes['session_ip'] = $ipAddress->getRaw(); } public function user() diff --git a/src/Users/User.php b/src/Users/User.php index b8dd6772..dcc5ad33 100644 --- a/src/Users/User.php +++ b/src/Users/User.php @@ -4,7 +4,7 @@ namespace Misuzu\Users; use Illuminate\Database\Eloquent\SoftDeletes; use Misuzu\Database; use Misuzu\Model; -use Misuzu\Net\IP; +use Misuzu\Net\IPAddress; class User extends Model { @@ -24,9 +24,9 @@ class User extends Model string $username, string $password, string $email, - ?string $ipAddress = null + ?IPAddress $ipAddress = null ): User { - $ipAddress = $ipAddress ?? IP::remote(); + $ipAddress = $ipAddress ?? IPAddress::remote(); $user = new User; $user->username = $username; @@ -34,7 +34,7 @@ class User extends Model $user->email = $email; $user->register_ip = $ipAddress; $user->last_ip = $ipAddress; - $user->user_country = get_country_code($ipAddress); + $user->user_country = get_country_code($ipAddress->getString()); $user->save(); return $user; @@ -139,24 +139,24 @@ class User extends Model } } - public function getRegisterIpAttribute(string $ipAddress): string + public function getRegisterIpAttribute(string $ipAddress): IPAddress { - return IP::pack($ipAddress); + return IPAddress::fromRaw($ipAddress); } - public function setRegisterIpAttribute(string $ipAddress): void + public function setRegisterIpAttribute(IPAddress $ipAddress): void { - $this->attributes['register_ip'] = IP::unpack($ipAddress); + $this->attributes['register_ip'] = $ipAddress->getRaw(); } public function getLastIpAttribute(string $ipAddress): string { - return IP::pack($ipAddress); + return IPAddress::fromRaw($ipAddress); } - public function setLastIpAttribute(string $ipAddress): void + public function setLastIpAttribute(IPAddress $ipAddress): void { - $this->attributes['last_ip'] = IP::unpack($ipAddress); + $this->attributes['last_ip'] = $ipAddress->getRaw(); } public function setPasswordAttribute(string $password): void diff --git a/tests/CIDRTest.php b/tests/CIDRTest.php deleted file mode 100644 index b80e2123..00000000 --- a/tests/CIDRTest.php +++ /dev/null @@ -1,29 +0,0 @@ -assertTrue(CIDR::match('104.27.135.189', '104.16.0.0/12')); - $this->assertTrue(CIDR::match('104.27.154.200', '104.16.0.0', 12)); - $this->assertTrue(CIDR::match('104.28.9.4', '104.16.0.0/12')); - $this->assertTrue(CIDR::match('104.27.135.189', '104.27.115.10', 12)); - $this->assertTrue(CIDR::match('104.27.154.200', '104.27.154.20/12')); - $this->assertTrue(CIDR::match('104.28.9.4', '104.28.9.8', 12)); - } - - // public function testIPv6() - // { - // // IPv6 matching is broken, yay - // $this->assertTrue(CIDR::match('2400:cb00:2048:1:0:0:681b:9ac8', '2400:cb00::', 32)); - // $this->assertTrue(CIDR::match('2400:cb00:2048:1:0:0:681b:9ac8', '2400:cb00:2048:1:0:0:681b:5341/32')); - // $this->assertTrue(CIDR::match('2400:cb00:2048:1:0:0:681c:804', '2400:cb00::/32')); - // $this->assertTrue(CIDR::match('2400:cb00:2048:1:0:0:681c:804', '2400:cb00:2048:1:0:0::804/64')); - // $this->assertTrue(CIDR::match('2400:cb00:2048:1:0:0:681b:86bd', '2400:cb00::/32')); - // $this->assertTrue(CIDR::match('2400:cb00:2048:1:0:0:681f:5e2a', '2400:cb00::/16')); - // } -} diff --git a/tests/IPAddressTest.php b/tests/IPAddressTest.php new file mode 100644 index 00000000..8fde1935 --- /dev/null +++ b/tests/IPAddressTest.php @@ -0,0 +1,170 @@ +assertEquals(IPAddress::V4, IPAddress::detectVersionFromString('127.0.0.1')); + $this->assertEquals(IPAddress::V4, IPAddress::detectVersionFromRaw(hex2bin('7f000001'))); + + $this->assertEquals(IPAddress::V4, IPAddress::detectVersionFromString('104.27.135.189')); + $this->assertEquals(IPAddress::V4, IPAddress::detectVersionFromRaw(hex2bin('681b87bd'))); + + $this->assertEquals(IPAddress::V4, IPAddress::detectVersionFromString('104.27.154.200')); + $this->assertEquals(IPAddress::V4, IPAddress::detectVersionFromRaw(hex2bin('681b9ac8'))); + + $this->assertEquals(IPAddress::V4, IPAddress::detectVersionFromString('104.28.9.4')); + $this->assertEquals(IPAddress::V4, IPAddress::detectVersionFromRaw(hex2bin('681c0904'))); + + $this->assertEquals(IPAddress::V6, IPAddress::detectVersionFromString('::1')); + $this->assertEquals( + IPAddress::V6, + IPAddress::detectVersionFromRaw(hex2bin('00000000000000000000000000000001')) + ); + + $this->assertEquals(IPAddress::V6, IPAddress::detectVersionFromString('2400:cb00:2048:1:0:0:681b:9ac8')); + $this->assertEquals( + IPAddress::V6, + IPAddress::detectVersionFromRaw(hex2bin('2400cb002048000100000000681b9ac8')) + ); + + $this->assertEquals(IPAddress::V6, IPAddress::detectVersionFromString('2400:cb00:2048:1:0:0:681c:804')); + $this->assertEquals( + IPAddress::V6, + IPAddress::detectVersionFromRaw(hex2bin('2400cb002048000100000000681c0804')) + ); + + $this->assertEquals(IPAddress::V6, IPAddress::detectVersionFromString('2400:cb00:2048:1:0:0:681b:86bd')); + $this->assertEquals( + IPAddress::V6, + IPAddress::detectVersionFromRaw(hex2bin('2400cb002048000100000000681b86bd')) + ); + + $this->assertEquals(IPAddress::V6, IPAddress::detectVersionFromString('2400:cb00:2048:1:0:0:681f:5e2a')); + $this->assertEquals( + IPAddress::V6, + IPAddress::detectVersionFromRaw(hex2bin('2400cb002048000100000000681f5e2a')) + ); + + $this->assertEquals(IPAddress::UNKNOWN_VERSION, IPAddress::detectVersionFromString('not an ip address')); + $this->assertEquals(IPAddress::UNKNOWN_VERSION, IPAddress::detectVersionFromString('256.256.256.256')); + $this->assertEquals(IPAddress::UNKNOWN_VERSION, IPAddress::detectVersionFromRaw('invalid')); + } + + public function testString() + { + $this->assertEquals(hex2bin('7f000001'), IPAddress::fromString('127.0.0.1')->getRaw()); + $this->assertEquals(hex2bin('681b87bd'), IPAddress::fromString('104.27.135.189')->getRaw()); + $this->assertEquals(hex2bin('681b9ac8'), IPAddress::fromString('104.27.154.200')->getRaw()); + $this->assertEquals(hex2bin('681c0904'), IPAddress::fromString('104.28.9.4')->getRaw()); + + $this->assertEquals( + hex2bin('00000000000000000000000000000001'), + IPAddress::fromString('::1')->getRaw() + ); + $this->assertEquals( + hex2bin('2400cb002048000100000000681b9ac8'), + IPAddress::fromString('2400:cb00:2048:1:0:0:681b:9ac8')->getRaw() + ); + $this->assertEquals( + hex2bin('2400cb002048000100000000681c0804'), + IPAddress::fromString('2400:cb00:2048:1:0:0:681c:804')->getRaw() + ); + $this->assertEquals( + hex2bin('2400cb002048000100000000681b86bd'), + IPAddress::fromString('2400:cb00:2048:1:0:0:681b:86bd')->getRaw() + ); + $this->assertEquals( + hex2bin('2400cb002048000100000000681f5e2a'), + IPAddress::fromString('2400:cb00:2048:1:0:0:681f:5e2a')->getRaw() + ); + } + + public function testRaw() + { + $this->assertEquals('127.0.0.1', IPAddress::fromRaw(hex2bin('7f000001'))->getString()); + $this->assertEquals('104.27.135.189', IPAddress::fromRaw(hex2bin('681b87bd'))->getString()); + $this->assertEquals('104.27.154.200', IPAddress::fromRaw(hex2bin('681b9ac8'))->getString()); + $this->assertEquals('104.28.9.4', IPAddress::fromRaw(hex2bin('681c0904'))->getString()); + + $this->assertEquals( + '::1', + IPAddress::fromRaw(hex2bin('00000000000000000000000000000001'))->getString() + ); + $this->assertEquals( + IPAddress::fromRaw(hex2bin('2400cb002048000100000000681b9ac8'))->getString(), + '2400:cb00:2048:1::681b:9ac8' + ); + $this->assertEquals( + '2400:cb00:2048:1::681c:804', + IPAddress::fromRaw(hex2bin('2400cb002048000100000000681c0804'))->getString() + ); + $this->assertEquals( + '2400:cb00:2048:1::681b:86bd', + IPAddress::fromRaw(hex2bin('2400cb002048000100000000681b86bd'))->getString() + ); + $this->assertEquals( + '2400:cb00:2048:1::681f:5e2a', + IPAddress::fromRaw(hex2bin('2400cb002048000100000000681f5e2a'))->getString() + ); + } + + public function testCompare() + { + $v4_start = IPAddress::fromString('117.0.0.255'); + $v4_end = IPAddress::fromString('127.0.0.255'); + $v6_start = IPAddress::fromString('::1'); + $v6_end = IPAddress::fromString('::FFFF'); + + $this->assertEquals(1, $v4_start->compareTo($v4_end)); + $this->assertEquals(-1, $v4_end->compareTo($v4_start)); + $this->assertEquals(0, $v4_start->compareTo($v4_start)); + $this->assertEquals(0, $v4_end->compareTo($v4_end)); + + $this->assertEquals(1, $v6_start->compareTo($v6_end)); + $this->assertEquals(-1, $v6_end->compareTo($v6_start)); + $this->assertEquals(0, $v6_start->compareTo($v6_start)); + $this->assertEquals(0, $v6_end->compareTo($v6_end)); + } + + public function testMaskedRange() + { + $range_v4 = IPAddressRange::fromMaskedString('127.0.0.1/8'); + $this->assertEquals('127.0.0.1', $range_v4->getMaskAddress()->getString()); + $this->assertEquals(8, $range_v4->getCidrLength()); + $this->assertEquals('127.0.0.1/8', $range_v4->getMaskedString()); + + $range_v6 = IPAddressRange::fromMaskedString('::1/16'); + $this->assertEquals('::1', $range_v6->getMaskAddress()->getString()); + $this->assertEquals(16, $range_v6->getCidrLength()); + $this->assertEquals('::1/16', $range_v6->getMaskedString()); + } + + // excellent naming + public function testRangedRange() + { + $range_v4 = IPAddressRange::fromRangeString('255.255.255.248-255.255.255.255'); + $this->assertEquals('255.255.255.248', $range_v4->getMaskAddress()->getString()); + $this->assertEquals(29, $range_v4->getCidrLength()); + + $range_v6 = IPAddressRange::fromRangeString('2400:cb00:2048:1::681b:86bd-2400:cb00:2048:1::681f:5e2a'); + $this->assertEquals('2400:cb00:2048:1::6818:0', $range_v6->getMaskAddress()->getString()); + $this->assertEquals(109, $range_v6->getCidrLength()); + } + + public function testMatchRange() + { + $range_v4 = new IPAddressRange(IPAddress::fromString('108.162.192.0'), 18); + $this->assertTrue($range_v4->match(IPAddress::fromString('108.162.255.255'))); + $this->assertFalse($range_v4->match(IPAddress::fromString('127.0.0.1'))); + + $range_v6 = new IPAddressRange(IPAddress::fromString('2a06:98c0::'), 29); + $this->assertTrue($range_v6->match(IPAddress::fromString('2a06:98c7:7f:43:645:ab:cd:2525'))); + $this->assertFalse($range_v6->match(IPAddress::fromString('::1'))); + } +} diff --git a/tests/IPTest.php b/tests/IPTest.php deleted file mode 100644 index 9049d3c0..00000000 --- a/tests/IPTest.php +++ /dev/null @@ -1,77 +0,0 @@ -assertEquals(IP::version('127.0.0.1'), IP::V4); - $this->assertEquals(IP::version('104.27.135.189'), IP::V4); - $this->assertEquals(IP::version('104.27.154.200'), IP::V4); - $this->assertEquals(IP::version('104.28.9.4'), IP::V4); - - $this->assertEquals(IP::version('::1'), IP::V6); - $this->assertEquals(IP::version('2400:cb00:2048:1:0:0:681b:9ac8'), IP::V6); - $this->assertEquals(IP::version('2400:cb00:2048:1:0:0:681c:804'), IP::V6); - $this->assertEquals(IP::version('2400:cb00:2048:1:0:0:681b:86bd'), IP::V6); - $this->assertEquals(IP::version('2400:cb00:2048:1:0:0:681f:5e2a'), IP::V6); - - $this->assertEquals(IP::version('not an ip address'), 0); - $this->assertEquals(IP::version('256.256.256.256'), 0); - } - - public function testUnpack() - { - $this->assertEquals(IP::unpack('127.0.0.1'), "\x7f\x00\x00\x01"); - $this->assertEquals(IP::unpack('104.27.135.189'), "\x68\x1b\x87\xbd"); - $this->assertEquals(IP::unpack('104.27.154.200'), "\x68\x1b\x9a\xc8"); - $this->assertEquals(IP::unpack('104.28.9.4'), "\x68\x1c\x09\x04"); - - $this->assertEquals(IP::unpack('::1'), "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"); - $this->assertEquals( - IP::unpack('2400:cb00:2048:1:0:0:681b:9ac8'), - "\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1b\x9a\xc8" - ); - $this->assertEquals( - IP::unpack('2400:cb00:2048:1:0:0:681c:804'), - "\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1c\x08\x04" - ); - $this->assertEquals( - IP::unpack('2400:cb00:2048:1:0:0:681b:86bd'), - "\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1b\x86\xbd" - ); - $this->assertEquals( - IP::unpack('2400:cb00:2048:1:0:0:681f:5e2a'), - "\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1f\x5e\x2a" - ); - } - - public function testPack() - { - $this->assertEquals(IP::pack("\x7f\x00\x00\x01"), '127.0.0.1'); - $this->assertEquals(IP::pack("\x68\x1b\x87\xbd"), '104.27.135.189'); - $this->assertEquals(IP::pack("\x68\x1b\x9a\xc8"), '104.27.154.200'); - $this->assertEquals(IP::pack("\x68\x1c\x09\x04"), '104.28.9.4'); - - $this->assertEquals(IP::pack("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), '::1'); - $this->assertEquals( - IP::pack("\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1b\x9a\xc8"), - '2400:cb00:2048:1::681b:9ac8' - ); - $this->assertEquals( - IP::pack("\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1c\x08\x04"), - '2400:cb00:2048:1::681c:804' - ); - $this->assertEquals( - IP::pack("\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1b\x86\xbd"), - '2400:cb00:2048:1::681b:86bd' - ); - $this->assertEquals( - IP::pack("\x24\x00\xcb\x00\x20\x48\x00\x01\x00\x00\x00\x00\x68\x1f\x5e\x2a"), - '2400:cb00:2048:1::681f:5e2a' - ); - } -}