1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5/**
6 * This file contains the implementation of the Net_IPv6 class
7 *
8 * PHP versions 4 and 5
9 *
10 * LICENSE: This source file is subject to the New BSD license, that is
11 * available through the world-wide-web at
12 * http://www.opensource.org/licenses/bsd-license.php
13 * If you did not receive a copy of the new BSDlicense and are unable
14 * to obtain it through the world-wide-web, please send a note to
15 * license@php.net so we can mail you a copy immediately
16 *
17 * @category  Net
18 * @package   Net_IPv6
19 * @author    Alexander Merz <alexander.merz@web.de>
20 * @copyright 2003-2005 The PHP Group
21 * @license   BSD License http://www.opensource.org/licenses/bsd-license.php
22 * @version   CVS: $Id: IPv6.php 339983 2016-09-04 21:13:31Z alexmerz $
23 * @link      http://pear.php.net/package/Net_IPv6
24 */
25
26// {{{ constants
27
28/**
29 * Error message if netmask bits was not found
30 * @see isInNetmask
31 */
32define("NET_IPV6_NO_NETMASK_MSG", "Netmask length not found");
33
34/**
35 * Error code if netmask bits was not found
36 * @see isInNetmask
37 */
38define("NET_IPV6_NO_NETMASK", 10);
39
40/**
41 * Address Type: Unassigned (RFC 1884, Section 2.3)
42 * @see getAddressType()
43 */
44define("NET_IPV6_UNASSIGNED", 1);
45
46/**
47 * Address Type: Reserved (RFC 1884, Section 2.3)
48 * @see getAddressType()
49 */
50define("NET_IPV6_RESERVED", 11);
51
52/**
53 * Address Type: Reserved for NSAP Allocation (RFC 1884, Section 2.3)
54 * @see getAddressType()
55 */
56define("NET_IPV6_RESERVED_NSAP", 12);
57
58/**
59 * Address Type: Reserved for IPX Allocation (RFC 1884, Section 2.3)
60 * @see getAddressType()
61 */
62define("NET_IPV6_RESERVED_IPX", 13);
63
64/**
65 * Address Type: Reserved for Geographic-Based Unicast Addresses
66 * (RFC 1884, Section 2.3)
67 * @see getAddressType()
68 */
69define("NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC", 14);
70
71/**
72 * Address Type: Provider-Based Unicast Address (RFC 1884, Section 2.3)
73 * @see getAddressType()
74 */
75define("NET_IPV6_UNICAST_PROVIDER", 22);
76
77/**
78 * Address Type: Multicast Addresses (RFC 1884, Section 2.3)
79 * @see getAddressType()
80 */
81define("NET_IPV6_MULTICAST", 31);
82
83/**
84 * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3)
85 * @see getAddressType()
86 */
87define("NET_IPV6_LOCAL_LINK", 42);
88
89/**
90 * Address Type: Link Local Use Addresses (RFC 1884, Section 2.3)
91 * @see getAddressType()
92 */
93define("NET_IPV6_LOCAL_SITE", 43);
94
95/**
96 * Address Type: Address range to embedded IPv4 ip in an IPv6 address (RFC 4291, Section 2.5.5)
97 * @see getAddressType()
98 */
99define("NET_IPV6_IPV4MAPPING", 51);
100
101/**
102 * Address Type: Unspecified (RFC 4291, Section 2.5.2)
103 * @see getAddressType()
104 */
105define("NET_IPV6_UNSPECIFIED", 52);
106
107/**
108 * Address Type: Unspecified (RFC 4291, Section 2.5.3)
109 * @see getAddressType()
110 */
111define("NET_IPV6_LOOPBACK", 53);
112
113/**
114 * Address Type: address can not assigned to a specific type
115 * @see getAddressType()
116 */
117define("NET_IPV6_UNKNOWN_TYPE", 1001);
118
119// }}}
120// {{{ Net_IPv6
121
122/**
123 * Class to validate and to work with IPv6 addresses.
124 *
125 * @category  Net
126 * @package   Net_IPv6
127 * @author    Alexander Merz <alexander.merz@web.de>
128 * @author    <elfrink at introweb dot nl>
129 * @author    Josh Peck <jmp at joshpeck dot org>
130 * @copyright 2003-2010 The PHP Group
131 * @license   BSD License http://www.opensource.org/licenses/bsd-license.php
132 * @version   Release: 1.1.0RC5
133 * @link      http://pear.php.net/package/Net_IPv6
134 */
135class Net_IPv6
136{
137
138    // {{{ separate()
139    /**
140     * Separates an IPv6 address into the address and a prefix length part
141     *
142     * @param String $ip the (compressed) IP as Hex representation
143     *
144     * @return Array the first element is the IP, the second the prefix length
145     * @since  1.2.0
146     * @access public
147     * @static
148     */
149    public static function separate($ip)
150    {
151
152        $addr = $ip;
153        $spec = '';
154
155        if(false === strrpos($ip, '/')) {
156
157            return array($addr, $spec);
158
159        }
160
161        $elements = explode('/', $ip);
162
163        if(2 == count($elements)) {
164
165            $addr = $elements[0];
166            $spec = $elements[1];
167
168        }
169
170        return array($addr, $spec);
171
172    }
173    // }}}
174
175    // {{{ removeNetmaskSpec()
176
177    /**
178     * Removes a possible existing prefix length/ netmask specification at an IP address.
179     *
180     * @param String $ip the (compressed) IP as Hex representation
181     *
182     * @return String the IP without netmask length
183     * @since  1.1.0
184     * @access public
185     * @static
186     */
187    public static function removeNetmaskSpec($ip)
188    {
189
190        $elements = Net_IPv6::separate($ip);
191
192        return $elements[0];
193
194    }
195    // }}}
196    // {{{ removePrefixLength()
197
198    /**
199     * Tests for a prefix length specification in the address
200     * and removes the prefix length, if exists
201     *
202     * The method is technically identical to removeNetmaskSpec() and
203     * will be dropped in a future release.
204     *
205     * @param String $ip a valid ipv6 address
206     *
207     * @return String the address without a prefix length
208     * @access public
209     * @static
210     * @see removeNetmaskSpec()
211     * @deprecated
212     */
213    public static function removePrefixLength($ip)
214    {
215        $pos = strrpos($ip, '/');
216
217        if (false !== $pos) {
218
219            return substr($ip, 0, $pos);
220
221        }
222
223        return $ip;
224    }
225
226    // }}}
227    // {{{ getNetmaskSpec()
228
229    /**
230     * Returns a possible existing prefix length/netmask specification on an IP address.
231     *
232     * @param String $ip the (compressed) IP as Hex representation
233     *
234     * @return String the netmask spec
235     * @since  1.1.0
236     * @access public
237     * @static
238     */
239    public static function getNetmaskSpec($ip)
240    {
241
242        $elements = Net_IPv6::separate($ip);
243
244        return $elements[1];
245
246    }
247
248    // }}}
249    // {{{ getPrefixLength()
250
251    /**
252     * Tests for a prefix length specification in the address
253     * and returns the prefix length, if exists
254     *
255     * The method is technically identical to getNetmaskSpec() and
256     * will be dropped in a future release.
257     *
258     * @param String $ip a valid ipv6 address
259     *
260     * @return Mixed the prefix as String or false, if no prefix was found
261     * @access public
262     * @static
263     * @deprecated
264     */
265    public static function getPrefixLength($ip)
266    {
267        if (preg_match("/^([0-9a-fA-F:]{2,39})\/(\d{1,3})*$/",
268                        $ip, $matches)) {
269
270            return $matches[2];
271
272        } else {
273
274            return false;
275
276        }
277
278    }
279
280    // }}}
281    // {{{ getNetmask()
282
283    /**
284     * Calculates the network prefix based on the netmask bits.
285     *
286     * @param String $ip   the (compressed) IP in Hex format
287     * @param int    $bits if the number of netmask bits is not part of the IP
288     *                     you must provide the number of bits
289     *
290     * @return String the network prefix
291     * @since  1.1.0
292     * @access public
293     * @static
294     */
295    public static function getNetmask($ip, $bits = null)
296    {
297        if (null==$bits) {
298
299            $elements = explode('/', $ip);
300
301            if (2 == count($elements)) {
302
303                $addr = $elements[0];
304                $bits = $elements[1];
305
306            } else {
307
308                include_once 'PEAR.php';
309
310                return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG,
311                                        NET_IPV6_NO_NETMASK);
312            }
313
314        } else {
315
316            $addr = $ip;
317
318        }
319
320        $addr       = Net_IPv6::uncompress($addr);
321        $binNetmask = str_repeat('1', $bits).str_repeat('0', 128 - $bits);
322
323        return Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($addr) & $binNetmask);
324    }
325
326    // }}}
327    // {{{ isInNetmask()
328
329    /**
330     * Checks if an (compressed) IP is in a specific address space.
331     *
332     * If the IP does not contain the number of netmask bits (F8000::FFFF/16)
333     * then you have to use the $bits parameter.
334     *
335     * @param String $ip      the IP to check (eg. F800::FFFF)
336     * @param String $netmask the netmask (eg F800::)
337     * @param int    $bits    the number of netmask bits to compare,
338     *                        if not given in $ip
339     *
340     * @return boolean true if $ip is in the netmask
341     * @since  1.1.0
342     * @access public
343     * @static
344     */
345    public static function isInNetmask($ip, $netmask, $bits=null)
346    {
347        // try to get the bit count
348
349        if (null == $bits) {
350
351            $elements = explode('/', $ip);
352
353            if (2 == count($elements)) {
354
355                $ip   = $elements[0];
356                $bits = $elements[1];
357
358            } else if (null == $bits) {
359
360                $elements = explode('/', $netmask);
361
362                if (2 == count($elements)) {
363
364                    $netmask = $elements[0];
365                    $bits    = $elements[1];
366
367                    // Correctly uncompress netmasks with prefixes, ie ::FFFF/96 == 0:0:0:0:0:FFFF:0:0/96
368                    // Need only for xxx::yyy/zz and ::yyy/zz
369                    // Added by Observium developers
370                    if (preg_match('/\:\:[a-f\d]{1,4}$/', $netmask) ||                  // ::yyy/zz
371                        preg_match('/^[a-f\d]{1,4}.*\:\:.*[a-f\d]{1,4}$/', $netmask)) { // xxx::yyy/zz
372                        $c_bits = intval((128 - $bits) / 16);
373                        $netmask .= str_repeat(':0', $c_bits);
374                    }
375
376                }
377
378                if (null == $bits) {
379
380                    include_once 'PEAR.php';
381                    return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG,
382                                            NET_IPV6_NO_NETMASK);
383
384                }
385
386            }
387
388        }
389
390        $binIp      = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($ip));
391        $binNetmask = Net_IPv6::_ip2Bin(Net_IPv6::removeNetmaskSpec($netmask));
392        //echo("IP: $ip, Net: $netmask, CIDR: /$bits\n");
393        //echo("IP:  ".substr($binIp, 0, $bits)." ".substr($binIp, $bits)." (".strlen($binIp).
394        //     ")\nNet: ".substr($binNetmask, 0, $bits)." ".substr($binNetmask, $bits)." (".strlen($binNetmask).")\n");
395
396        if (null != $bits
397            && "" != $bits
398            && 0 == strncmp($binNetmask, $binIp, $bits)) {
399
400            return true;
401
402        }
403
404        return false;
405    }
406
407    // }}}
408    // {{{ getAddressType()
409
410    /**
411     * Returns the type of an IPv6 address.
412     *
413     * RFC 2373, Section 2.3 describes several types of addresses in
414     * the IPv6 address space.
415     * Several address types are markers for reserved spaces and as
416     * a consequence are subject to change.
417     *
418     * @param String $ip the IP address in Hex format,
419     *                    compressed IPs are allowed
420     *
421     * @return int one of the address type constants
422     * @access public
423     * @since  1.1.0
424     * @static
425     *
426     * @see    NET_IPV6_UNASSIGNED
427     * @see    NET_IPV6_RESERVED
428     * @see    NET_IPV6_RESERVED_NSAP
429     * @see    NET_IPV6_RESERVED_IPX
430     * @see    NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC
431     * @see    NET_IPV6_UNICAST_PROVIDER
432     * @see    NET_IPV6_MULTICAST
433     * @see    NET_IPV6_LOCAL_LINK
434     * @see    NET_IPV6_LOCAL_SITE
435     * @see    NET_IPV6_IPV4MAPPING
436     * @see    NET_IPV6_UNSPECIFIED
437     * @see    NET_IPV6_LOOPBACK
438     * @see    NET_IPV6_UNKNOWN_TYPE
439     */
440    public static function getAddressType($ip)
441    {
442        $ip    = Net_IPv6::removeNetmaskSpec($ip);
443        $binip = Net_IPv6::_ip2Bin($ip);
444
445        if(0 == strncmp(str_repeat('0', 128), $binip, 128)) { // ::/128
446
447            return NET_IPV6_UNSPECIFIED;
448
449        } else if(0 == strncmp(str_repeat('0', 127).'1', $binip, 128)) { // ::/128
450
451            return NET_IPV6_LOOPBACK;
452
453        } else if (0 == strncmp(str_repeat('0', 80).str_repeat('1', 16), $binip, 96)) { // ::ffff/96
454
455            return NET_IPV6_IPV4MAPPING;
456
457        } else if (0 == strncmp('1111111010', $binip, 10)) {
458
459            return NET_IPV6_LOCAL_LINK;
460
461        } else if (0 == strncmp('1111111011', $binip, 10)) {
462
463            return NET_IPV6_LOCAL_SITE;
464
465        } else if (0 == strncmp('111111100', $binip, 9)) {
466
467            return NET_IPV6_UNASSIGNED;
468
469        } else if (0 == strncmp('11111111', $binip, 8)) {
470
471            return NET_IPV6_MULTICAST;
472
473        } else if (0 == strncmp('00000000', $binip, 8)) {
474
475            return NET_IPV6_RESERVED;
476
477        } else if (0 == strncmp('00000001', $binip, 8)
478                    || 0 == strncmp('1111110', $binip, 7)) {
479
480            return NET_IPV6_UNASSIGNED;
481
482        } else if (0 == strncmp('0000001', $binip, 7)) {
483
484            return NET_IPV6_RESERVED_NSAP;
485
486        } else if (0 == strncmp('0000010', $binip, 7)) {
487
488            return NET_IPV6_RESERVED_IPX;
489
490        } else if (0 == strncmp('0000011', $binip, 7) ||
491                    0 == strncmp('111110', $binip, 6) ||
492                    0 == strncmp('11110', $binip, 5) ||
493                    0 == strncmp('00001', $binip, 5) ||
494                    0 == strncmp('1110', $binip, 4) ||
495                    0 == strncmp('0001', $binip, 4) ||
496                    0 == strncmp('001', $binip, 3) ||
497                    0 == strncmp('011', $binip, 3) ||
498                    0 == strncmp('101', $binip, 3) ||
499                    0 == strncmp('110', $binip, 3)) {
500
501            return NET_IPV6_UNASSIGNED;
502
503        } else if (0 == strncmp('010', $binip, 3)) {
504
505            return NET_IPV6_UNICAST_PROVIDER;
506
507        } else if (0 == strncmp('100', $binip, 3)) {
508
509            return NET_IPV6_RESERVED_UNICAST_GEOGRAPHIC;
510
511        }
512
513        return NET_IPV6_UNKNOWN_TYPE;
514    }
515
516    // }}}
517    // {{{ Uncompress()
518
519    /**
520     * Uncompresses an IPv6 address
521     *
522     * RFC 2373 allows you to compress zeros in an address to '::'. This
523     * function expects a valid IPv6 address and expands the '::' to
524     * the required zeros.
525     *
526     * Example:  FF01::101  ->  FF01:0:0:0:0:0:0:101
527     *           ::1        ->  0:0:0:0:0:0:0:1
528     *
529     * @param String $ip a valid IPv6-address (hex format)
530     * @param Boolean $leadingZeros if true, leading zeros are added to each
531     *                              block of the address
532     *                              (FF01::101  ->
533     *                               FF01:0000:0000:0000:0000:0000:0000:0101)
534     *
535     * @return String the uncompressed IPv6-address (hex format)
536     * @access public
537     * @see Compress()
538     * @static
539     * @author Pascal Uhlmann
540     */
541    public static function uncompress($ip, $leadingZeros = false)
542    {
543
544        $prefix = Net_IPv6::getPrefixLength($ip);
545
546        if (false === $prefix) {
547
548            $prefix = '';
549
550        } else {
551
552            $ip     = Net_IPv6::removeNetmaskSpec($ip);
553            $prefix = '/'.$prefix;
554
555        }
556
557        $netmask = Net_IPv6::getNetmaskSpec($ip);
558        $uip     = Net_IPv6::removeNetmaskSpec($ip);
559
560        $c1 = -1;
561        $c2 = -1;
562
563        if (false !== strpos($uip, '::') ) {
564
565            list($ip1, $ip2) = explode('::', $uip, 2);
566
567            if ("" == $ip1) {
568
569                $c1 = -1;
570
571            } else {
572
573                $pos = 0;
574
575                if (0 < ($pos = substr_count($ip1, ':'))) {
576
577                    $c1 = $pos;
578
579                } else {
580
581                    $c1 = 0;
582
583                }
584            }
585            if ("" == $ip2) {
586
587                $c2 = -1;
588
589            } else {
590
591                $pos = 0;
592
593                if (0 < ($pos = substr_count($ip2, ':'))) {
594
595                    $c2 = $pos;
596
597                } else {
598
599                    $c2 = 0;
600
601                }
602
603            }
604
605            if (strstr($ip2, '.')) {
606
607                $c2++;
608
609            }
610            if (-1 == $c1 && -1 == $c2) { // ::
611
612                $uip = "0:0:0:0:0:0:0:0";
613
614            } else if (-1 == $c1) {              // ::xxx
615
616                $fill = str_repeat('0:', max(1, 7-$c2));
617                $uip  = str_replace('::', $fill, $uip);
618
619            } else if (-1 == $c2) {              // xxx::
620
621                $fill = str_repeat(':0', max(1, 7-$c1));
622                $uip  = str_replace('::', $fill, $uip);
623
624            } else {                          // xxx::xxx
625
626                $fill = str_repeat(':0:', 6-$c2-$c1);
627                $uip  = str_replace('::', $fill, $uip);
628                $uip  = str_replace('::', ':', $uip);
629
630            }
631        }
632
633        if(true == $leadingZeros) {
634
635            $uipT    = array();
636            $uiparts = explode(':', $uip);
637
638            foreach($uiparts as $p) {
639
640                $uipT[] = sprintf('%04s', $p);
641
642            }
643
644            $uip = implode(':', $uipT);
645        }
646
647        if ('' != $netmask) {
648
649                $uip = $uip.'/'.$netmask;
650
651        }
652
653        return $uip.$prefix;
654    }
655
656    // }}}
657    // {{{ Compress()
658
659    /**
660     * Compresses an IPv6 address
661     *
662     * RFC 2373 allows you to compress zeros in an address to '::'. This
663     * function expects a valid IPv6 address and compresses successive zeros
664     * to '::'
665     *
666     * Example:  FF01:0:0:0:0:0:0:101   -> FF01::101
667     *           0:0:0:0:0:0:0:1        -> ::1
668     *
669     * When $ip is an already compressed address and $force is false, the method returns
670     * the value as is, even if the address can be compressed further.
671     *
672     * Example: FF01::0:1 -> FF01::0:1
673     *
674     * To enforce maximum compression, you can set the second argument $force to true.
675     *
676     * Example: FF01::0:1 -> FF01::1
677     *
678     * @param String  $ip    a valid IPv6-address (hex format)
679     * @param boolean $force if true the address will be compressed as best as possible (since 1.2.0)
680     *
681     * @return String the compressed IPv6-address (hex format)
682     * @access public
683     * @see    Uncompress()
684     * @static
685     * @author elfrink at introweb dot nl
686     */
687    public static function compress($ip, $force = false)
688    {
689
690        if(false !== strpos($ip, '::')) { // its already compressed
691
692            if(true == $force) {
693
694                $ip = Net_IPv6::uncompress($ip);
695
696            } else {
697
698                return $ip;
699
700            }
701
702        }
703
704        $prefix = Net_IPv6::getPrefixLength($ip);
705
706        if (false === $prefix) {
707
708            $prefix = '';
709
710        } else {
711
712            $ip     = Net_IPv6::removeNetmaskSpec($ip);
713            $prefix = '/'.$prefix;
714
715        }
716
717        $netmask = Net_IPv6::getNetmaskSpec($ip);
718        $ip      = Net_IPv6::removeNetmaskSpec($ip);
719
720        $ipp = explode(':', $ip);
721
722        for ($i = 0; $i < count($ipp); $i++) {
723
724            $ipp[$i] = dechex(hexdec($ipp[$i]));
725
726        }
727
728        $cip = ':' . join(':', $ipp) . ':';
729
730        preg_match_all("/(:0)(:0)+/", $cip, $zeros);
731
732        if (count($zeros[0]) > 0) {
733
734            $match = '';
735
736            foreach ($zeros[0] as $zero) {
737
738                if (strlen($zero) > strlen($match)) {
739
740                    $match = $zero;
741
742                }
743            }
744
745            $cip = preg_replace('/' . $match . '/', ':', $cip, 1);
746
747        }
748
749        $cip = preg_replace('/((^:)|(:$))/', '', $cip);
750        $cip = preg_replace('/((^:)|(:$))/', '::', $cip);
751
752        if ('' != $netmask) {
753
754            $cip = $cip.'/'.$netmask;
755
756        }
757
758        return $cip.$prefix;
759
760    }
761
762    // }}}
763    // {{{ recommendedFormat()
764    /**
765     * Represent IPv6 address in RFC5952 format.
766     *
767     * @param String  $ip a valid IPv6-address (hex format)
768     *
769     * @return String the recommended representation of IPv6-address (hex format)
770     * @access public
771     * @see    compress()
772     * @static
773     * @author koyama at hoge dot org
774     * @todo This method may become a part of compress() in a further releases
775     */
776    public static function recommendedFormat($ip)
777    {
778        $compressed = self::compress($ip, true);
779        // RFC5952 4.2.2
780        // The symbol "::" MUST NOT be used to shorten just one
781        // 16-bit 0 field.
782        if ((substr_count($compressed, ':') == 7) &&
783            (strpos($compressed, '::') !== false)) {
784            $compressed = str_replace('::', ':0:', $compressed);
785        }
786        return $compressed;
787    }
788    // }}}
789
790    // {{{ isCompressible()
791
792    /**
793     * Checks, if an IPv6 address can be compressed
794     *
795     * @param String $ip a valid IPv6 address
796     *
797     * @return Boolean true, if address can be compressed
798     * @access public
799     * @since 1.2.0b
800     * @static
801     * @author Manuel Schmitt
802     */
803    public static function isCompressible($ip)
804    {
805
806        return (bool)($ip != Net_IPv6::compress($address));
807
808    }
809
810    // }}}
811    // {{{ SplitV64()
812
813    /**
814     * Splits an IPv6 address into the IPv6 and a possible IPv4 part
815     *
816     * RFC 2373 allows you to note the last two parts of an IPv6 address as
817     * an IPv4 compatible address
818     *
819     * Example:  0:0:0:0:0:0:13.1.68.3
820     *           0:0:0:0:0:FFFF:129.144.52.38
821     *
822     * @param String  $ip         a valid IPv6-address (hex format)
823     * @param Boolean $uncompress if true, the address will be uncompressed
824     *                            before processing
825     *
826     * @return Array  [0] contains the IPv6 part,
827     *                [1] the IPv4 part (hex format)
828     * @access public
829     * @static
830     */
831    public static function SplitV64($ip, $uncompress = true)
832    {
833        $ip = Net_IPv6::removeNetmaskSpec($ip);
834
835        if ($uncompress) {
836
837            $ip = Net_IPv6::uncompress($ip);
838
839        }
840
841        if (strstr($ip, '.')) {
842
843            $pos      = strrpos($ip, ':');
844
845            if(false === $pos) {
846                return array("", $ip);
847            }
848
849            $ip{$pos} = '_';
850            $ipPart   = explode('_', $ip);
851
852            return $ipPart;
853
854        } else {
855
856            return array($ip, "");
857
858        }
859    }
860
861    // }}}
862    // {{{ checkIPv6()
863
864    /**
865     * Checks an IPv6 address
866     *
867     * Checks if the given IP is IPv6-compatible
868     *
869     * @param String $ip a valid IPv6-address
870     *
871     * @return Boolean true if $ip is an IPv6 address
872     * @access public
873     * @static
874     */
875    public static function checkIPv6($ip)
876    {
877
878        $elements = Net_IPv6::separate($ip);
879
880        $ip = $elements[0];
881
882        if('' != $elements[1] && ( !is_numeric($elements[1]) || 0 > $elements || 128 < $elements[1])) {
883
884            return false;
885
886        }
887
888        $ipPart = Net_IPv6::SplitV64($ip);
889        $count  = 0;
890
891        if (!empty($ipPart[0])) {
892            $ipv6 = explode(':', $ipPart[0]);
893
894			if(8 < count($ipv6)) {
895				return false;
896			}
897
898            foreach($ipv6 as $element) { // made a validate precheck
899                if(!preg_match('/[0-9a-fA-F]*/', $element)) {
900                    return false;
901                }
902            }
903
904            for ($i = 0; $i < count($ipv6); $i++) {
905
906                if(4 < strlen($ipv6[$i])) {
907
908                    return false;
909
910                }
911
912                $dec = hexdec($ipv6[$i]);
913                $hex = strtoupper(preg_replace("/^[0]{1,3}(.*[0-9a-fA-F])$/",
914                                                "\\1",
915                                                $ipv6[$i]));
916
917                if ($ipv6[$i] >= 0 && $dec <= 65535
918                    && $hex == strtoupper(dechex($dec))) {
919
920                    $count++;
921
922                }
923
924            }
925
926            if (8 == $count and empty($ipPart[1])) {
927
928                return true;
929
930            } else if (6 == $count and !empty($ipPart[1])) {
931
932                $ipv4  = explode('.', $ipPart[1]);
933                $count = 0;
934
935                for ($i = 0; $i < count($ipv4); $i++) {
936
937                    if ($ipv4[$i] >= 0 && (integer)$ipv4[$i] <= 255
938                        && preg_match("/^\d{1,3}$/", $ipv4[$i])) {
939
940                        $count++;
941
942                    }
943
944                }
945
946                if (4 == $count) {
947
948                    return true;
949
950                }
951
952            } else {
953
954                return false;
955
956            }
957
958        } else {
959
960            return false;
961
962        }
963
964    }
965
966    // }}}
967
968    // {{{ _parseAddress()
969
970    /**
971     * Returns the lowest and highest IPv6 address
972     * for a given IP and netmask specification
973     *
974     * The netmask may be a part of the $ip or
975     * the number of netmask bits is provided via $bits
976     *
977     * The result is an indexed array. The key 'start'
978     * contains the lowest possible IP address. The key
979     * 'end' the highest address.
980     *
981     * @param String $ipToParse the IPv6 address
982     * @param String $bits      the optional count of netmask bits
983     *
984     * @return Array ['start', 'end'] the lowest and highest IPv6 address
985     * @access public
986     * @static
987     * @author Nicholas Williams
988     */
989
990    public static function parseAddress($ipToParse, $bits = null)
991    {
992
993        $ip      = null;
994        $bitmask = null;
995
996        if ( null == $bits ) {
997
998            $elements = explode('/', $ipToParse);
999
1000            if ( 2 == count($elements) ) {
1001
1002                $ip      = Net_IPv6::uncompress($elements[0]);
1003                $bitmask = $elements[1];
1004
1005            } else {
1006
1007                include_once 'PEAR.php';
1008
1009                return PEAR::raiseError(NET_IPV6_NO_NETMASK_MSG,
1010                                        NET_IPV6_NO_NETMASK);
1011            }
1012        } else {
1013
1014            $ip      = Net_IPv6::uncompress($ipToParse);
1015            $bitmask = $bits;
1016
1017        }
1018
1019        $binNetmask = str_repeat('1', $bitmask).
1020                      str_repeat('0', 128 - $bitmask);
1021        $maxNetmask = str_repeat('1', 128);
1022        $netmask    = Net_IPv6::_bin2Ip($binNetmask);
1023
1024        $startAddress = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip)
1025                                          & $binNetmask);
1026        $endAddress   = Net_IPv6::_bin2Ip(Net_IPv6::_ip2Bin($ip)
1027                                          | ($binNetmask ^ $maxNetmask));
1028
1029        return array('start' => $startAddress, 'end' => $endAddress);
1030    }
1031
1032    // }}}
1033
1034    // {{{ _ip2Bin()
1035
1036    /**
1037     * Converts an IPv6 address from Hex into Binary representation.
1038     *
1039     * @param String $ip the IP to convert (a:b:c:d:e:f:g:h),
1040     *                   compressed IPs are allowed
1041     *
1042     * @return String the binary representation
1043     * @access private
1044     * @since 1.1.0
1045     */
1046    protected static function _ip2Bin($ip)
1047    {
1048        $binstr = '';
1049
1050        $ip = Net_IPv6::removeNetmaskSpec($ip);
1051
1052        // Correctly convert IPv4 mapped addresses
1053        // Added by Observium developers
1054        list(, $ipv4) = Net_IPv6::SplitV64($ip, FALSE);
1055        if (strlen($ipv4))
1056        {
1057            $ipv4map = explode('.', $ipv4, 4);
1058            $ipv4replace = dechex($ipv4map[0] * 256 + $ipv4map[1]) . ':' . dechex($ipv4map[2] * 256 + $ipv4map[3]);
1059            $ip = str_replace($ipv4, $ipv4replace, $ip);
1060        }
1061
1062        $ip = Net_IPv6::Uncompress($ip);
1063
1064        $parts = explode(':', $ip);
1065
1066        foreach ( $parts as $v ) {
1067
1068            $str     = base_convert($v, 16, 2);
1069            $binstr .= str_pad($str, 16, '0', STR_PAD_LEFT);
1070
1071        }
1072
1073        return $binstr;
1074    }
1075
1076    // }}}
1077    // {{{ _bin2Ip()
1078
1079    /**
1080     * Converts an IPv6 address from Binary into Hex representation.
1081     *
1082     * @param String $bin the IP address as binary
1083     *
1084     * @return String the uncompressed Hex representation
1085     * @access private
1086     * @since 1.1.0
1087     */
1088    protected static function _bin2Ip($bin)
1089    {
1090        $ip = "";
1091
1092        if (strlen($bin) < 128) {
1093
1094            $bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
1095
1096        }
1097
1098        $parts = str_split($bin, "16");
1099
1100        foreach ( $parts as $v ) {
1101
1102            $str = base_convert($v, 2, 16);
1103            $ip .= $str.":";
1104
1105        }
1106
1107        $ip = substr($ip, 0, -1);
1108
1109        return $ip;
1110    }
1111
1112    // }}}
1113}
1114// }}}
1115
1116/*
1117 * Local variables:
1118 * tab-width: 4
1119 * c-basic-offset: 4
1120 * c-hanging-comment-ender-p: nil
1121 * End:
1122 */
1123
1124?>
1125