r4998 Added a reference to COPYING into every file with meaningful PHP, Perl or
[racktables] / wwwroot / inc / IPv6.php
1 <?php
2
3 # This file is a part of RackTables, a datacenter and server room management
4 # framework. See accompanying file "COPYING" for the full copyright and
5 # licensing information.
6
7 class IPv6Address
8 {
9 const zero_address = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; // 16 bytes
10 protected $words = self::zero_address;
11
12 function __construct ($bin_str = self::zero_address)
13 {
14 if (strlen ($bin_str) < 16)
15 $bin_str .= substr (self::zero_address, 0, 16 - strlen ($bin_str));
16 elseif (strlen ($bin_str) > 16)
17 $bin_str = substr ($bin_str, 0, 16);
18 $this->words = $bin_str;
19 }
20
21 // returns 16-byte binary string
22 function getBin ()
23 {
24 return $this->words;
25 }
26
27 // returns string for PTR DNS query (reversed IPv6 address)
28 function getArpa()
29 {
30 $ret = '';
31 foreach (array_reverse (unpack ('C*', $this->words)) as $octet)
32 {
33 $ret .= dechex ($octet & 0xF) . ".";
34 $ret .= dechex ($octet >> 4) . ".";
35 }
36 return $ret . "ip6.arpa";
37 }
38
39 private static function set_word_value (&$haystack, $nword, $hexvalue)
40 {
41 // check that $hexvalue is like /^[0-9a-fA-F]*$/
42 for ($i = 0; $i < strlen ($hexvalue); $i++)
43 {
44 $char = ord ($hexvalue[$i]);
45 if (! ($char >= 0x30 && $char <= 0x39 || $char >= 0x41 && $char <= 0x46 || $char >=0x61 && $char <= 0x66))
46 return FALSE;
47 }
48 $haystack = substr_replace ($haystack, pack ('n', hexdec ($hexvalue)), $nword * 2, 2);
49 return TRUE;
50 }
51
52 // returns bool - was the object modified or not.
53 // return true only if address syntax is completely correct.
54 function parse ($str_ipv6)
55 {
56 if (empty ($str_ipv6))
57 return FALSE;
58
59 $result = self::zero_address;
60 // remove one of double beginning/tailing colons
61 if (substr ($str_ipv6, 0, 2) == '::')
62 $str_ipv6 = substr ($str_ipv6, 1);
63 elseif (substr ($str_ipv6, -2, 2) == '::')
64 $str_ipv6 = substr ($str_ipv6, 0, strlen ($str_ipv6) - 1);
65
66 $tokens = explode (':', $str_ipv6);
67 $last_token = $tokens[count ($tokens) - 1];
68 $split = explode ('.', $last_token);
69 if (count ($split) == 4)
70 {
71 $hex_tokens = array();
72 $hex_tokens[] = dechex ($split[0] * 256 + $split[1]);
73 $hex_tokens[] = dechex ($split[2] * 256 + $split[3]);
74 array_splice ($tokens, -1, 1, $hex_tokens);
75 }
76 if (count ($tokens) > 8)
77 return FALSE;
78 for ($i = 0; $i < count ($tokens); $i++)
79 {
80 if ($tokens[$i] != '')
81 {
82 if (! self::set_word_value ($result, $i, $tokens[$i]))
83 return FALSE;
84 }
85 else
86 {
87 $k = 8; //index in result string (last word)
88 for ($j = count ($tokens) - 1; $j > $i; $j--) // $j is an index in $tokens for reverse walk
89 if ($tokens[$j] == '')
90 break;
91 elseif (! self::set_word_value ($result, --$k, $tokens[$j]))
92 return FALSE;
93 if ($i != $j)
94 return FALSE; //error, more than 1 '::' range
95 break;
96 }
97 }
98 if (! isset ($k) && count ($tokens) != 8)
99 return FALSE;
100 $this->words = $result;
101 return TRUE;
102 }
103
104 function format ()
105 {
106 // maybe this is IPv6-to-IPv4 address?
107 if (substr ($this->words, 0, 12) == "\0\0\0\0\0\0\0\0\0\0\xff\xff")
108 return '::ffff:' . implode ('.', unpack ('C*', substr ($this->words, 12, 4)));
109
110 $result = array();
111 $hole_index = NULL;
112 $max_hole_index = NULL;
113 $hole_length = 0;
114 $max_hole_length = 0;
115
116 for ($i = 0; $i < 8; $i++)
117 {
118 $unpacked = unpack ('n', substr ($this->words, $i * 2, 2));
119 $value = array_shift ($unpacked);
120 $result[] = dechex ($value & 0xffff);
121 if ($value != 0)
122 {
123 unset ($hole_index);
124 $hole_length = 0;
125 }
126 else
127 {
128 if (! isset ($hole_index))
129 $hole_index = $i;
130 if (++$hole_length >= $max_hole_length)
131 {
132 $max_hole_index = $hole_index;
133 $max_hole_length = $hole_length;
134 }
135 }
136 }
137 if (isset ($max_hole_index))
138 {
139 array_splice ($result, $max_hole_index, $max_hole_length, array (''));
140 if ($max_hole_index == 0 && $max_hole_length == 8)
141 return '::';
142 elseif ($max_hole_index == 0)
143 return ':' . implode (':', $result);
144 elseif ($max_hole_index + $max_hole_length == 8)
145 return implode (':', $result) . ':';
146 }
147 return implode (':', $result);
148 }
149
150 // returns new object with applied mask, or NULL if mask is incorrect
151 function get_first_subnet_address ($prefix_length)
152 {
153 if ($prefix_length < 0 || $prefix_length > 128)
154 return NULL;
155 $result = clone $this;
156 if ($prefix_length == 128)
157 return $result;
158 $char_num = intval ($prefix_length / 8);
159 if (0xff00 != $bitmask = 0xff00 >> ($prefix_length % 8))
160 {
161 $result->words[$char_num] = chr (ord ($result->words[$char_num]) & $bitmask);
162 ++$char_num;
163 }
164 for ($i = $char_num; $i < 16; $i++)
165 $result->words[$i] = "\0";
166 return $result;
167 }
168
169 // returns new object with applied mask, or NULL if mask is incorrect
170 function get_last_subnet_address ($prefix_length)
171 {
172 if ($prefix_length < 0 || $prefix_length > 128)
173 return NULL;
174 $result = clone $this;
175 if ($prefix_length == 128)
176 return $result;
177 $char_num = intval ($prefix_length / 8);
178 if (0xff != $bitmask = 0xff >> ($prefix_length % 8))
179 {
180 $result->words[$char_num] = chr (ord ($result->words[$char_num]) | $bitmask);
181 ++$char_num;
182 }
183 for ($i = $char_num; $i < 16; $i++)
184 $result->words[$i] = "\xff";
185 return $result;
186 }
187
188 // returns new object
189 function next ()
190 {
191 $result = clone $this;
192 for ($i = 15; $i >= 0; $i--)
193 {
194 if ($result->words[$i] == "\xff")
195 $result->words[$i] = "\0";
196 else
197 {
198 $result->words[$i] = chr (ord ($result->words[$i]) + 1);
199 break;
200 }
201 }
202 return $result;
203 }
204
205 // returns new object
206 function prev ()
207 {
208 $result = clone $this;
209 for ($i = 15; $i >= 0; $i--)
210 {
211 if ($result->words[$i] == "\0")
212 $result->words[$i] = "\xff";
213 else
214 {
215 $result->words[$i] = chr (ord ($result->words[$i]) - 1);
216 break;
217 }
218 }
219 return $result;
220 }
221
222 # $a == $b
223 public static function eq (IPv6Address $a, IPv6Address $b)
224 {
225 return $a->words === $b->words;
226 }
227
228 # $a > $b
229 public static function gt (IPv6Address $a, IPv6Address $b)
230 {
231 for ($i = 0; $i < 16; $i++)
232 if ($a->words[$i] > $b->words[$i])
233 return TRUE;
234 elseif ($a->words[$i] < $b->words[$i])
235 return FALSE;
236 return FALSE;
237 }
238
239 # $a < $b
240 public static function lt (IPv6Address $a, IPv6Address $b)
241 {
242 return ! self::eq ($a, $b) && ! self::gt ($a, $b);
243 }
244
245 # $a >= $b
246 public static function ge (IPv6Address $a, IPv6Address $b)
247 {
248 return self::eq ($a, $b) || self::gt ($a, $b);
249 }
250
251 # $a <= $b
252 public static function le (IPv6Address $a, IPv6Address $b)
253 {
254 return self::eq ($a, $b) || ! self::gt ($a, $b);
255 }
256
257 } // class IPv6Address
258
259 ?>