1<?php
2
3/**
4 * IPv6 Address Functions for PHP
5 *
6 * Functions to manipulate IPv6 addresses for PHP
7 *
8 * Copyright (C) 2009, 2011 Ray Patrick Soucy
9 *
10 * LICENSE:
11 *
12 * This program is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24 *
25 * @package   inet6
26 * @author    Ray Soucy <rps@soucy.org>
27 * @version   1.0.2
28 * @copyright 2009, 2011 Ray Patrick Soucy
29 * @link      http://www.soucy.org/
30 * @license   GNU General Public License version 3 or later
31 * @since     File available since Release 1.0.1
32 */
33
34 /**
35  * Expand an IPv6 Address
36  *
37  * This will take an IPv6 address written in short form and expand it to include all zeros.
38  *
39  * @param  string  $addr A valid IPv6 address
40  * @return string  The expanded notation IPv6 address
41  */
42function inet6_expand($addr)
43{
44    /* Check if there are segments missing, insert if necessary */
45    if (strpos($addr, '::') !== false) {
46        $part = explode('::', $addr);
47        $part[0] = explode(':', $part[0]);
48        $part[1] = explode(':', $part[1]);
49        $missing = array();
50        for ($i = 0; $i < (8 - (count($part[0]) + count($part[1]))); $i++)
51            array_push($missing, '0000');
52        $missing = array_merge($part[0], $missing);
53        $part = array_merge($missing, $part[1]);
54    } else {
55        $part = explode(":", $addr);
56    } // if .. else
57    /* Pad each segment until it has 4 digits */
58    foreach ($part as &$p) {
59        while (strlen($p) < 4) $p = '0' . $p;
60    } // foreach
61    unset($p);
62    /* Join segments */
63    $result = implode(':', $part);
64    /* Quick check to make sure the length is as expected */
65    if (strlen($result) == 39) {
66        return $result;
67    } else {
68        return false;
69    } // if .. else
70} // inet6_expand
71
72 /**
73  * Compress an IPv6 Address
74  *
75  * This will take an IPv6 address and rewrite it in short form.
76  *
77  * @param  string  $addr A valid IPv6 address
78  * @return string  The address in short form notation
79  */
80function inet6_compress($addr)
81{
82    /* PHP provides a shortcut for this operation */
83    $result = inet_ntop(inet_pton($addr));
84    return $result;
85} // inet6_compress
86
87 /**
88  * Generate an IPv6 mask from prefix notation
89  *
90  * This will convert a prefix to an IPv6 address mask (used for IPv6 math)
91  *
92  * @param  integer $prefix The prefix size, an integer between 1 and 127 (inclusive)
93  * @return string  The IPv6 mask address for the prefix size
94  */
95function inet6_prefix_to_mask($prefix)
96{
97    /* Make sure the prefix is a number between 1 and 127 (inclusive) */
98    $prefix = intval($prefix);
99    if ($prefix < 0 || $prefix > 128) return false;
100    $mask = '0b';
101    for ($i = 0; $i < $prefix; $i++) $mask .= '1';
102    for ($i = strlen($mask) - 2; $i < 128; $i++) $mask .= '0';
103    $mask = gmp_strval(gmp_init($mask), 16);
104    for ($i = 0; $i < 8; $i++) {
105        $result .= substr($mask, $i * 4, 4);
106        if ($i != 7) $result .= ':';
107    } // for
108    return inet6_compress($result);
109} // inet6_prefix_to_mask
110
111 /**
112  * Convert an IPv6 address and prefix size to an address range for the network.
113  *
114  * This will take an IPv6 address and prefix and return the first and last address available for the network.
115  *
116  * @param  string  $addr A valid IPv6 address
117  * @param  integer $prefix The prefix size, an integer between 1 and 127 (inclusive)
118  * @return array   An array with two strings containing the start and end address for the IPv6 network
119  */
120function inet6_to_range($addr, $prefix)
121{
122    $size = 128 - $prefix;
123    $addr = gmp_init('0x' . str_replace(':', '', inet6_expand($addr)));
124    $mask = gmp_init('0x' . str_replace(':', '', inet6_expand(inet6_prefix_to_mask($prefix))));
125    $prefix = gmp_and($addr, $mask);
126    $start = gmp_strval(gmp_add($prefix, '0x1'), 16);
127    $end = '0b';
128    for ($i = 0; $i < $size; $i++) $end .= '1';
129    $end = gmp_strval(gmp_add($prefix, gmp_init($end)), 16);
130    for ($i = 0; $i < 8; $i++) {
131        $start_result .= substr($start, $i * 4, 4);
132        if ($i != 7) $start_result .= ':';
133    } // for
134    for ($i = 0; $i < 8; $i++) {
135        $end_result .= substr($end, $i * 4, 4);
136        if ($i != 7) $end_result .= ':';
137    } // for
138    $result = array(inet6_compress($start_result), inet6_compress($end_result));
139    return $result;
140} // inet6_to_range
141
142 /**
143  * Convert an IPv6 address to two 64-bit integers.
144  *
145  * This will translate an IPv6 address into two 64-bit integer values for storage in an SQL database.
146  *
147  * @param  string  $addr A valid IPv6 address
148  * @return array   An array with two strings containing the 64-bit interger values
149  */
150function inet6_to_int64($addr)
151{
152    /* Expand the address if necessary */
153    if (strlen($addr) != 39) {
154        $addr = inet6_expand($addr);
155        if ($addr == false) return false;
156    } // if
157    $addr = str_replace(':', '', $addr);
158    $p1 = '0x' . substr($addr, 0, 16);
159    $p2 = '0x' . substr($addr, 16);
160    $p1 = gmp_init($p1);
161    $p2 = gmp_init($p2);
162    $result = array(gmp_strval($p1), gmp_strval($p2));
163    return $result;
164} // inet6_to_int64()
165
166 /**
167  * Convert two 64-bit integer values into an IPv6 address
168  *
169  * This will translate an array of 64-bit integer values back into an IPv6 address
170  *
171  * @param  array  $val An array containing two strings representing 64-bit integer values
172  * @return string An IPv6 address
173  */
174function int64_to_inet6($val)
175{
176    /* Make sure input is an array with 2 numerical strings */
177    $result = false;
178    if ( ! is_array($val) || count($val) != 2) return $result;
179    $p1 = gmp_strval(gmp_init($val[0]), 16);
180    $p2 = gmp_strval(gmp_init($val[1]), 16);
181    while (strlen($p1) < 16) $p1 = '0' . $p1;
182    while (strlen($p2) < 16) $p2 = '0' . $p2;
183    $addr = $p1 . $p2;
184    for ($i = 0; $i < 8; $i++) {
185        $result .= substr($addr, $i * 4, 4);
186        if ($i != 7) $result .= ':';
187    } // for
188    return inet6_compress($result);
189} // int64_to_inet6()
190
191// trailing PHP tag omitted to prevent accidental whitespace
192