Subversion Repositories vgwhois

Rev

Rev 65 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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