├── .gitignore ├── .travis.yml ├── composer.json ├── src └── v6tools │ ├── Runtime.php │ ├── autoload.php │ ├── EUI64.php │ ├── Subnet.php │ └── IPv6Address.php ├── test └── v6tools │ ├── EUI64Test.php │ ├── SubnetTest.php │ └── IPv6AddressTest.php ├── phpunit.xml.dist ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - '5.6' 4 | - '7.0' 5 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dsp/v6tools", 3 | "type": "library", 4 | "description": "IPv6 tools for PHP", 5 | "keywords": ["network", "ipv6"], 6 | "homepage": "http://github.com/dsp/v6tools", 7 | "license": "MIT", 8 | "authors": [{ 9 | "name": "David Soria Parra", 10 | "email": "dsp@php.net" 11 | }], 12 | "require": { 13 | "php": ">=5.3.0" 14 | }, 15 | "autoload": { 16 | "psr-0": {"v6tools\\": "src/"} 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^6.5" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/v6tools/Runtime.php: -------------------------------------------------------------------------------- 1 | assertTrue($c->isValid()); 8 | 9 | $c = new v6tools\EUI64('2a01:198:603:0:5416:473:fac9:f59c'); 10 | $this->assertFalse($c->isValid()); 11 | } 12 | 13 | public function testGetMacAddress() { 14 | $c = new v6tools\EUI64('2a01:198:603:0:224:d7ff:fe18:618c'); 15 | $this->assertEquals('00:24:d7:18:61:8c', $c->getMacAddress()); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/v6tools/autoload.php: -------------------------------------------------------------------------------- 1 | '/EUI64.php', 8 | 'v6tools\\ipv6address' => '/IPv6Address.php', 9 | 'v6tools\\runtime' => '/Runtime.php', 10 | 'v6tools\\subnet' => '/Subnet.php' 11 | ); 12 | } 13 | $cn = strtolower($class); 14 | if (isset($classes[$cn])) { 15 | require __DIR__ . $classes[$cn]; 16 | } 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | test 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | src 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PHP IPv6 Tools 2 | ============== 3 | 4 | [![Build Status](https://secure.travis-ci.org/dsp/v6tools.png?branch=master)](http://travis-ci.org/dsp/v6tools) 5 | 6 | PHP IPv6 Tools (v6tools) is a small library that provides validation of 7 | IPv6 addresses, subnets and EUI64. 8 | 9 | getMacAddress(); 19 | // echos 00:24:d6:18:61:8c 20 | 21 | $ip = new v6tools\Subnet('2001::/16'); 22 | $ip->isInSubnet('2001::1'); 23 | // returns true 24 | $ip->isInSubnet('2000::1'); 25 | // return false 26 | 27 | License 28 | ------- 29 | Licensed under the terms of the MIT License with additional Beerware clause. 30 | If you like v6tools feel free to buy me beer. 31 | -------------------------------------------------------------------------------- /test/v6tools/SubnetTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($c->isInSubnet('2a01:198:603:0:396e:4789:8e99:890f')); 7 | $this->assertFalse($c->isInSubnet('2a00:198:603:0:396e:4789:8e99:890f')); 8 | 9 | $c = new v6tools\Subnet('2001::/16'); 10 | $this->assertFalse($c->isInSubnet('2000::1')); 11 | } 12 | 13 | public function testEUI64() { 14 | $c = new v6tools\Subnet('2a01:198:603:0::/64'); 15 | $this->assertEquals('2a01:0198:0603:0000:0225:90ff:fea8:04c3', $c->getEUI64Address('00:25:90:a8:04:c3')); 16 | $this->assertEquals('2a01:0198:0603:0000:5265:f3ff:fef0:b6f2', $c->getEUI64Address('50-65-F3-F0-B6-F2')); 17 | } 18 | 19 | public function testEUI64InvalidMask() { 20 | $this->setExpectedException(\InvalidArgumentException::class); 21 | $c = new v6tools\Subnet('2a01:198:603:0::/48'); 22 | $c->getEUI64Address('00:25:90:a8:04:c3'); 23 | } 24 | 25 | public function testEUI64InvalidMac() { 26 | $this->setExpectedException(\InvalidArgumentException::class); 27 | $c = new v6tools\Subnet('2a01:198:603:0::/64'); 28 | $c->getEUI64Address('00:25:90:a8::c3'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011 David Soria Parra 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE 20 | 21 | Additional Beerware-Clause 22 | 23 | dsp wrote this. As long as you retain this notice you can do whatever 24 | you want with this stuff. If we meet some day, and you think this stuff 25 | is worth it, you can buy me a beer in return - David Soria Parra 26 | -------------------------------------------------------------------------------- /src/v6tools/EUI64.php: -------------------------------------------------------------------------------- 1 | 9 | * @package v6tools 10 | * @version 1.0 11 | */ 12 | class EUI64 { 13 | private $addr; 14 | 15 | /** 16 | * Create a new instance for the given IPv6 ddress 17 | * 18 | * @param string $addr The IPv6 address 19 | */ 20 | public function __construct($addr) { 21 | if (!IPv6Address::validate($addr)) { 22 | throw new \InvalidArgumentException("Not a valid IPv6 address."); 23 | } 24 | 25 | $this->addr = $addr; 26 | } 27 | 28 | /** 29 | * Checks if the IP address might have a valid EUI64. 30 | * 31 | * @return boolean 32 | */ 33 | public function isValid() { 34 | return preg_match('@ff:fe[0-9a-f]{2}:[0-9a-f]{4}$@', $this->addr) > 0; 35 | } 36 | 37 | /** 38 | * Calculate the mac address from the EUI64 part of the address. 39 | * 40 | * @return string 41 | */ 42 | public function getMacAddress() { 43 | if (!$this->isValid()) { 44 | return false; 45 | } 46 | 47 | $a = array_slice(explode(':', str_replace('ff:fe', '', $this->addr)), 4); 48 | $s = array_map( 49 | function ($e) { 50 | return hexdec($e); 51 | }, $a); 52 | $s[0] ^= 0x200; 53 | 54 | return implode(':', array_map( 55 | function ($e) { 56 | return sprintf("%02x:%02x", $e >> 8, $e & 0xff); 57 | }, $s)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /test/v6tools/IPv6AddressTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(v6tools\IPv6Address::validate('2a01:198:603:0:89d8:32f6:cd7e:9172')); 8 | } 9 | 10 | public function testValidatorInvalidAddress() { 11 | $this->assertFalse(v6tools\IPv6Address::validate('2a01:ggg:xxx:0:89d8:32f6:::cd7e:9172')); 12 | } 13 | 14 | 15 | public function testExpand() { 16 | $addr = new v6tools\IPv6Address('2001::1'); 17 | $this->assertEquals('2001:0000:0000:0000:0000:0000:0000:0001', 18 | $addr->expand()); 19 | } 20 | 21 | public function testCompact() { 22 | $addr = new v6tools\IPv6Address('2001:0000:0000:0000:0000:0000:0000:0001'); 23 | $this->assertEquals('2001:0000:0000:0000:0000:0000:0000:0001', 24 | (string) $addr); 25 | $this->assertEquals('2001::1', $addr->compact()); 26 | } 27 | /** 28 | * @expectedException InvalidArgumentException 29 | */ 30 | public function testInvalidAddress() { 31 | $addr = new v6tools\IPv6Address('foobar'); 32 | } 33 | 34 | public function testIsGlobal() { 35 | $addr = new v6tools\IPv6Address('2a01:198:603:0:89d8:32f6:cd7e:9172'); 36 | $this->assertTrue($addr->isGlobal()); 37 | 38 | $addr = new v6tools\IPv6Address('fe80::224:d7ff:fe18:618c'); 39 | $this->assertFalse($addr->isGlobal()); 40 | 41 | $addr = new v6tools\IPv6Address('ff02::1:ff18:618c'); 42 | $this->assertTrue($addr->isGlobal()); 43 | 44 | $addr = new v6tools\IPv6Address('0::0:0:0:0'); 45 | $this->assertFalse($addr->isGlobal()); 46 | } 47 | 48 | public function testIsLinkLocal() { 49 | $addr = new v6tools\IPv6Address('2a01:198:603:0:89d8:32f6:cd7e:9172'); 50 | $this->assertFalse($addr->isLinkLocal()); 51 | 52 | $addr = new v6tools\IPv6Address('fe80::224:d7ff:fe18:618c'); 53 | $this->assertTrue($addr->isLinkLocal()); 54 | 55 | $addr = new v6tools\IPv6Address('ff02::1:ff18:618c'); 56 | $this->assertFalse($addr->isLinkLocal()); 57 | } 58 | 59 | public function testIsUnicast() { 60 | $addr = new v6tools\IPv6Address('2a01:198:603:0:89d8:32f6:cd7e:9172'); 61 | $this->assertTrue($addr->isUnicast()); 62 | 63 | $addr = new v6tools\IPv6Address('fe80::224:d7ff:fe18:618c'); 64 | $this->assertTrue($addr->isUnicast()); 65 | 66 | $addr = new v6tools\IPv6Address('ff02::1:ff18:618c'); 67 | $this->assertFalse($addr->isUnicast()); 68 | } 69 | 70 | public function testIsMulticast() { 71 | $addr = new v6tools\IPv6Address('2a01:198:603:0:89d8:32f6:cd7e:9172'); 72 | $this->assertFalse($addr->isMulticast()); 73 | 74 | $addr = new v6tools\IPv6Address('fe80::224:d7ff:fe18:618c'); 75 | $this->assertFalse($addr->isMulticast()); 76 | 77 | $addr = new v6tools\IPv6Address('ff02::1:ff18:618c'); 78 | $this->assertTrue($addr->isMulticast()); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/v6tools/Subnet.php: -------------------------------------------------------------------------------- 1 | 9 | * @package v6tools 10 | * @version 1.0 11 | */ 12 | class Subnet { 13 | private $canonial; 14 | private $preflen; 15 | private $addr; 16 | 17 | /** 18 | * Create a new instance for the given IPv6 subnet. 19 | * 20 | * A subnet is given as ADDR/PREFIX. For example 200a:a32a:/64 21 | * 22 | * @param string $addr The IPv6 subnet 23 | */ 24 | public function __construct($subnet) { 25 | if (false === strpos($subnet, '/')) { 26 | throw new \InvalidArgumentException("Not a valid IPv6 subnet."); 27 | } 28 | 29 | list($addr, $preflen) = explode('/', $subnet); 30 | if (!is_numeric($preflen) || $preflen > 255) { 31 | throw new \InvalidArgumentException("Not a valid IPv6 preflen."); 32 | } 33 | 34 | if (!IPv6Address::validate($addr)) { 35 | throw new \InvalidArgumentException("Not a valid IPv6 address."); 36 | } 37 | 38 | $this->addr = $addr; 39 | $this->preflen = (int) $preflen; 40 | $this->canonial = $subnet; 41 | } 42 | 43 | /** 44 | * Checks if the give IPv6 Address is part of the subnet. 45 | * 46 | * @param string The IPv6 address to check 47 | */ 48 | public function isInSubnet($ipv6addr) { 49 | if (!IPv6Address::validate($ipv6addr)) { 50 | throw new \InvalidArgumentException("Not a valid IPv6 address."); 51 | } 52 | 53 | $bytes_addr = unpack("n*", inet_pton($this->addr)); 54 | $bytes_test = unpack("n*", inet_pton($ipv6addr)); 55 | for ($i = 1; $i <= ceil($this->preflen / 16); $i++) { 56 | $left = $this->preflen - 16 * ($i-1); 57 | $left = ($left <= 16) ? $left : 16; 58 | $mask = ~(0xffff >> $left) & 0xffff; 59 | if (($bytes_addr[$i] & $mask) != ($bytes_test[$i] & $mask)) { 60 | return false; 61 | } 62 | } 63 | return true; 64 | } 65 | 66 | /** 67 | * Generates EUI64 address 68 | * 69 | * @param string Mac address 70 | */ 71 | public function getEUI64Address($mac) { 72 | // Validate prefix length 73 | if ($this->preflen != 64) { 74 | throw new \InvalidArgumentException("Generating of IP addresses using EUI64 is only allowed in autoconfigured networks (/64 subnets). You must use a 64 bit prefix."); 75 | } 76 | 77 | // Validate mac address 78 | if (!filter_var($mac, FILTER_VALIDATE_MAC)) { 79 | throw new \InvalidArgumentException("Invalid mac address."); 80 | } 81 | 82 | // Expand MAC address and convert it to AAAA:AAAA:AAAA:AAAA format for simple merge with IPv6 address 83 | $mac = explode(':', str_replace(['.', '-', ':'], ':', $mac)); 84 | $mac = sprintf('%04x', (hexdec($mac[0]) << 8 | hexdec($mac[1])) ^ 0x200) . ':' . 85 | sprintf('%04x', hexdec($mac[2]) << 8 | 0xff) . ':' . 86 | sprintf('%04x', hexdec($mac[3]) | 0xfe00) . ':' . 87 | sprintf('%02x', hexdec($mac[4])) . 88 | sprintf('%02x', hexdec($mac[5])); 89 | 90 | // Expand IP address 91 | $ip = new IPv6Address($this->addr); 92 | $ip = $ip->expand(); 93 | 94 | for ($i = 1; $i <= strlen($mac); $i ++) { 95 | $ip[strlen($ip) - $i] = $mac[strlen($mac) - $i]; 96 | } 97 | 98 | // Return result 99 | return new IPv6Address($ip); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/v6tools/IPv6Address.php: -------------------------------------------------------------------------------- 1 | 9 | * @package v6tools 10 | * @version 1.0 11 | */ 12 | class IPv6Address { 13 | private $addr; 14 | 15 | const UNSPECIFIED = 0x0; 16 | const MULTICAST = 0xff00; 17 | const GLOBAL_UNICAST = 0x2000; 18 | const LINK_LOCAL = 0xfe80; 19 | const UNIQUE_LOCAL = 0xfc00; 20 | const SITE_LOCAL = 0xfec0; /* decprecated */ 21 | 22 | /** 23 | * Create a new instance for the given IPv6 address. 24 | * 25 | * @param string $addr The IPv6 address 26 | */ 27 | public function __construct($addr) { 28 | if (!self::validate($addr)) { 29 | throw new \InvalidArgumentException('Not a valid IPv6 address'); 30 | } 31 | 32 | $this->addr = $addr; 33 | } 34 | 35 | /** 36 | * Test if the given address is a valid IPv6 address 37 | * 38 | * @param string $addr The address to be validated as IPv6 39 | * @return boolean 40 | */ 41 | public static function validate ($addr) { 42 | if (!filter_var($addr, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { 43 | return false; 44 | } 45 | 46 | return true; 47 | } 48 | 49 | /** 50 | * Returns a fully expanded representation of the IPv6 address. 51 | * 52 | * This will not compact the address and return exactly 8 x 2byte integers 53 | * in a hexdecimal representation separated by :. 54 | * 55 | * 2001::1 becomes 2001:0000:0000:0000:0000:0000:0000:0001 56 | * 57 | * @return string 58 | */ 59 | public function expand() { 60 | $bytes = unpack('n*', inet_pton($this->addr)); 61 | return implode(':', array_map(function ($b) { 62 | return sprintf("%04x", $b); 63 | }, $bytes)); 64 | } 65 | 66 | /** 67 | * Returns a a compact representation of the IPv6 address. 68 | * 69 | * For further information about compact IPv6 addresses, please read 70 | * RFC 3513. 71 | * 72 | * 2001:0000:0000:0000:0000:0000:0000:0001 becomes 2001::1 73 | * 74 | * @return string 75 | */ 76 | public function compact() { 77 | return inet_ntop(inet_pton($this->addr)); 78 | } 79 | 80 | /** 81 | * Check if the given address is global routed according to current 82 | * IANA assignment. 83 | * 84 | * @return boolean 85 | */ 86 | public function isGlobal() { 87 | $prefix = $this->getRoutingPrefix(); 88 | return ($prefix & 0xe000) === self::GLOBAL_UNICAST 89 | || $this->isMulticast(); 90 | } 91 | 92 | /** 93 | * Check if the given address is a uncode address according to current 94 | * IANA assignment. 95 | * 96 | * @return boolean 97 | */ 98 | public function isUnicast() { 99 | return !$this->isMulticast(); 100 | } 101 | 102 | /** 103 | * Check if the given address is link local and will not be routed 104 | * by a router. 105 | * 106 | * @return boolean 107 | */ 108 | public function isLinkLocal() { 109 | return ($this->getRoutingPrefix() & 0xffc0) === self::LINK_LOCAL; 110 | } 111 | 112 | /** 113 | * Check if the given address is a multicast address. 114 | * 115 | * @return boolean 116 | */ 117 | public function isMulticast() { 118 | return ($this->getRoutingPrefix() & 0xff00) === self::MULTICAST; 119 | } 120 | 121 | /** 122 | * Returns the given address. 123 | * 124 | * The address is not expanded or compcated. It is returned 125 | * exactly how it was provided. 126 | * 127 | * @return string 128 | */ 129 | public function __toString() { 130 | return $this->addr; 131 | } 132 | 133 | protected function getRoutingPrefix() { 134 | $bytes = unpack('n*', inet_pton($this->addr)); 135 | 136 | if (count($bytes) < 1) { 137 | return self::UNSPECIFIED; 138 | } 139 | 140 | return $bytes[1] & 0xffff; 141 | } 142 | } 143 | 144 | --------------------------------------------------------------------------------