Rework IP address handling.
This commit is contained in:
parent
fdff8e00d9
commit
19d19adec3
13 changed files with 452 additions and 348 deletions
|
@ -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();
|
||||
|
||||
|
|
107
src/Net/CIDR.php
107
src/Net/CIDR.php
|
@ -1,107 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* CIDR functions.
|
||||
* @package Misuzu\Net
|
||||
* @author flashwave <me@flash.moe>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
/**
|
||||
* IP functions.
|
||||
* @package Misuzu\Net
|
||||
* @author flashwave <me@flash.moe>
|
||||
*/
|
||||
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));
|
||||
}
|
||||
}
|
141
src/Net/IPAddress.php
Normal file
141
src/Net/IPAddress.php
Normal file
|
@ -0,0 +1,141 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* IP Address object.
|
||||
* @package Misuzu\Net
|
||||
* @author flashwave <me@flash.moe>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
121
src/Net/IPAddressRange.php
Normal file
121
src/Net/IPAddressRange.php
Normal file
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
final class IPAddressRange
|
||||
{
|
||||
private $maskAddress;
|
||||
private $cidrLength;
|
||||
|
||||
public function getMaskAddress(): IPAddress
|
||||
{
|
||||
return $this->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);
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
/**
|
||||
* Thrown when Net doesn't know how to handle the IP type.
|
||||
* @package Misuzu\Net
|
||||
* @author flashwave <me@flash.moe>
|
||||
*/
|
||||
class NetAddressTypeException extends NetException
|
||||
{
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
/**
|
||||
* Holds the base net exception.
|
||||
* @package Misuzu\Net
|
||||
* @author flashwave <me@flash.moe>
|
||||
*/
|
||||
class NetException extends \Exception
|
||||
{
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Net;
|
||||
|
||||
/**
|
||||
* Thrown when Net has to handle an invalid IP.
|
||||
* @package Misuzu\Net
|
||||
* @author flashwave <me@flash.moe>
|
||||
*/
|
||||
class NetInvalidAddressException extends NetException
|
||||
{
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
<?php
|
||||
namespace MisuzuTests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Misuzu\Net\CIDR;
|
||||
|
||||
class CIDRTest extends TestCase
|
||||
{
|
||||
public function testIPv4()
|
||||
{
|
||||
$this->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'));
|
||||
// }
|
||||
}
|
170
tests/IPAddressTest.php
Normal file
170
tests/IPAddressTest.php
Normal file
|
@ -0,0 +1,170 @@
|
|||
<?php
|
||||
namespace MisuzuTests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Net\IPAddressRange;
|
||||
|
||||
class IPAddressTest extends TestCase
|
||||
{
|
||||
public function testVersion()
|
||||
{
|
||||
$this->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')));
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
<?php
|
||||
namespace MisuzuTests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Misuzu\Net\IP;
|
||||
|
||||
class IPTest extends TestCase
|
||||
{
|
||||
public function testVersion()
|
||||
{
|
||||
$this->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'
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue