Subversion Repositories vgwhois

Rev

Rev 2 | Rev 45 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * IPv6 functions for PHP
  5.  * Copyright 2012-2014 Daniel Marschall, ViaThinkSoft
  6.  * Version 2014-12-12
  7.  *
  8.  * Licensed under the Apache License, Version 2.0 (the "License");
  9.  * you may not use this file except in compliance with the License.
  10.  * You may obtain a copy of the License at
  11.  *
  12.  *     http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing, software
  15.  * distributed under the License is distributed on an "AS IS" BASIS,
  16.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.  * See the License for the specific language governing permissions and
  18.  * limitations under the License.
  19.  */
  20.  
  21. # This library needs gmp! aptitude install php5-gmp
  22.  
  23. // TODO: oop, exceptions?
  24. // TODO: variant without gmp ?
  25. // TODO: IPv6 auflösung 'ffff::192.168.69.1' -> 'ffff:0000:0000:0000:0000:0000:c0a8:4501' geht nicht!!!
  26.  
  27. define('GMP_ONE', gmp_init('1'));
  28.  
  29. // Very small self-test:
  30. /*
  31. function ipv6_selftest() {
  32.         $iv_b = 'c0ff:ee00::';
  33.         $iv_m = 32;
  34.         $r = ipv6_cidr2range($iv_b, $iv_m);
  35.         echo "$iv_b/$iv_m => $r[0] - $r[1]\n";
  36.  
  37.         $rev = ipv6_range2cidr($r[0], $r[1]);
  38.         $rev = implode("\n", $rev);
  39.         echo "$r[0] - $r[1] => $rev [";
  40.         $ok = $rev == "$iv_b/$iv_m";
  41.         echo $ok ? 'OK' : 'Mismatch';
  42.         echo "]\n";
  43.         echo "In-CIDR-Test: ";
  44.         echo ipv6_in_cidr("$iv_b/$iv_m", "$iv_b/$iv_m") ? 'OK' : 'Fail';
  45.         echo "\n";
  46. }
  47. ipv6_selftest();
  48. */
  49.  
  50. $cache_ipv6_cidr2range = array();
  51. function ipv6_cidr2range($baseip_or_cidr, $subnet='') {
  52.         # (C) 2012 ViaThinkSoft
  53.         # Version 1.1
  54.         # This function converts an CIDR notation <baseip>/<subnet> into an IPv6 address block array($low_ip, $high_ip)
  55.  
  56.         global $cache_ipv6_cidr2range;
  57.         $vvv = $baseip_or_cidr.'|'.$subnet;
  58.         if (isset($cache_ipv6_cidr2range[$vvv])) return $cache_ipv6_cidr2range[$vvv];
  59.  
  60.         if (strpos($baseip_or_cidr, '/') !== false) {
  61.                 $tmp = explode('/', $baseip_or_cidr, 2);
  62.                 $baseip_or_cidr = $tmp[0];
  63.                 $subnet = $tmp[1];
  64.                 unset($tmp);
  65.         }
  66.  
  67.         if (($subnet < 0) || ($subnet > 128)) {
  68.                 $cache_ipv6_cidr2range[$vvv] = false;
  69.                 return false;
  70.         }
  71.  
  72.         $maxint128 = gmp_sub(gmp_pow('2', 128), GMP_ONE); # TODO: GMP_TWO ?
  73.         $netmask = gmp_shiftl($maxint128, 128-$subnet);
  74.         $netmask = gmp_and($netmask, $maxint128); // crop to 128 bit
  75.         $wildcard = gmp_xor($maxint128, $netmask);
  76.  
  77.         $x = gmp_and(ip2long6($baseip_or_cidr), $netmask);
  78.         $nums = $wildcard;
  79.         $low = long2ip6($x);
  80.         $high = long2ip6(gmp_add($x, $nums));
  81.  
  82.         $out = array($low, $high);
  83.         $cache_ipv6_cidr2range[$vvv] = $out;
  84.         return $out;
  85. }
  86.  
  87. $cache_ipv6_range2cidr = array();
  88. function ipv6_range2cidr($baseip, $topip) {
  89.         # (C) 2012 ViaThinkSoft
  90.         # Version 1.0
  91.         # This function converts an IPv6 address block into valid CIDR blocks (There may be multiple blocks!)
  92.  
  93.         global $cache_ipv6_range2cidr;
  94.         $vvv = $baseip.'|'.$topip;
  95.         if (isset($cache_ipv6_range2cidr[$vvv])) return $cache_ipv6_range2cidr[$vvv];
  96.  
  97.         $out = array();
  98.         if (ipv6_cmp($baseip, $topip) > 0) {
  99.                 $cache_ipv6_range2cidr[$vvv] = false;
  100.                 return false;
  101.         }
  102.         while (gmp_cmp(gmp_sub(ip2long6($baseip), GMP_ONE), ip2long6($topip)) != 0) {
  103.                 $i = -1;
  104.                 do {
  105.                         $i++;
  106.                         $range = ipv6_cidr2range($baseip, $i);
  107.                         $l = $range[0];
  108.                         $t = $range[1];
  109.                 } while ((ipv6_cmp($l, $baseip) != 0) || (ipv6_cmp($t, $topip) > 0));
  110.  
  111.                 $out[] = "$baseip/$i";
  112.                 $baseip = ipv6_add($t, GMP_ONE);
  113.         }
  114.  
  115.         $cache_ipv6_range2cidr[$vvv] = $out;
  116.         return $out;
  117. }
  118.  
  119. function ipv6_add($baseip, $num) {
  120.         # (C) 2012 ViaThinkSoft
  121.         # Version 1.0
  122.  
  123.         return long2ip6(gmp_add(ip2long6($baseip), $num));
  124. }
  125.  
  126. function ipv6_sub($baseip, $num) {
  127.         # (C) 2012 ViaThinkSoft
  128.         # Version 1.0
  129.  
  130.         return long2ip6(gmp_sub(ip2long6($baseip), $num));
  131. }
  132.  
  133. function ipv6_cmp($a, $b) {
  134.         # (C) 2012 ViaThinkSoft
  135.         # Version 1.0
  136.  
  137.         return gmp_cmp(ip2long6($a), ip2long6($b));
  138. }
  139.  
  140. $cache_ipv6_in_cidr = array();
  141. function ipv6_in_cidr($haystack, $needle) {
  142.         # (C) 2012 ViaThinkSoft
  143.         # Version 1.1
  144.  
  145.         global $cache_ipv6_in_cidr;
  146.         $vvv = $haystack.'|'.$needle;
  147.         if (isset($cache_ipv6_in_cidr[$vvv])) return $cache_ipv6_in_cidr[$vvv];
  148.  
  149.         $x = explode('/', $haystack);
  150.         $ha = ipv6_cidr2range($x[0], $x[1]);
  151.  
  152.         $x = explode('/', $needle);
  153.         if (!isset($x[1])) $x[1] = 128; // single IP
  154.         $ne = ipv6_cidr2range($x[0], $x[1]);
  155.  
  156.         $ha_low = ip2long6($ha[0]);
  157.         $ha_hig = ip2long6($ha[1]);
  158.         $ne_low = ip2long6($ne[0]);
  159.         $ne_hig = ip2long6($ne[1]);
  160.  
  161.         # HA:    low[                               ]high
  162.         # NE:            low[             ]high
  163.  
  164.         $out = (gmp_cmp($ne_low, $ha_low) >= 0) && (gmp_cmp($ne_hig, $ha_hig) <= 0);
  165.         $cache_ipv6_in_cidr[$vvv] = $out;
  166.         return $out;
  167. }
  168.  
  169. // IMPORTANT! $cmp_ary[x]=y MUST HAVE x<=y !
  170. function ipv6_merge_address_blocks($data, $debug = false) {
  171.         # (C) 2012-2013 ViaThinkSoft
  172.         # Version 2.2
  173.  
  174.         if ($debug !== false) $STARTZEIT = time();
  175.  
  176.         // 1. Convert IPs to numbers
  177.  
  178.         $cmp_ary = array();
  179.         foreach ($data as $a => &$b) {
  180.                 $a = ip2long6($a);
  181.                 $b = ip2long6($b);
  182.  
  183.                 $cmp_ary[gmp_strval($a)] = gmp_strval($b);
  184.                 unset($a);
  185.                 unset($b);
  186.         }
  187.  
  188.         // 2. Sort array
  189.  
  190.         ksort($cmp_ary);
  191.  
  192.         // 3. Merge the blocks in an intelligent way (and remove redundant blocks)
  193.  
  194.         # Merge overlapping blocks
  195.         #   [          ]
  196.         #           [            ]   ->   [                    ]
  197.  
  198.         # Merge neighbor blocks
  199.         #   [   ][   ]   ->   [        ]
  200.  
  201.         # Remove redundant blocks
  202.         #  [          ]   ->   [          ]
  203.         #      [  ]
  204.  
  205.         $merge_count = 0;
  206.         $redundant_deleted_count = 0;
  207.         $round_count = 0;
  208.         do {
  209.                 if ($debug !== false) {
  210.                         $LAUFZEIT = time() - $STARTZEIT;
  211.                         echo $debug."Merging... $round_count rounds; merged $merge_count blocks; deleted $redundant_deleted_count redundant blocks; time: $LAUFZEIT seconds\r";
  212.                 }
  213.  
  214.                 $round_count++;
  215.  
  216.                 $clean = true;
  217.  
  218.                 foreach ($cmp_ary as $a => &$b) {
  219.                         foreach ($cmp_ary as $x => &$y) {
  220.                                 // x in range [a+1..b+1] ?
  221.                                 if (gmp_cmp(gmp_init($x), gmp_init($a)) <= 0) continue;
  222.                                 if (gmp_cmp(gmp_init($x), gmp_add(gmp_init($b), GMP_ONE)) > 0) break;
  223.  
  224.                                 // Merge
  225.                                 $clean = false;
  226.                                 if (gmp_cmp(gmp_init($y), gmp_init($b)) > 0) {
  227.                                         $merge_count++;
  228.                                         $b = $y;
  229.                                         unset($cmp_ary[$x]);
  230.                                 } else {
  231.                                         $redundant_deleted_count++;
  232.                                         unset($cmp_ary[$x]);
  233.                                 }
  234.                         }
  235.                 }
  236.         } while (!$clean);
  237.  
  238.         if ($debug !== false) {
  239.                 $LAUFZEIT = time() - $STARTZEIT;
  240.                 echo $debug."Merge completed. $round_count rounds; merged $merge_count blocks; deleted $redundant_deleted_count redundant blocks; time: $LAUFZEIT seconds\n";
  241.         }
  242.  
  243.         // 4. Convert back to IPs
  244.  
  245.         $out_ary = array();
  246.         foreach ($cmp_ary as $a => &$b) {
  247.                 $a = long2ip6(gmp_init($a));
  248.                 $b = long2ip6(gmp_init($b));
  249.                 $out_ary[$a] = $b;
  250.         }
  251.  
  252.         return $out_ary;
  253. }
  254.  
  255. function ipv6_merge_arrays($data_a, $data_b) {
  256.         # (C) 2012 ViaThinkSoft
  257.         # Version 1.2
  258.  
  259.         $normalized_data_a = array();
  260.         foreach ($data_a as $from => &$to) {
  261.                 $normalized_data_a[ipv6_normalize($from)] = ipv6_normalize($to);
  262.         }
  263.  
  264.         $normalized_data_b = array();
  265.         foreach ($data_b as $from => &$to) {
  266.                 $normalized_data_b[ipv6_normalize($from)] = ipv6_normalize($to);
  267.         }
  268.  
  269.         $data = array();
  270.  
  271.         foreach ($normalized_data_a as $from => &$to) {
  272.                 if (isset($normalized_data_b[$from])) {
  273.                         $data[$from] = ipv6_max($to, $normalized_data_b[$from]);
  274.                 } else {
  275.                         $data[$from] = $to;
  276.                 }
  277.         }
  278.  
  279.         foreach ($normalized_data_b as $from => &$to) {
  280.                 if (!isset($normalized_data_a[$from])) {
  281.                         $data[$from] = $to;
  282.                 }
  283.         }
  284.  
  285.         return $data;
  286. }
  287.  
  288. function ipv6_valid($ip) {
  289.         # (C) 2012 ViaThinkSoft
  290.         # Version 1.0
  291.  
  292.         return ip2long6($ip) !== false;
  293. }
  294.  
  295. function ipv6_normalize($ip) {
  296.         # (C) 2012 ViaThinkSoft
  297.         # Version 1.0
  298.  
  299.         # Example:
  300.         # 2001:0000:0000::1 -> 2001::1
  301.  
  302.         $long = ip2long6($ip);
  303.         if ($long == -1 || $long === FALSE) return false;
  304.         return long2ip6($long);
  305. }
  306.  
  307. function ipv6_expand($ip) {
  308.         # (C) 2012 ViaThinkSoft
  309.         # Version 1.0
  310.  
  311.         # Example:
  312.         # 2001::1 -> 2001:0000:0000:0000:0000:0000:0000:0000
  313.  
  314.         $long = ip2long6($ip);
  315.         if ($long == -1 || $long === FALSE) return false;
  316.         return long2ip6($long, false);
  317. }
  318.  
  319. function ipv6_min($ip_a, $ip_b) {
  320.         # (C) 2012 ViaThinkSoft
  321.         # Version 1.0
  322.  
  323.         if (ipv6_cmp($ip_a, $ip_b) == -1) {
  324.                 return $ip_a;
  325.         } else {
  326.                 return $ip_b;
  327.         }
  328. }
  329.  
  330. function ipv6_max($ip_a, $ip_b) {
  331.         # (C) 2012 ViaThinkSoft
  332.         # Version 1.0
  333.  
  334.         if (ipv6_cmp($ip_a, $ip_b) == 1) {
  335.                 return $ip_a;
  336.         } else {
  337.                 return $ip_b;
  338.         }
  339. }
  340.  
  341. function ipv6_ipcount($data) {
  342.         # (C) 2012 ViaThinkSoft
  343.         # Version 1.0
  344.  
  345.         $cnt = gmp_init('0');
  346.  
  347.         foreach ($data as $from => &$to) {
  348.                 $cnt = gmp_add($cnt, gmp_sub(ip2long6($to), ip2long6($from)));
  349.         }
  350.  
  351.         return gmp_strval($cnt, 10);
  352. }
  353.  
  354. function ipv6_read_file($file) {
  355.         # (C) 2012 ViaThinkSoft
  356.         # Version 1.0
  357.  
  358.         $data = array();
  359.  
  360.         $lines = file($file);
  361.         foreach ($lines as &$line) {
  362.                 $rng = ipv6_line2range($line);
  363.                 $data[$rng[0]] = $rng[1];
  364.         }
  365.  
  366.         return $data;
  367. }
  368.  
  369. function ipv6_line2range($line) {
  370.         # (C) 2012 ViaThinkSoft
  371.         # Version 1.0
  372.  
  373.         $line = trim($line);
  374.  
  375.         if (strpos($line, '/') !== false) {
  376.                 $rng = ipv6_cidr2range($line);
  377.         } else {
  378.                 $rng = explode('-', $line);
  379.                 $rng[0] = trim($rng[0]);
  380.                 $rng[1] = trim($rng[1]);
  381.                 $rng[0] = ipv6_normalize($rng[0]);
  382.                 if (!isset($rng[1])) $rng[1] = $rng[0];
  383.                 $rng[1] = ipv6_normalize($rng[1]);
  384.         }
  385.  
  386.         return $rng;
  387. }
  388.  
  389. # ---
  390.  
  391. function gmp_shiftl($x, $n) { // shift left
  392.         // http://www.php.net/manual/en/ref.gmp.php#99788
  393.         return gmp_mul($x, gmp_pow('2', $n));
  394. }
  395.  
  396. function gmp_shiftr($x, $n) { // shift right
  397.         // http://www.php.net/manual/en/ref.gmp.php#99788
  398.         return gmp_div($x, gmp_pow('2', $n));
  399. }
  400.  
  401. $cache_ip2long6 = array();
  402. function ip2long6($ipv6) {
  403.         // Source:
  404.         // http://www.netz-guru.de/2009/11/07/php-ipv6-ip2long-und-long2ip-funktionen/
  405.         // Slightly modified
  406.  
  407.         global $cache_ip2long6;
  408.         if (isset($cache_ip2long6[$ipv6])) return $cache_ip2long6[$ipv6];
  409.  
  410.         if ($ipv6 == '') $ipv6 = '::';
  411.  
  412.         if (filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
  413.                 $cache_ip2long6[$ipv6] = false;
  414.                 return false;
  415.         }
  416.  
  417.         $ip_n = @inet_pton($ipv6);
  418.         if ($ip_n === false) {
  419.                 $cache_ip2long6[$ipv6] = false;
  420.                 return false; // modified
  421.         }
  422.         $bytes = 16; // 16 bytes x 8 bit/byte = 128bit
  423.         $ipv6long = '';
  424.  
  425.         while ($bytes > 0) {
  426.                 $bin = sprintf('%08b',(ord($ip_n[$bytes-1])));
  427.                 $ipv6long = $bin.$ipv6long;
  428.                 $bytes--;
  429.         }
  430.  
  431.         // $out = gmp_strval(gmp_init($ipv6long, 2), 10);
  432.         $out = gmp_init($ipv6long, 2);
  433.         $cache_ip2long6[$ipv6] = $out;
  434.         return $out;
  435. }
  436.  
  437. $cache_long2ip6 = array();
  438. function long2ip6($ipv6long, $compress=true) {
  439.         // Source:
  440.         // http://www.netz-guru.de/2009/11/07/php-ipv6-ip2long-und-long2ip-funktionen/
  441.         // Slightly modified
  442.  
  443.         global $cache_long2ip6;
  444.         $vvv = ($compress ? 'T' : 'F').$ipv6long;
  445.         if (isset($cache_long2ip6[$vvv])) return $cache_long2ip6[$vvv];
  446.  
  447.         // $bin = gmp_strval(gmp_init($ipv6long, 10), 2);
  448.         $bin = gmp_strval($ipv6long, 2);
  449.         if (strlen($bin) < 128) {
  450.                 $pad = 128 - strlen($bin);
  451.                 for ($i = 1; $i <= $pad; $i++) {
  452.                         $bin = '0'.$bin;
  453.                 }
  454.         }
  455.  
  456.         $bytes = 0;
  457.         $ipv6 = '';
  458.         while ($bytes < 8) { // 16 bytes x 8 bit/byte = 128bit
  459.                 $bin_part = substr($bin,($bytes*16),16);
  460.                 $part = dechex(bindec($bin_part));
  461.                 if (!$compress) {
  462.                         $part = str_pad($part, 4, '0', STR_PAD_LEFT);
  463.                 }
  464.                 $ipv6 .= $part.':';
  465.                 $bytes++;
  466.         }
  467.  
  468.         if ($compress) {
  469.                 $out = inet_ntop(inet_pton(substr($ipv6, 0, -1)));
  470.         } else {
  471.                 $out = substr($ipv6, 0, strlen($ipv6)-1);
  472.         }
  473.         $cache_long2ip6[$vvv] = $out;
  474.         return $out;
  475. }
  476.  
  477. # --- New 16,12,12
  478.  
  479. define('IPV6_BITS', 128);
  480.  
  481. $global_ipv6_distance = array();
  482. function ipv6_distance($ipOrCIDR_Searchterm, $ipOrCIDR_Candidate) {
  483.         global $global_ipv6_distance;
  484.         $vvv = $ipOrCIDR_Searchterm.'|'.$ipOrCIDR_Candidate;
  485.         if (isset($global_ipv6_distance[$vvv])) return $global_ipv6_distance[$vvv];
  486.  
  487.         $ary = ipv6_cidr_split($ipOrCIDR_Searchterm);
  488.         $ip = $ary[0];
  489.  
  490.         if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
  491.                 $global_ipv6_distance[$vvv] = false;
  492.                 return false;
  493.         }
  494.  
  495.         $ary = ipv6_cidr_split($ipOrCIDR_Candidate);
  496.         $ip = $ary[0];
  497.         $cidr_bits = $ary[1];
  498.         if ($cidr_bits > IPV6_BITS) {
  499.                 $global_ipv6_distance[$vvv] = false;
  500.                 return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
  501.         }
  502.         if (!is_numeric($cidr_bits)) return false;
  503.  
  504.         if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
  505.                 $global_ipv6_distance[$vvv] = false;
  506.                 return false;
  507.         }
  508.  
  509.         $x = ipv6_trackdown($ipOrCIDR_Searchterm);
  510.  
  511.         if (ipv6_in_cidr($x[0], $ip.'/'.$cidr_bits)) {
  512.                 $ary = ipv6_cidr_split($x[0]);
  513.                 $cidr_bits2 = $ary[1];
  514.                 if ($cidr_bits2 > IPV6_BITS) {
  515.                         $global_ipv6_distance[$vvv] = false;
  516.                         return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
  517.                 }
  518.                 $out = $cidr_bits2-$cidr_bits;
  519.                 $global_ipv6_distance[$vvv] = $out;
  520.                 return $out;
  521.         }
  522.  
  523.         $i = 0;
  524.         $max = false;
  525.         foreach ($x as &$y) {
  526.                 if (ipv6_in_cidr($ip.'/'.$cidr_bits, $y)) {
  527.                         $max = $i;
  528.                 }
  529.                 $i++;
  530.         }
  531.  
  532.         $global_ipv6_distance[$vvv] = $max;
  533.         return $max;
  534. }
  535.  
  536. function ipv6_cidr_split($ipOrCIDR) {
  537.         $ary = explode('/', $ipOrCIDR, 2);
  538.         $cidr_bits = isset($ary[1]) ? $ary[1] : IPV6_BITS;
  539.         if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
  540.         if (!is_numeric($cidr_bits)) return false;
  541.         $ip = $ary[0];
  542.         return array($ip, $cidr_bits);
  543. }
  544.  
  545. function ipv6_equals($ipOrCIDRA, $ipOrCIDRB) {
  546.         return ipv6_normalize_range($ipOrCIDRA) == ipv6_normalize_range($ipOrCIDRB);
  547. }
  548.  
  549. function ipv6_cidr_min_ip($ipOrCIDR) {
  550.         $ary = ipv6_cidr_split($ipOrCIDR);
  551.         $ipOrCIDR  = $ary[0];
  552.         $cidr_bits = $ary[1];
  553.         if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
  554.         if (!is_numeric($cidr_bits)) return false;
  555.  
  556.         $m = ip2bin($ipOrCIDR);
  557.         $m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV6_BITS-$cidr_bits);
  558.  
  559.         return bin2ip($m);
  560. }
  561.  
  562. function ipv6_cidr_max_ip($ipOrCIDR) {
  563.         $ary = ipv6_cidr_split($ipOrCIDR);
  564.         $ipOrCIDR  = $ary[0];
  565.         $cidr_bits = $ary[1];
  566.         if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
  567.         if (!is_numeric($cidr_bits)) return false;
  568.  
  569.         $m = ip2bin($ipOrCIDR);
  570.         $m = substr($m, 0, $cidr_bits) . str_repeat('1', IPV6_BITS-$cidr_bits);
  571.  
  572.         return bin2ip($m);
  573. }
  574.  
  575. function ipv6_normalize_range($ipOrCIDR) {
  576.         #     2001:1800::1/21
  577.         # --> 2001:1800::/21
  578.  
  579.         #     2001:1af8:4100:a061:0001::1337
  580.         # --> 2001:1af8:4100:a061:1::1337/128
  581.  
  582.         $ary = ipv6_cidr_split($ipOrCIDR);
  583.         $ipOrCIDR  = $ary[0];
  584.         $cidr_bits = $ary[1];
  585.         if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
  586.         if (!is_numeric($cidr_bits)) return false;
  587.  
  588.         $m = ip2bin($ipOrCIDR);
  589.         $m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV6_BITS-$cidr_bits);
  590.  
  591.         return bin2ip($m) . '/' . $cidr_bits;
  592. }
  593.  
  594. function ipv6_trackdown($ipOrCIDR) {
  595.         $ary = ipv6_cidr_split($ipOrCIDR);
  596.         $ipOrCIDR  = $ary[0];
  597.         $cidr_bits = $ary[1];
  598.         if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
  599.         if (!is_numeric($cidr_bits)) return false;
  600.  
  601.         $out = array();
  602.         $m = ip2bin($ipOrCIDR);
  603.         for ($i=$cidr_bits; $i>=0; $i--) {
  604.                 $m = substr($m, 0, $i) . str_repeat('0', IPV6_BITS-$i);
  605.                 $out[] = bin2ip($m) . '/' . $i;
  606.         }
  607.  
  608.         return $out;
  609. }
  610.  
  611. function ipv6_sort($ary) {
  612.         $f = array();
  613.         foreach ($ary as $c) {
  614.                 $a = explode('/', $c);
  615.                 $ip = $a[0];
  616.                 $bits = isset($a[1]) ? $a[1] : 128;
  617.  
  618.                 $d = ip2bin($ip);
  619.  
  620.                 # ord('*') must be smaller than ord('0')
  621.                 $d = substr($d, 0, $bits).str_repeat('*', 128-$bits);
  622.  
  623.                 $f[$d] = $c;
  624.         }
  625.  
  626.         return $f;
  627. }
  628.  
  629. function ipv6_make_tree($ary) {
  630.         $ary = ipv6_sort($ary);
  631.  
  632.         if (count($ary) == 0) return array();
  633.  
  634.         $sub_begin = '';
  635.         $sub_begin_ip = '';
  636.         foreach ($ary as $n => $d) {
  637.                 $sub_begin = substr($n, 0, strpos($n, '*'));
  638.                 $sub_begin_ip = $d;
  639.                 unset($ary[$n]);
  640.                 break;
  641.         }
  642.  
  643.         $sub = array();
  644.         $nonsub = array();
  645.         foreach ($ary as $n => $d) {
  646.                 if (substr($n, 0, strlen($sub_begin)) == $sub_begin) {
  647.                         $sub[$n] = $d;
  648.                 } else {
  649.                         $nonsub[$n] = $d;
  650.                 }
  651.         }
  652.  
  653.         $out = array();
  654.         $out[$sub_begin_ip] = ipv6_make_tree($sub);
  655.  
  656.         $a = ipv6_make_tree($nonsub);
  657.  
  658.         $out = array_merge($out, $a);
  659.  
  660.         return $out;
  661. }
  662.  
  663. # ---
  664.  
  665. if (!function_exists('ip2bin')) {
  666.         $cache_ip2bin = array();
  667.         function ip2bin($ip) {
  668.                 # Source: http://php.net/manual/en/function.ip2long.php#104163
  669.                 # modified by VTS
  670.  
  671.                 global $cache_ip2bin;
  672.                 if (isset($cache_ip2bin[$ip])) return $cache_ip2bin[$ip];
  673.  
  674.                 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
  675.                         $out = base_convert(ip2long($ip), 10, 2);
  676.                         $cache_ip2bin[$ip] = $out;
  677.                         return $out;
  678.                 }
  679.                 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
  680.                         $cache_ip2bin[$ip] = false;
  681.                         return false;
  682.                 }
  683.                 if (($ip_n = inet_pton($ip)) === false) {
  684.                         $cache_ip2bin[$ip] = false;
  685.                         return false;
  686.                 }
  687.                 $bits = 15; // 16 x 8 bit = 128bit (ipv6)
  688.                 $ipbin = ''; # added by vts to avoid warning
  689.                 while ($bits >= 0) {
  690.                         $bin = sprintf('%08b', (ord($ip_n[$bits])));
  691.                         $ipbin = $bin.$ipbin;
  692.                         $bits--;
  693.                 }
  694.  
  695.                 $cache_ip2bin[$ip] = $ipbin;
  696.                 return $ipbin;
  697.         }
  698. }
  699.  
  700. if (!function_exists('bin2ip')) {
  701.         $cache_bin2ip = array();
  702.         function bin2ip($bin) {
  703.                 # Source: http://php.net/manual/en/function.ip2long.php#104163
  704.                 # modified by VTS
  705.  
  706.                 global $cache_bin2ip;
  707.                 if (isset($cache_bin2ip[$bin])) return $cache_bin2ip[$bin];
  708.  
  709.                 if (strlen($bin) <= 32) { // 32bits (ipv4)
  710.                         $out = long2ip(base_convert($bin, 2, 10));
  711.                         $cache_bin2ip[$bin] = $out;
  712.                         return $out;
  713.                 }
  714.                 if (strlen($bin) != 128) {
  715.                         $cache_bin2ip[$bin] = false;
  716.                         return false;
  717.                 }
  718.                 $pad = 128 - strlen($bin);
  719.                 for ($i = 1; $i <= $pad; $i++) {
  720.                         $bin = '0'.$bin;
  721.                 }
  722.                 $bits = 0;
  723.                 $ipv6 = ''; # added by vts to avoid warning
  724.                 while ($bits <= 7) {
  725.                         $bin_part = substr($bin,($bits*16),16);
  726.                         $ipv6 .= dechex(bindec($bin_part)) . ':';
  727.                         $bits++;
  728.                 }
  729.  
  730.                 $out = inet_ntop(inet_pton(substr($ipv6, 0, -1)));
  731.                 $cache_bin2ip[$bin] = $out;
  732.                 return $out;
  733.         }
  734. }
  735.  
  736. # --- TEST
  737.  
  738. /*
  739. assert(ipv6_normalize('2001:0000:0000::1') == '2001::1');
  740.  
  741. assert(ipv6_distance('2001:1ae0::/27', '2001:1af8::/29') == -2);
  742. assert(ipv6_distance('2001:1af0::/28', '2001:1af8::/29') == -1);
  743. assert(ipv6_distance('2001:1af8::/29', '2001:1af8::/29') == 0);
  744. assert(ipv6_distance('2001:1af8::/30', '2001:1af8::/29') == 1);
  745. assert(ipv6_distance('2001:1af8::/31', '2001:1af8::/29') == 2);
  746.  
  747. assert(ipv6_distance('2001:1af8:4100:a061:0001::1336/127', '2001:1af8:4100:a061:0001::1335/127') === false);
  748. assert(ipv6_distance('2001:1af8:4100:a061:0001::1336/128', '2001:1af8:4100:a061:0001::1337/128') === false);
  749. assert(ipv6_distance('2001:1af8:4100:a061:0001::1336',     '2001:1af8:4100:a061:0001::1337')     === false);
  750. */
  751.  
  752. /*
  753. $test = '2001:1af8:4100:a061:0001::1337';
  754. $x = ipv6_trackdown($test);
  755. foreach ($x as &$cidr) {
  756.         $min = ipv6_cidr_min_ip($cidr);
  757.         $max = ipv6_cidr_max_ip($cidr);
  758.         echo "$cidr ($min - $max)\n";
  759. }
  760. */
  761.