init commit

This commit is contained in:
2025-02-23 20:52:25 +01:00
parent 5b272d6536
commit 9971cd719b
1719 changed files with 281982 additions and 2 deletions

View File

@ -0,0 +1,4 @@
languages:
PHP: true
exclude_paths:
- "tests/*"

View File

@ -0,0 +1,32 @@
# phpstorm project files
.idea
# netbeans project files
nbproject
# zend studio for eclipse project files
.buildpath
.project
.settings
# windows thumbnail cache
Thumbs.db
# Mac DS_Store Files
.DS_Store
# composer vendor dir
/vendor
# phpunit itself is not needed
phpunit.phar
# local phpunit config
phpunit.xml
phpunit.xml.dist
# coverage report
/coverage
composer.phar
composer.lock

View File

@ -0,0 +1,17 @@
language: php
php:
- 5.4
- 5.5
- 5.6
- 7.0
before_script:
- wget http://getcomposer.org/composer.phar
- php composer.phar install --dev --no-interaction
script:
- mkdir -p build/logs
- phpunit --coverage-clover build/logs/clover.xml
after_script:
- php vendor/bin/coveralls

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 Safarov Alisher
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,206 @@
# IPTools
PHP Library for manipulating network addresses (IPv4 and IPv6).
[![Build Status](https://travis-ci.org/S1lentium/IPTools.svg)](https://travis-ci.org/S1lentium/IPTools)
[![Coverage Status](https://coveralls.io/repos/S1lentium/IPTools/badge.svg?branch=master&service=github)](https://coveralls.io/github/S1lentium/IPTools?branch=master)
[![Code Climate](https://codeclimate.com/github/S1lentium/IPTools/badges/gpa.svg)](https://codeclimate.com/github/S1lentium/IPTools)
[![PHP 5.4](https://img.shields.io/badge/PHP-5.4-8892BF.svg)](http://php.net)
[![PHP 7.0](https://img.shields.io/badge/PHP-7.0-8892BF.svg)](http://php.net)
## Installation
Composer:
Run in command line:
```
composer require s1lentium/iptools
```
or put in composer.json:
```json
{
"require": {
"s1lentium/iptools": "*"
}
}
```
## Usage
### IP Operations
```php
$ip = new IP('192.168.1.1');
echo $ip->version;// IPv4
```
```php
$ip = new IP('fc00::');
echo $ip->version; // IPv6
```
**Parsing IP from integer, binary and hex:**
```php
echo (string)IP::parse(2130706433); // 127.0.0.1
echo (string)IP::parse('0b11000000101010000000000100000001') // 192.168.1.1
echo (string)IP::parse('0x0a000001'); // 10.0.0.1
```
or:
```php
echo (string)IP::parseLong(2130706433); // 127.0.0.1
echo (string)IP::parseBin('11000000101010000000000100000001'); // 192.168.1.1
echo (string)IP::parseHex('0a000001'); // 10.0.0.1
```
**Converting IP to other formats:**
```php
echo IP::parse('192.168.1.1')->bin // 11000000101010000000000100000001
echo IP::parse('10.0.0.1')->hex // 0a000001
echo IP::parse('127.0.0.1')->long // 2130706433
```
#### Other public properties:
`maxPrefixLength`
The max number of bits in the address representation: 32 for IPv4, 128 for IPv6.
`octetsCount`
The count of octets in IP address: 4 for IPv4, 16 for IPv6
`reversePointer`
The name of the reverse DNS PTR for the address:
```php
echo new IP::parse('192.0.2.5')->reversePointer // 5.2.0.192.in-addr.arpa
echo new IP::parse('2001:db8::567:89ab')->reversePointer // b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa
```
### Network Operations
```php
echo Network::parse('192.0.0.1 255.0.0.0')->CIDR; // 192.0.0.0/8
echo (string)Network::parse('192.0.0.1/8')->netmask; // 255.0.0.0
echo (string)Network::parse('192.0.0.1'); // 192.0.0.1/32
```
**Exclude IP from Network:**
```php
$excluded = Network::parse('192.0.0.0/8')->exclude(new IP('192.168.1.1'));
foreach($excluded as $network) {
echo (string)$network . '<br>';
}
```
192.0.0.0/9
192.128.0.0/11
192.160.0.0/13
192.168.0.0/24
192.168.1.0/32
192.168.1.2/31
...
192.192.0.0/10
**Exclude Subnet from Network:**
```php
$excluded = Network::parse('192.0.0.0/8')->exclude(new Network('192.168.1.0/24'));
foreach($excluded as $network) {
echo (string)$network . '<br>';
}
```
192.0.0.0/9
192.128.0.0/11
192.160.0.0/13
192.168.0.0/24
192.168.2.0/23
...
192.192.0.0/10
**Split network into equal subnets**
```php
$networks = Network::parse('192.168.0.0/22')->moveTo('24');
foreach ($networks as $network) {
echo (string)$network . '<br>';
}
```
192.168.0.0/24
192.168.1.0/24
192.168.2.0/24
192.168.3.0/24
**Iterate over Network IP adresses:**
```php
$network = Network::parse('192.168.1.0/24');
foreach($network as $ip) {
echo (string)$ip . '<br>';
}
```
192.168.1.0
...
192.168.1.255
**Get Network hosts adresses as Range:**
```php
$hosts = Network::parse('192.168.1.0/24')->hosts // Range(192.168.1.1, 192.168.1.254);
foreach($hosts as $ip) {
echo (string)$ip . '<br>';
}
```
192.168.1.1
...
192.168.1.254
**Count Network IP adresses**
```php
echo count(Network::parse('192.168.1.0/24')) // 254
```
### Range Operations
**Define the range in different formats:**
```php
$range = new Range(new IP('192.168.1.0'), new IP('192.168.1.255'));
$range = Range::parse('192.168.1.0-192.168.1.255');
$range = Range::parse('192.168.1.*');
$range = Range::parse('192.168.1.0/24');
```
**Check if IP is within Range:**
```php
echo Range::parse('192.168.1.1-192.168.1.254')->contains(new IP('192.168.1.5')); // true
echo Range::parse('::1-::ffff')->contains(new IP('::1234')); // true
```
**Iterate over Range IP adresses:**
```php
$range = Range::parse('192.168.1.1-192.168.1.254');
foreach($range as $ip) {
echo (string)$ip . '<br>';
}
```
192.168.1.1
...
192.168.1.254
**Get Networks that fit into a specified range of IP Adresses:**
```php
$networks = Range::parse('192.168.1.1-192.168.1.254')->getNetworks();
foreach($networks as $network) {
echo (string)$network . '<br>';
}
```
192.168.1.1/32
192.168.1.2/31
192.168.1.4/30
192.168.1.8/29
192.168.1.16/28
192.168.1.32/27
192.168.1.64/26
192.168.1.128/26
192.168.1.192/27
192.168.1.224/28
192.168.1.240/29
192.168.1.248/30
192.168.1.252/31
192.168.1.254/32
**Count IP adresses in Range**
```php
echo count(Range::parse('192.168.1.1-192.168.1.254')) // 254
```
# License
The library is released under the [MIT](https://opensource.org/licenses/MIT).

View File

@ -0,0 +1,25 @@
{
"name": "s1lentium/iptools",
"type": "library",
"description": "PHP Library for manipulating network addresses (IPv4 and IPv6)",
"keywords": ["IP-Tools", "network", "subnet", "cidr", "IP", "IPv4", "IPv6"],
"license": "MIT",
"authors": [{
"name": "Safarov Alisher",
"email": "alisher.safarov@outlook.com",
"homepage": "https://github.com/S1lentium"
}],
"require": {
"php": ">=5.4.0",
"ext-bcmath": "*"
},
"require-dev": {
"satooshi/php-coveralls": "~1.0",
"phpunit/phpunit": "~4.0"
},
"autoload": {
"psr-4": {
"IPTools\\": "src/"
}
}
}

View File

@ -0,0 +1,286 @@
<?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))));
}
}

View File

@ -0,0 +1,365 @@
<?php
namespace IPTools;
/**
* @author Safarov Alisher <alisher.safarov@outlook.com>
* @link https://github.com/S1lentium/IPTools
*/
class Network implements \Iterator, \Countable
{
use PropertyTrait;
/**
* @var IP
*/
private $ip;
/**
* @var IP
*/
private $netmask;
/**
* @var int
*/
private $position = 0;
/**
* @param IP $ip
* @param IP $netmask
*/
public function __construct(IP $ip, IP $netmask)
{
$this->setIP($ip);
$this->setNetmask($netmask);
}
/**
*
* @return string
*/
public function __toString()
{
return $this->getCIDR();
}
/**
* @param string $data
* @return Network
*/
public static function parse($data)
{
if (preg_match('~^(.+?)/(\d+)$~', $data, $matches)) {
$ip = IP::parse($matches[1]);
$netmask = self::prefix2netmask((int)$matches[2], $ip->getVersion());
} elseif (strpos($data,' ')) {
list($ip, $netmask) = explode(' ', $data, 2);
$ip = IP::parse($ip);
$netmask = IP::parse($netmask);
} else {
$ip = IP::parse($data);
$netmask = self::prefix2netmask($ip->getMaxPrefixLength(), $ip->getVersion());
}
return new self($ip, $netmask);
}
/**
* @param int $prefixLength
* @param string $version
* @return IP
* @throws \Exception
*/
public static function prefix2netmask($prefixLength, $version)
{
if (!in_array($version, array(IP::IP_V4, IP::IP_V6))) {
throw new \Exception("Wrong IP version");
}
$maxPrefixLength = $version === IP::IP_V4
? IP::IP_V4_MAX_PREFIX_LENGTH
: IP::IP_V6_MAX_PREFIX_LENGTH;
if (!is_numeric($prefixLength)
|| !($prefixLength >= 0 && $prefixLength <= $maxPrefixLength)
) {
throw new \Exception('Invalid prefix length');
}
$binIP = str_pad(str_pad('', (int)$prefixLength, '1'), $maxPrefixLength, '0');
return IP::parseBin($binIP);
}
/**
* @param IP ip
* @return int
*/
public static function netmask2prefix(IP $ip)
{
return strlen(rtrim($ip->toBin(), 0));
}
/**
* @param IP ip
* @throws \Exception
*/
public function setIP(IP $ip)
{
if (isset($this->netmask) && $this->netmask->getVersion() !== $ip->getVersion()) {
throw new \Exception('IP version is not same as Netmask version');
}
$this->ip = $ip;
}
/**
* @param IP ip
* @throws \Exception
*/
public function setNetmask(IP $ip)
{
if (!preg_match('/^1*0*$/',$ip->toBin())) {
throw new \Exception('Invalid Netmask address format');
}
if (isset($this->ip) && $ip->getVersion() !== $this->ip->getVersion()) {
throw new \Exception('Netmask version is not same as IP version');
}
$this->netmask = $ip;
}
/**
* @param int $prefixLength
*/
public function setPrefixLength($prefixLength)
{
$this->setNetmask(self::prefix2netmask((int)$prefixLength, $this->ip->getVersion()));
}
/**
* @return IP
*/
public function getIP()
{
return $this->ip;
}
/**
* @return IP
*/
public function getNetmask()
{
return $this->netmask;
}
/**
* @return IP
*/
public function getNetwork()
{
return new IP(inet_ntop($this->getIP()->inAddr() & $this->getNetmask()->inAddr()));
}
/**
* @return int
*/
public function getPrefixLength()
{
return self::netmask2prefix($this->getNetmask());
}
/**
* @return string
*/
public function getCIDR()
{
return sprintf('%s/%s', $this->getNetwork(), $this->getPrefixLength());
}
/**
* @return IP
*/
public function getWildcard()
{
return new IP(inet_ntop(~$this->getNetmask()->inAddr()));
}
/**
* @return IP
*/
public function getBroadcast()
{
return new IP(inet_ntop($this->getNetwork()->inAddr() | ~$this->getNetmask()->inAddr()));
}
/**
* @return IP
*/
public function getFirstIP()
{
return $this->getNetwork();
}
/**
* @return IP
*/
public function getLastIP()
{
return $this->getBroadcast();
}
/**
* @return int|string
*/
public function getBlockSize()
{
$maxPrefixLength = $this->ip->getMaxPrefixLength();
$prefixLength = $this->getPrefixLength();
if ($this->ip->getVersion() === IP::IP_V6) {
return bcpow('2', (string)($maxPrefixLength - $prefixLength));
}
return pow(2, $maxPrefixLength - $prefixLength);
}
/**
* @return Range
*/
public function getHosts()
{
$firstHost = $this->getNetwork();
$lastHost = $this->getBroadcast();
if ($this->ip->getVersion() === IP::IP_V4) {
if ($this->getBlockSize() > 2) {
$firstHost = IP::parseBin(substr($firstHost->toBin(), 0, $firstHost->getMaxPrefixLength() - 1) . '1');
$lastHost = IP::parseBin(substr($lastHost->toBin(), 0, $lastHost->getMaxPrefixLength() - 1) . '0');
}
}
return new Range($firstHost, $lastHost);
}
/**
* @param IP|Network $exclude
* @return Network[]
* @throws \Exception
*/
public function exclude($exclude)
{
$exclude = self::parse($exclude);
if (strcmp($exclude->getFirstIP()->inAddr() , $this->getLastIP()->inAddr()) > 0
|| strcmp($exclude->getLastIP()->inAddr() , $this->getFirstIP()->inAddr()) < 0
) {
throw new \Exception('Exclude subnet not within target network');
}
$networks = array();
$newPrefixLength = $this->getPrefixLength() + 1;
if ($newPrefixLength > $this->ip->getMaxPrefixLength()) {
return $networks;
}
$lower = clone $this;
$lower->setPrefixLength($newPrefixLength);
$upper = clone $lower;
$upper->setIP($lower->getLastIP()->next());
while ($newPrefixLength <= $exclude->getPrefixLength()) {
$range = new Range($lower->getFirstIP(), $lower->getLastIP());
if ($range->contains($exclude)) {
$matched = $lower;
$unmatched = $upper;
} else {
$matched = $upper;
$unmatched = $lower;
}
$networks[] = clone $unmatched;
if (++$newPrefixLength > $this->getNetwork()->getMaxPrefixLength()) break;
$matched->setPrefixLength($newPrefixLength);
$unmatched->setPrefixLength($newPrefixLength);
$unmatched->setIP($matched->getLastIP()->next());
}
sort($networks);
return $networks;
}
/**
* @param int $prefixLength
* @return Network[]
* @throws \Exception
*/
public function moveTo($prefixLength)
{
$maxPrefixLength = $this->ip->getMaxPrefixLength();
if ($prefixLength <= $this->getPrefixLength() || $prefixLength > $maxPrefixLength) {
throw new \Exception('Invalid prefix length ');
}
$netmask = self::prefix2netmask($prefixLength, $this->ip->getVersion());
$networks = array();
$subnet = clone $this;
$subnet->setPrefixLength($prefixLength);
while ($subnet->ip->inAddr() <= $this->getLastIP()->inAddr()) {
$networks[] = $subnet;
$subnet = new self($subnet->getLastIP()->next(), $netmask);
}
return $networks;
}
/**
* @return IP
*/
public function current()
{
return $this->getFirstIP()->next($this->position);
}
/**
* @return int
*/
public function key()
{
return $this->position;
}
public function next()
{
++$this->position;
}
public function rewind()
{
$this->position = 0;
}
/**
* @return bool
*/
public function valid()
{
return strcmp($this->getFirstIP()->next($this->position)->inAddr(), $this->getLastIP()->inAddr()) <= 0;
}
/**
* @return int
*/
public function count()
{
return (integer)$this->getBlockSize();
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace IPTools;
/**
* @author Safarov Alisher <alisher.safarov@outlook.com>
* @link https://github.com/S1lentium/IPTools
*/
trait PropertyTrait
{
/**
* @param string $name
* @return mixed
*/
public function __get($name)
{
if(method_exists($this, $name)) {
return $this->$name();
}
foreach (array('get', 'to') as $prefix) {
$method = $prefix . ucfirst($name);
if(method_exists($this, $method)) {
return $this->$method();
}
}
trigger_error('Undefined property');
return null;
}
/**
* @param string $name
* @param mixed $value
*/
public function __set($name, $value)
{
$method = 'set'. ucfirst($name);
if (!method_exists($this, $method)) {
trigger_error('Undefined property');
return;
}
$this->$method($value);
}
}

View File

@ -0,0 +1,228 @@
<?php
namespace IPTools;
/**
* @author Safarov Alisher <alisher.safarov@outlook.com>
* @link https://github.com/S1lentium/IPTools
*/
class Range implements \Iterator, \Countable
{
use PropertyTrait;
/**
* @var IP
*/
private $firstIP;
/**
* @var IP
*/
private $lastIP;
/**
* @var int
*/
private $position = 0;
/**
* @param IP $firstIP
* @param IP $lastIP
* @throws \Exception
*/
public function __construct(IP $firstIP, IP $lastIP)
{
$this->setFirstIP($firstIP);
$this->setLastIP($lastIP);
}
/**
* @param string $data
* @return Range
*/
public static function parse($data)
{
if (strpos($data,'/') || strpos($data,' ')) {
$network = Network::parse($data);
$firstIP = $network->getFirstIP();
$lastIP = $network->getLastIP();
} elseif (strpos($data, '*') !== false) {
$firstIP = IP::parse(str_replace('*', '0', $data));
$lastIP = IP::parse(str_replace('*', '255', $data));
} elseif (strpos($data, '-')) {
list($first, $last) = explode('-', $data, 2);
$firstIP = IP::parse($first);
$lastIP = IP::parse($last);
} else {
$firstIP = IP::parse($data);
$lastIP = clone $firstIP;
}
return new self($firstIP, $lastIP);
}
/**
* @param IP|Network|Range $find
* @return bool
* @throws \Exception
*/
public function contains($find)
{
if ($find instanceof IP) {
$within = (strcmp($find->inAddr(), $this->firstIP->inAddr()) >= 0)
&& (strcmp($find->inAddr(), $this->lastIP->inAddr()) <= 0);
} elseif ($find instanceof Range || $find instanceof Network) {
/**
* @var Network|Range $find
*/
$within = (strcmp($find->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0)
&& (strcmp($find->getLastIP()->inAddr(), $this->lastIP->inAddr()) <= 0);
} else {
throw new \Exception('Invalid type');
}
return $within;
}
/**
* @param IP $ip
* @throws \Exception
*/
public function setFirstIP(IP $ip)
{
if ($this->lastIP && strcmp($ip->inAddr(), $this->lastIP->inAddr()) > 0) {
throw new \Exception('First IP is grater than second');
}
$this->firstIP = $ip;
}
/**
* @param IP $ip
* @throws \Exception
*/
public function setLastIP(IP $ip)
{
if ($this->firstIP && strcmp($ip->inAddr(), $this->firstIP->inAddr()) < 0) {
throw new \Exception('Last IP is less than first');
}
$this->lastIP = $ip;
}
/**
* @return IP
*/
public function getFirstIP()
{
return $this->firstIP;
}
/**
* @return IP
*/
public function getLastIP()
{
return $this->lastIP;
}
/**
* @return Network[]
*/
public function getNetworks()
{
$span = $this->getSpanNetwork();
$networks = array();
if ($span->getFirstIP()->inAddr() === $this->firstIP->inAddr()
&& $span->getLastIP()->inAddr() === $this->lastIP->inAddr()
) {
$networks = array($span);
} else {
if ($span->getFirstIP()->inAddr() !== $this->firstIP->inAddr()) {
$excluded = $span->exclude($this->firstIP->prev());
foreach ($excluded as $network) {
if (strcmp($network->getFirstIP()->inAddr(), $this->firstIP->inAddr()) >= 0) {
$networks[] = $network;
}
}
}
if ($span->getLastIP()->inAddr() !== $this->lastIP->inAddr()) {
if (!$networks) {
$excluded = $span->exclude($this->lastIP->next());
} else {
$excluded = array_pop($networks);
$excluded = $excluded->exclude($this->lastIP->next());
}
foreach ($excluded as $network) {
$networks[] = $network;
if ($network->getLastIP()->inAddr() === $this->lastIP->inAddr()) {
break;
}
}
}
}
return $networks;
}
/**
* @return Network
*/
public function getSpanNetwork()
{
$xorIP = IP::parseInAddr($this->getFirstIP()->inAddr() ^ $this->getLastIP()->inAddr());
preg_match('/^(0*)/', $xorIP->toBin(), $match);
$prefixLength = strlen($match[1]);
$ip = IP::parseBin(str_pad(substr($this->getFirstIP()->toBin(), 0, $prefixLength), $xorIP->getMaxPrefixLength(), '0'));
return new Network($ip, Network::prefix2netmask($prefixLength, $ip->getVersion()));
}
/**
* @return IP
*/
public function current()
{
return $this->firstIP->next($this->position);
}
/**
* @return int
*/
public function key()
{
return $this->position;
}
public function next()
{
++$this->position;
}
public function rewind()
{
$this->position = 0;
}
/**
* @return bool
*/
public function valid()
{
return strcmp($this->firstIP->next($this->position)->inAddr(), $this->lastIP->inAddr()) <= 0;
}
/**
* @return int
*/
public function count()
{
return (integer)bcadd(bcsub($this->lastIP->toLong(), $this->firstIP->toLong()), 1);
}
}

View File

@ -0,0 +1,255 @@
<?php
use IPTools\IP;
class IPTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$ipv4String = '127.0.0.1';
$ipv6String = '2001::';
$ipv4 = new IP($ipv4String);
$ipv6 = new IP($ipv6String);
$this->assertEquals(inet_pton($ipv4String), $ipv4->inAddr());
$this->assertEquals(IP::IP_V4, $ipv4->getVersion());
$this->assertEquals(IP::IP_V4_MAX_PREFIX_LENGTH, $ipv4->getMaxPrefixLength());
$this->assertEquals(IP::IP_V4_OCTETS, $ipv4->getOctetsCount());
$this->assertEquals(inet_pton($ipv6String), $ipv6->inAddr());
$this->assertEquals(IP::IP_V6, $ipv6->getVersion());
$this->assertEquals(IP::IP_V6_MAX_PREFIX_LENGTH, $ipv6->getMaxPrefixLength());
$this->assertEquals(IP::IP_V6_OCTETS, $ipv6->getOctetsCount());
}
/**
* @dataProvider getTestContructorExceptionData
* @expectedException Exception
* @expectedExceptionMessage Invalid IP address format
*/
public function testConstructorException($string)
{
$ip = new IP($string);
}
public function testProperties()
{
$ip = new IP('127.0.0.1');
$this->assertNotEmpty($ip->maxPrefixLength);
$this->assertNotEmpty($ip->octetsCount);
$this->assertNotEmpty($ip->reversePointer);
$this->assertNotEmpty($ip->bin);
$this->assertNotEmpty($ip->long);
$this->assertNotEmpty($ip->hex);
}
/**
* @dataProvider getToStringData
*/
public function testToString($actual, $expected)
{
$ip = new IP($actual);
$this->assertEquals($expected, (string)$ip);
}
/**
* @dataProvider getTestParseData
*/
public function testParse($ipString, $expected)
{
$ip = IP::parse($ipString);
$this->assertEquals($expected, (string) $ip);
}
/**
* @dataProvider getParseBinData
*/
public function testParseBin($bin, $expectedString)
{
$ip = IP::parseBin($bin);
$this->assertEquals($expectedString, (string) $ip);
$this->assertEquals($bin, $ip->toBin());
}
/**
* @expectedException Exception
* @expectedExceptionMessage Invalid binary IP address format
*/
public function testParseBinException()
{
IP::parseBin('192.168.1.1');
}
public function testParseLong()
{
$ipv4long = '2130706433';
$ipv4 = IP::parseLong($ipv4long);
$ipv6Long = '340277174624079928635746076935438991360';
$ipv6 = IP::parseLong($ipv6Long, IP::IP_V6);
$this->assertEquals('127.0.0.1', (string)$ipv4);
$this->assertEquals($ipv4long, $ipv4->toLong());
$this->assertEquals('ffff::', (string)$ipv6);
$this->assertEquals($ipv6Long, $ipv6->toLong());
}
public function testParseHex()
{
$hex = '7f000001';
$ip = IP::parseHex($hex);
$this->assertEquals('127.0.0.1', (string)$ip);
$this->assertEquals($hex, $ip->toHex());
}
/**
* @expectedException Exception
* @expectedExceptionMessage Invalid hexadecimal IP address format
*/
public function testParseHexException()
{
IP::parseHex('192.168.1.1');
}
public function testParseInAddr()
{
$inAddr = inet_pton('127.0.0.1');
$ip = IP::parseInAddr($inAddr);
$this->assertEquals($inAddr, $ip->inAddr());
$inAddr = inet_pton('2001::8000:0:0:0');
$ip = IP::parseInAddr($inAddr);
$this->assertEquals($inAddr, $ip->inAddr());
}
/**
* @dataProvider getTestNextData
*/
public function testNext($ip, $step, $expected)
{
$object = new IP($ip);
$next = $object->next($step);
$this->assertEquals($expected, (string) $next);
}
/**
* @dataProvider getTestPrevData
*/
public function testPrev($ip, $step, $expected)
{
$object = new IP($ip);
$prev = $object->prev($step);
$this->assertEquals($expected, (string) $prev);
}
/**
* @expectedException Exception
* @expectedExceptionMessage Number must be greater than 0
*/
public function testPrevException()
{
$object = new IP('192.168.1.1');
$object->prev(-1);
}
/**
* @dataProvider getReversePointerData
*/
public function testReversePointer($ip, $expected)
{
$object = new IP($ip);
$reversePointer = $object->getReversePointer();
$this->assertEquals($expected, $reversePointer);
}
public function getTestContructorExceptionData()
{
return array(
array('256.0.0.1'),
array('127.-1.0.1'),
array(123.45),
array(-123.45),
array('cake'),
array('12345'),
array('-12345'),
array('0000:0000:0000:ffff:0127:0000:0000:0001:0000'),
);
}
public function getToStringData()
{
return array(
array('127.0.0.1', '127.0.0.1'),
array('2001::', '2001::'),
array('2001:0000:0000:0000:0000:0000:0000:0000', '2001::'),
array('2001:0000:0000:0000:8000:0000:0000:0000', '2001::8000:0:0:0')
);
}
public function getTestParseData()
{
return array(
array(2130706433, '127.0.0.1'), //long
array('0b01111111000000000000000000000001', '127.0.0.1'), //bin
array('0x7f000001', '127.0.0.1'), //hex,
array('0x20010000000000008000000000000000', '2001::8000:0:0:0'), //hex
array('127.0.0.1', '127.0.0.1'),
array('2001::', '2001::')
);
}
public function getParseBinData()
{
return array(
array(
'00100000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
'2001::'
),
array('01111111000000000000000000000001', '127.0.0.1')
);
}
public function getTestNextData()
{
return array(
array('192.168.0.1', 1, '192.168.0.2'),
array('192.168.0.1', 254, '192.168.0.255'),
array('192.168.0.1', 255, '192.168.1.0'),
array('2001::', 1, '2001::1'),
array('2001::', 65535, '2001::ffff'),
array('2001::', 65536, '2001::1:0')
);
}
public function getTestPrevData()
{
return array(
array('192.168.1.1', 1, '192.168.1.0'),
array('192.168.1.0', 1, '192.168.0.255'),
array('192.168.1.1', 258, '192.167.255.255'),
array('2001::1', 1, '2001::'),
array('2001::1:0', 1, '2001::ffff'),
array('2001::1:0', 65536, '2001::'),
);
}
public function getReversePointerData()
{
return array(
array('192.0.2.5', '5.2.0.192.in-addr.arpa'),
array('2001:db8::567:89ab', 'b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa'),
);
}
}

View File

@ -0,0 +1,305 @@
<?php
use IPTools\Network;
use IPTools\IP;
class NetworkTest extends \PHPUnit_Framework_TestCase
{
public function testConstructor()
{
$ipv4 = new IP('127.0.0.1');
$ipv4Netmask = new IP('255.255.255.0');
$ipv6 = new IP('2001::');
$ipv6Netmask = new IP('ffff:ffff:ffff:ffff:ffff:ffff:ffff::');
$ipv4Network = new Network($ipv4, $ipv4Netmask);
$ipv6Network = new Network($ipv6, $ipv6Netmask);
$this->assertEquals('127.0.0.0/24', (string)$ipv4Network);
$this->assertEquals('2001::/112', (string)$ipv6Network);
}
public function testProperties()
{
$network = Network::parse('127.0.0.1/24');
$network->ip = new IP('192.0.0.2');
$this->assertEquals('192.0.0.2', $network->ip);
$this->assertEquals('192.0.0.0/24', (string)$network);
$this->assertEquals('0.0.0.255', (string)$network->wildcard);
$this->assertEquals('192.0.0.0', (string)$network->firstIP);
$this->assertEquals('192.0.0.255', (string)$network->lastIP);
}
/**
* @dataProvider getTestParseData
*/
public function testParse($data, $expected)
{
$this->assertEquals($expected, (string)Network::parse($data));
}
/**
* @expectedException Exception
* @expectedExceptionMessage Invalid IP address format
*/
public function testParseWrongNetwork()
{
Network::parse('10.0.0.0/24 abc');
}
/**
* @dataProvider getPrefixData
*/
public function testPrefix2Mask($prefix, $version, $mask)
{
$this->assertEquals($mask, Network::prefix2netmask($prefix, $version));
}
/**
* @expectedException Exception
* @expectedExceptionMessage Wrong IP version
*/
public function testPrefix2MaskWrongIPVersion()
{
Network::prefix2netmask('128', 'ip_version');
}
/**
* @dataProvider getInvalidPrefixData
* @expectedException Exception
* @expectedExceptionMessage Invalid prefix length
*/
public function testPrefix2MaskInvalidPrefix($prefix, $version)
{
Network::prefix2netmask($prefix, $version);
}
/**
* @dataProvider getHostsData
*/
public function testHosts($data, $expected)
{
foreach(Network::parse($data)->getHosts as $ip) {
$result[] = (string)$ip;
}
$this->assertEquals($expected, $result);
}
/**
* @dataProvider getExcludeData
*/
public function testExclude($data, $exclude, $expected)
{
$result = array();
foreach(Network::parse($data)->exclude($exclude) as $network) {
$result[] = (string)$network;
}
$this->assertEquals($expected, $result);
}
/**
* @dataProvider getExcludeExceptionData
* @expectedException Exception
* @expectedExceptionMessage Exclude subnet not within target network
*/
public function testExcludeException($data, $exclude)
{
Network::parse($data)->exclude($exclude);
}
/**
* @dataProvider getMoveToData
*/
public function testMoveTo($network, $prefixLength, $expected)
{
$result = array();
foreach (Network::parse($network)->moveTo($prefixLength) as $network) {
$result[] = (string)$network;
}
$this->assertEquals($expected, $result);
}
/**
* @dataProvider getMoveToExceptionData
* @expectedException Exception
* @expectedExceptionMessage Invalid prefix length
*/
public function testMoveToException($network, $prefixLength)
{
Network::parse($network)->moveTo($prefixLength);
}
/**
* @dataProvider getTestIterationData
*/
public function testNetworkIteration($data, $expected)
{
foreach (Network::parse($data) as $key => $ip) {
$result[] = (string)$ip;
}
$this->assertEquals($expected, $result);
}
/**
* @dataProvider getTestCountData
*/
public function testCount($data, $expected)
{
$this->assertEquals($expected, count(Network::parse($data)));
}
public function getTestParseData()
{
return array(
array('192.168.0.54/24', '192.168.0.0/24'),
array('2001::2001:2001/32', '2001::/32'),
array('127.168.0.1 255.255.255.255', '127.168.0.1/32'),
array('1234::1234', '1234::1234/128'),
);
}
public function getPrefixData()
{
return array(
array('24', IP::IP_V4, IP::parse('255.255.255.0')),
array('32', IP::IP_V4, IP::parse('255.255.255.255')),
array('64', IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff::')),
array('128', IP::IP_V6, IP::parse('ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'))
);
}
public function getInvalidPrefixData()
{
return array(
array('-1', IP::IP_V4),
array('33', IP::IP_V4),
array('prefix', IP::IP_V4),
array('-1', IP::IP_V6),
array('129', IP::IP_V6),
);
}
public function getHostsData()
{
return array(
array('192.0.2.0/29',
array(
'192.0.2.1',
'192.0.2.2',
'192.0.2.3',
'192.0.2.4',
'192.0.2.5',
'192.0.2.6',
)
),
);
}
public function getExcludeData()
{
return array(
array('192.0.2.0/28', '192.0.2.1/32',
array(
'192.0.2.0/32',
'192.0.2.2/31',
'192.0.2.4/30',
'192.0.2.8/29',
)
),
array('192.0.2.2/32', '192.0.2.2/32', array()),
);
}
public function getExcludeExceptionData()
{
return array(
array('192.0.2.0/28', '192.0.3.0/24'),
array('192.0.2.2/32', '192.0.2.3/32'),
);
}
public function getMoveToData()
{
return array(
array('192.168.0.0/22', '24',
array(
'192.168.0.0/24',
'192.168.1.0/24',
'192.168.2.0/24',
'192.168.3.0/24'
)
),
array('192.168.2.0/24', '25',
array(
'192.168.2.0/25',
'192.168.2.128/25'
)
),
array('192.168.2.0/30', '32',
array(
'192.168.2.0/32',
'192.168.2.1/32',
'192.168.2.2/32',
'192.168.2.3/32'
)
),
);
}
public function getMoveToExceptionData()
{
return array(
array('192.168.0.0/22', '22'),
array('192.168.0.0/22', '21'),
array('192.168.0.0/22', '33'),
array('192.168.0.0/22', 'prefixLength')
);
}
public function getTestIterationData()
{
return array(
array('192.168.2.0/29',
array(
'192.168.2.0',
'192.168.2.1',
'192.168.2.2',
'192.168.2.3',
'192.168.2.4',
'192.168.2.5',
'192.168.2.6',
'192.168.2.7',
)
),
array('2001:db8::/125',
array(
'2001:db8::',
'2001:db8::1',
'2001:db8::2',
'2001:db8::3',
'2001:db8::4',
'2001:db8::5',
'2001:db8::6',
'2001:db8::7',
)
),
);
}
public function getTestCountData()
{
return array(
array('127.0.0.0/31', 2),
array('2001:db8::/120', 256),
);
}
}

View File

@ -0,0 +1,146 @@
<?php
use IPTools\Range;
use IPTools\Network;
use IPTools\IP;
class RangeTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getTestParseData
*/
public function testParse($data, $expected)
{
$range = Range::parse($data);
$this->assertEquals($expected[0], $range->firstIP);
$this->assertEquals($expected[1], $range->lastIP);
}
/**
* @dataProvider getTestNetworksData
*/
public function testGetNetworks($data, $expected)
{
$result = array();
foreach (Range::parse($data)->getNetworks() as $network) {
$result[] = (string)$network;
}
$this->assertEquals($expected, $result);
}
/**
* @dataProvider getTestContainsData
*/
public function testContains($data, $find, $expected)
{
$this->assertEquals($expected, Range::parse($data)->contains(new IP($find)));
}
/**
* @dataProvider getTestIterationData
*/
public function testRangeIteration($data, $expected)
{
foreach (Range::parse($data) as $key => $ip) {
$result[] = (string)$ip;
}
$this->assertEquals($expected, $result);
}
/**
* @dataProvider getTestCountData
*/
public function testCount($data, $expected)
{
$this->assertEquals($expected, count(Range::parse($data)));
}
public function getTestParseData()
{
return array(
array('127.0.0.1-127.255.255.255', array('127.0.0.1', '127.255.255.255')),
array('127.0.0.1/24', array('127.0.0.0', '127.0.0.255')),
array('127.*.0.0', array('127.0.0.0', '127.255.0.0')),
array('127.255.255.0', array('127.255.255.0', '127.255.255.0')),
);
}
public function getTestNetworksData()
{
return array(
array('192.168.1.*', array('192.168.1.0/24')),
array('192.168.1.208-192.168.1.255', array(
'192.168.1.208/28',
'192.168.1.224/27'
)),
array('192.168.1.0-192.168.1.191', array(
'192.168.1.0/25',
'192.168.1.128/26'
)),
array('192.168.1.125-192.168.1.126', array(
'192.168.1.125/32',
'192.168.1.126/32',
)),
);
}
public function getTestContainsData()
{
return array(
array('192.168.*.*', '192.168.245.15', true),
array('192.168.*.*', '192.169.255.255', false),
/**
* 10.10.45.48 --> 00001010 00001010 00101101 00110000
* the last 0000 leads error
*/
array('10.10.45.48/28', '10.10.45.58', true),
array('2001:db8::/64', '2001:db8::ffff', true),
array('2001:db8::/64', '2001:db8:ffff::', false),
);
}
public function getTestIterationData()
{
return array(
array('192.168.2.0-192.168.2.7',
array(
'192.168.2.0',
'192.168.2.1',
'192.168.2.2',
'192.168.2.3',
'192.168.2.4',
'192.168.2.5',
'192.168.2.6',
'192.168.2.7',
)
),
array('2001:db8::/125',
array(
'2001:db8::',
'2001:db8::1',
'2001:db8::2',
'2001:db8::3',
'2001:db8::4',
'2001:db8::5',
'2001:db8::6',
'2001:db8::7',
)
),
);
}
public function getTestCountData()
{
return array(
array('127.0.0.0/31', 2),
array('2001:db8::/120', 256),
);
}
}

View File

@ -0,0 +1,10 @@
<?php
if (!class_exists('\PHPUnit_Framework_TestCase') && class_exists('\PHPUnit\Framework\TestCase')) {
class_alias('\PHPUnit\Framework\TestCase', '\PHPUnit_Framework_TestCase');
}
require __DIR__ . '/../src/PropertyTrait.php';
require __DIR__ . '/../src/IP.php';
require __DIR__ . '/../src/Network.php';
require __DIR__ . '/../src/Range.php';