287 lines
5.3 KiB
PHP
287 lines
5.3 KiB
PHP
|
<?php
|
||
|
namespace IPTools;
|
||
|
|
||
|
/**
|
||
|
* @author Safarov Alisher <alisher.safarov@outlook.com>
|
||
|
* @link https://github.com/S1lentium/IPTools
|
||
|
*/
|
||
|
class IP
|
||
|
{
|
||
|
use PropertyTrait;
|
||
|
|
||
|
const IP_V4 = 'IPv4';
|
||
|
const IP_V6 = 'IPv6';
|
||
|
|
||
|
const IP_V4_MAX_PREFIX_LENGTH = 32;
|
||
|
const IP_V6_MAX_PREFIX_LENGTH = 128;
|
||
|
|
||
|
const IP_V4_OCTETS = 4;
|
||
|
const IP_V6_OCTETS = 16;
|
||
|
|
||
|
/**
|
||
|
* @var string
|
||
|
*/
|
||
|
private $in_addr;
|
||
|
|
||
|
/**
|
||
|
* @param string ip
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public function __construct($ip)
|
||
|
{
|
||
|
if (!filter_var($ip, FILTER_VALIDATE_IP)) {
|
||
|
throw new \Exception("Invalid IP address format");
|
||
|
}
|
||
|
$this->in_addr = inet_pton($ip);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string
|
||
|
*/
|
||
|
public function __toString()
|
||
|
{
|
||
|
return inet_ntop($this->in_addr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string ip
|
||
|
* @return IP
|
||
|
*/
|
||
|
public static function parse($ip)
|
||
|
{
|
||
|
if (strpos($ip, '0x') === 0) {
|
||
|
$ip = substr($ip, 2);
|
||
|
return self::parseHex($ip);
|
||
|
}
|
||
|
|
||
|
if (strpos($ip, '0b') === 0) {
|
||
|
$ip = substr($ip, 2);
|
||
|
return self::parseBin($ip);
|
||
|
}
|
||
|
|
||
|
if (is_numeric($ip)) {
|
||
|
return self::parseLong($ip);
|
||
|
}
|
||
|
|
||
|
return new self($ip);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $binIP
|
||
|
* @throws \Exception
|
||
|
* @return IP
|
||
|
*/
|
||
|
public static function parseBin($binIP)
|
||
|
{
|
||
|
if (!preg_match('/^([0-1]{32}|[0-1]{128})$/', $binIP)) {
|
||
|
throw new \Exception("Invalid binary IP address format");
|
||
|
}
|
||
|
|
||
|
$in_addr = '';
|
||
|
foreach (array_map('bindec', str_split($binIP, 8)) as $char) {
|
||
|
$in_addr .= pack('C*', $char);
|
||
|
}
|
||
|
|
||
|
return new self(inet_ntop($in_addr));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $hexIP
|
||
|
* @throws \Exception
|
||
|
* @return IP
|
||
|
*/
|
||
|
public static function parseHex($hexIP)
|
||
|
{
|
||
|
if (!preg_match('/^([0-9a-fA-F]{8}|[0-9a-fA-F]{32})$/', $hexIP)) {
|
||
|
throw new \Exception("Invalid hexadecimal IP address format");
|
||
|
}
|
||
|
|
||
|
return new self(inet_ntop(pack('H*', $hexIP)));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string|int $longIP
|
||
|
* @return IP
|
||
|
*/
|
||
|
public static function parseLong($longIP, $version=self::IP_V4)
|
||
|
{
|
||
|
if ($version === self::IP_V4) {
|
||
|
$ip = new self(long2ip($longIP));
|
||
|
} else {
|
||
|
$binary = array();
|
||
|
for ($i = 0; $i < self::IP_V6_OCTETS; $i++) {
|
||
|
$binary[] = bcmod($longIP, 256);
|
||
|
$longIP = bcdiv($longIP, 256, 0);
|
||
|
}
|
||
|
$ip = new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), array_reverse($binary)))));
|
||
|
}
|
||
|
|
||
|
return $ip;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $inAddr
|
||
|
* @return IP
|
||
|
*/
|
||
|
public static function parseInAddr($inAddr)
|
||
|
{
|
||
|
return new self(inet_ntop($inAddr));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getVersion()
|
||
|
{
|
||
|
$version = '';
|
||
|
|
||
|
if (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||
|
$version = self::IP_V4;
|
||
|
} elseif (filter_var(inet_ntop($this->in_addr), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||
|
$version = self::IP_V6;
|
||
|
}
|
||
|
|
||
|
return $version;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return int
|
||
|
*/
|
||
|
public function getMaxPrefixLength()
|
||
|
{
|
||
|
return $this->getVersion() === self::IP_V4
|
||
|
? self::IP_V4_MAX_PREFIX_LENGTH
|
||
|
: self::IP_V6_MAX_PREFIX_LENGTH;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return int
|
||
|
*/
|
||
|
public function getOctetsCount()
|
||
|
{
|
||
|
return $this->getVersion() === self::IP_V4
|
||
|
? self::IP_V4_OCTETS
|
||
|
: self::IP_V6_OCTETS;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getReversePointer()
|
||
|
{
|
||
|
if ($this->getVersion() === self::IP_V4) {
|
||
|
$reverseOctets = array_reverse(explode('.', $this->__toString()));
|
||
|
$reversePointer = implode('.', $reverseOctets) . '.in-addr.arpa';
|
||
|
} else {
|
||
|
$unpacked = unpack('H*hex', $this->in_addr);
|
||
|
$reverseOctets = array_reverse(str_split($unpacked['hex']));
|
||
|
$reversePointer = implode('.', $reverseOctets) . '.ip6.arpa';
|
||
|
}
|
||
|
|
||
|
return $reversePointer;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string
|
||
|
*/
|
||
|
public function inAddr()
|
||
|
{
|
||
|
return $this->in_addr;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string
|
||
|
*/
|
||
|
public function toBin()
|
||
|
{
|
||
|
$binary = array();
|
||
|
foreach (unpack('C*', $this->in_addr) as $char) {
|
||
|
$binary[] = str_pad(decbin($char), 8, '0', STR_PAD_LEFT);
|
||
|
}
|
||
|
|
||
|
return implode($binary);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string
|
||
|
*/
|
||
|
public function toHex()
|
||
|
{
|
||
|
return bin2hex($this->in_addr);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string
|
||
|
*/
|
||
|
public function toLong()
|
||
|
{
|
||
|
$long = 0;
|
||
|
if ($this->getVersion() === self::IP_V4) {
|
||
|
$long = sprintf('%u', ip2long(inet_ntop($this->in_addr)));
|
||
|
} else {
|
||
|
$octet = self::IP_V6_OCTETS - 1;
|
||
|
foreach ($chars = unpack('C*', $this->in_addr) as $char) {
|
||
|
$long = bcadd($long, bcmul($char, bcpow(256, $octet--)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $long;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param int $to
|
||
|
* @return IP
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public function next($to=1)
|
||
|
{
|
||
|
if ($to < 0) {
|
||
|
throw new \Exception("Number must be greater than 0");
|
||
|
}
|
||
|
|
||
|
$unpacked = unpack('C*', $this->in_addr);
|
||
|
|
||
|
for ($i = 0; $i < $to; $i++) {
|
||
|
for ($byte = count($unpacked); $byte >= 0; --$byte) {
|
||
|
if ($unpacked[$byte] < 255) {
|
||
|
$unpacked[$byte]++;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
$unpacked[$byte] = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), $unpacked))));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param int $to
|
||
|
* @return IP
|
||
|
* @throws \Exception
|
||
|
*/
|
||
|
public function prev($to=1)
|
||
|
{
|
||
|
|
||
|
if ($to < 0) {
|
||
|
throw new \Exception("Number must be greater than 0");
|
||
|
}
|
||
|
|
||
|
$unpacked = unpack('C*', $this->in_addr);
|
||
|
|
||
|
for ($i = 0; $i < $to; $i++) {
|
||
|
for ($byte = count($unpacked); $byte >= 0; --$byte) {
|
||
|
if ($unpacked[$byte] === 0) {
|
||
|
$unpacked[$byte] = 255;
|
||
|
} else {
|
||
|
$unpacked[$byte]--;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return new self(inet_ntop(call_user_func_array('pack', array_merge(array('C*'), $unpacked))));
|
||
|
}
|
||
|
|
||
|
}
|