Subversion Repositories php_utils

Rev

Rev 5 | Rev 51 | 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
 
3
/*
4
 * IPv6 functions for PHP
46 daniel-mar 5
 * Copyright 2012-2022 Daniel Marschall, ViaThinkSoft
6
 * Version 2022-09-18
2 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
 */
20
 
21
# This library requires either the GMP extension (or BCMath if gmp_supplement.inc.php is present)
22
 
23
// TODO: oop, exceptions?
24
// TODO: variant without gmp ?
25
// TODO: IPv6 resolution 'ffff::192.168.69.1' -> 'ffff:0000:0000:0000:0000:0000:c0a8:4501' does not work!
26
 
27
if (file_exists(__DIR__ . '/gmp_supplement.inc.php')) include_once __DIR__ . '/gmp_supplement.inc.php';
28
 
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);
381
                $rng[0] = trim($rng[0]);
46 daniel-mar 382
                if (isset($rng[1])) $rng[1] = trim($rng[1]);
2 daniel-mar 383
                $rng[0] = ipv6_normalize($rng[0]);
384
                if (!isset($rng[1])) $rng[1] = $rng[0];
385
                $rng[1] = ipv6_normalize($rng[1]);
386
        }
387
 
388
        return $rng;
389
}
390
 
391
# ---
392
 
393
if (!function_exists('gmp_shiftl')) {
394
        function gmp_shiftl($x, $n) { // shift left
395
                // http://www.php.net/manual/en/ref.gmp.php#99788
396
                return gmp_mul($x, gmp_pow('2', $n));
397
        }
398
}
399
 
400
if (!function_exists('gmp_shiftr')) {
401
        function gmp_shiftr($x, $n) { // shift right
402
                // http://www.php.net/manual/en/ref.gmp.php#99788
403
                return gmp_div($x, gmp_pow('2', $n));
404
        }
405
}
406
 
407
$cache_ip2long6 = array();
408
function ip2long6($ipv6) {
409
        // Source:
410
        // http://www.netz-guru.de/2009/11/07/php-ipv6-ip2long-und-long2ip-funktionen/
411
        // Slightly modified
412
 
413
        global $cache_ip2long6;
414
        if (isset($cache_ip2long6[$ipv6])) return $cache_ip2long6[$ipv6];
415
 
416
        if ($ipv6 == '') $ipv6 = '::';
417
 
418
        if (filter_var($ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
419
                $cache_ip2long6[$ipv6] = false;
420
                return false;
421
        }
422
 
423
        $ip_n = @inet_pton($ipv6);
424
        if ($ip_n === false) {
425
                $cache_ip2long6[$ipv6] = false;
426
                return false; // modified
427
        }
428
        $bytes = 16; // 16 bytes x 8 bit/byte = 128bit
429
        $ipv6long = '';
430
 
431
        while ($bytes > 0) {
432
                $bin = sprintf('%08b',(ord($ip_n[$bytes-1])));
433
                $ipv6long = $bin.$ipv6long;
434
                $bytes--;
435
        }
436
 
437
        // $out = gmp_strval(gmp_init($ipv6long, 2), 10);
438
        $out = gmp_init($ipv6long, 2);
439
        $cache_ip2long6[$ipv6] = $out;
440
        return $out;
441
}
442
 
443
$cache_long2ip6 = array();
444
function long2ip6($ipv6long, $compress=true) {
445
        // Source:
446
        // http://www.netz-guru.de/2009/11/07/php-ipv6-ip2long-und-long2ip-funktionen/
447
        // Slightly modified
448
 
449
        global $cache_long2ip6;
450
        $vvv = ($compress ? 'T' : 'F').$ipv6long;
451
        if (isset($cache_long2ip6[$vvv])) return $cache_long2ip6[$vvv];
452
 
453
        // $bin = gmp_strval(gmp_init($ipv6long, 10), 2);
454
        $bin = gmp_strval($ipv6long, 2);
46 daniel-mar 455
        $bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
2 daniel-mar 456
 
457
        $bytes = 0;
458
        $ipv6 = '';
459
        while ($bytes < 8) { // 16 bytes x 8 bit/byte = 128bit
460
                $bin_part = substr($bin,($bytes*16),16);
461
                $part = dechex(bindec($bin_part));
462
                if (!$compress) {
463
                        $part = str_pad($part, 4, '0', STR_PAD_LEFT);
464
                }
465
                $ipv6 .= $part.':';
466
                $bytes++;
467
        }
468
 
469
        if ($compress) {
470
                $out = inet_ntop(inet_pton(substr($ipv6, 0, -1)));
471
        } else {
472
                $out = substr($ipv6, 0, strlen($ipv6)-1);
473
        }
474
        $cache_long2ip6[$vvv] = $out;
475
        return $out;
476
}
477
 
478
# --- New 16,12,12
479
 
480
define('IPV6_BITS', 128);
481
 
482
$global_ipv6_distance = array();
483
function ipv6_distance($ipOrCIDR_Searchterm, $ipOrCIDR_Candidate) {
484
        global $global_ipv6_distance;
485
        $vvv = $ipOrCIDR_Searchterm.'|'.$ipOrCIDR_Candidate;
486
        if (isset($global_ipv6_distance[$vvv])) return $global_ipv6_distance[$vvv];
487
 
488
        $ary = ipv6_cidr_split($ipOrCIDR_Searchterm);
489
        $ip = $ary[0];
490
 
491
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
492
                $global_ipv6_distance[$vvv] = false;
493
                return false;
494
        }
495
 
496
        $ary = ipv6_cidr_split($ipOrCIDR_Candidate);
497
        $ip = $ary[0];
498
        $cidr_bits = $ary[1];
499
        if ($cidr_bits > IPV6_BITS) {
500
                $global_ipv6_distance[$vvv] = false;
501
                return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
502
        }
503
        if (!is_numeric($cidr_bits)) return false;
504
 
505
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
506
                $global_ipv6_distance[$vvv] = false;
507
                return false;
508
        }
509
 
510
        $x = ipv6_trackdown($ipOrCIDR_Searchterm);
511
 
512
        if (ipv6_in_cidr($x[0], $ip.'/'.$cidr_bits)) {
513
                $ary = ipv6_cidr_split($x[0]);
514
                $cidr_bits2 = $ary[1];
515
                if ($cidr_bits2 > IPV6_BITS) {
516
                        $global_ipv6_distance[$vvv] = false;
517
                        return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
518
                }
519
                $out = $cidr_bits2-$cidr_bits;
520
                $global_ipv6_distance[$vvv] = $out;
521
                return $out;
522
        }
523
 
524
        $i = 0;
525
        $max = false;
526
        foreach ($x as &$y) {
527
                if (ipv6_in_cidr($ip.'/'.$cidr_bits, $y)) {
528
                        $max = $i;
529
                }
530
                $i++;
531
        }
532
 
533
        $global_ipv6_distance[$vvv] = $max;
534
        return $max;
535
}
536
 
537
function ipv6_cidr_split($ipOrCIDR) {
538
        $ary = explode('/', $ipOrCIDR, 2);
539
        $cidr_bits = isset($ary[1]) ? $ary[1] : IPV6_BITS;
540
        if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
541
        if (!is_numeric($cidr_bits)) return false;
542
        $ip = $ary[0];
543
        return array($ip, $cidr_bits);
544
}
545
 
546
function ipv6_equals($ipOrCIDRA, $ipOrCIDRB) {
547
        return ipv6_normalize_range($ipOrCIDRA) == ipv6_normalize_range($ipOrCIDRB);
548
}
549
 
550
function ipv6_cidr_min_ip($ipOrCIDR) {
551
        $ary = ipv6_cidr_split($ipOrCIDR);
552
        $ipOrCIDR  = $ary[0];
553
        $cidr_bits = $ary[1];
554
        if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
555
        if (!is_numeric($cidr_bits)) return false;
556
 
557
        $m = ip2bin($ipOrCIDR);
558
        $m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV6_BITS-$cidr_bits);
559
 
560
        return bin2ip($m);
561
}
562
 
563
function ipv6_cidr_max_ip($ipOrCIDR) {
564
        $ary = ipv6_cidr_split($ipOrCIDR);
565
        $ipOrCIDR  = $ary[0];
566
        $cidr_bits = $ary[1];
567
        if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
568
        if (!is_numeric($cidr_bits)) return false;
569
 
570
        $m = ip2bin($ipOrCIDR);
571
        $m = substr($m, 0, $cidr_bits) . str_repeat('1', IPV6_BITS-$cidr_bits);
572
 
573
        return bin2ip($m);
574
}
575
 
576
function ipv6_normalize_range($ipOrCIDR) {
577
        #     2001:1800::1/21
578
        # --> 2001:1800::/21
579
 
580
        #     2001:1af8:4100:a061:0001::1337
581
        # --> 2001:1af8:4100:a061:1::1337/128
582
 
583
        $ary = ipv6_cidr_split($ipOrCIDR);
584
        $ipOrCIDR  = $ary[0];
585
        $cidr_bits = $ary[1];
586
        if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
587
        if (!is_numeric($cidr_bits)) return false;
588
 
589
        $m = ip2bin($ipOrCIDR);
590
        $m = substr($m, 0, $cidr_bits) . str_repeat('0', IPV6_BITS-$cidr_bits);
591
 
592
        return bin2ip($m) . '/' . $cidr_bits;
593
}
594
 
595
function ipv6_trackdown($ipOrCIDR) {
596
        $ary = ipv6_cidr_split($ipOrCIDR);
597
        $ipOrCIDR  = $ary[0];
598
        $cidr_bits = $ary[1];
599
        if ($cidr_bits > IPV6_BITS) return false; // throw new Exception('CIDR bits > '.IPV6_BITS);
600
        if (!is_numeric($cidr_bits)) return false;
601
 
602
        $out = array();
603
        $m = ip2bin($ipOrCIDR);
604
        for ($i=$cidr_bits; $i>=0; $i--) {
605
                $m = substr($m, 0, $i) . str_repeat('0', IPV6_BITS-$i);
606
                $out[] = bin2ip($m) . '/' . $i;
607
        }
608
 
609
        return $out;
610
}
611
 
612
function ipv6_sort($ary) {
613
        $f = array();
614
        foreach ($ary as $c) {
615
                $a = explode('/', $c);
616
                $ip = $a[0];
617
                $bits = isset($a[1]) ? $a[1] : 128;
618
 
619
                $d = ip2bin($ip);
620
 
621
                # ord('*') must be smaller than ord('0')
622
                $d = substr($d, 0, $bits).str_repeat('*', 128-$bits);
623
 
624
                $f[$d] = $c;
625
        }
626
 
627
        return $f;
628
}
629
 
630
function ipv6_make_tree($ary) {
631
        $ary = ipv6_sort($ary);
632
 
633
        if (count($ary) == 0) return array();
634
 
635
        $sub_begin = '';
636
        $sub_begin_ip = '';
637
        foreach ($ary as $n => $d) {
638
                $sub_begin = substr($n, 0, strpos($n, '*'));
639
                $sub_begin_ip = $d;
640
                unset($ary[$n]);
641
                break;
642
        }
643
 
644
        $sub = array();
645
        $nonsub = array();
646
        foreach ($ary as $n => $d) {
647
                if (substr($n, 0, strlen($sub_begin)) == $sub_begin) {
648
                        $sub[$n] = $d;
649
                } else {
650
                        $nonsub[$n] = $d;
651
                }
652
        }
653
 
654
        $out = array();
655
        $out[$sub_begin_ip] = ipv6_make_tree($sub);
656
 
657
        $a = ipv6_make_tree($nonsub);
658
 
659
        $out = array_merge($out, $a);
660
 
661
        return $out;
662
}
663
 
664
# ---
665
 
666
if (!function_exists('ip2bin')) {
667
        $cache_ip2bin = array();
668
        function ip2bin($ip) {
669
                # Source: http://php.net/manual/en/function.ip2long.php#104163
670
                # modified by VTS
671
 
672
                global $cache_ip2bin;
673
                if (isset($cache_ip2bin[$ip])) return $cache_ip2bin[$ip];
674
 
675
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) {
676
                        $iplong = ip2long($ip);
677
                        assert($iplong !== false);
678
                        $out = base_convert((string)$iplong, 10, 2);
679
                        $cache_ip2bin[$ip] = $out;
680
                        return $out;
681
                }
682
                if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
683
                        $cache_ip2bin[$ip] = false;
684
                        return false;
685
                }
686
                if (($ip_n = inet_pton($ip)) === false) {
687
                        $cache_ip2bin[$ip] = false;
688
                        return false;
689
                }
690
                $bits = 15; // 16 x 8 bit = 128bit (ipv6)
691
                $ipbin = ''; # added by vts to avoid warning
692
                while ($bits >= 0) {
693
                        $bin = sprintf('%08b', (ord($ip_n[$bits])));
694
                        $ipbin = $bin.$ipbin;
695
                        $bits--;
696
                }
697
 
698
                $cache_ip2bin[$ip] = $ipbin;
699
                return $ipbin;
700
        }
701
}
702
 
703
if (!function_exists('bin2ip')) {
704
        $cache_bin2ip = array();
705
        function bin2ip($bin) {
706
                # Source: http://php.net/manual/en/function.ip2long.php#104163
707
                # modified by VTS
708
 
709
                global $cache_bin2ip;
710
                if (isset($cache_bin2ip[$bin])) return $cache_bin2ip[$bin];
711
 
712
                if (strlen($bin) <= 32) { // 32bits (ipv4)
713
                        $iplong = base_convert($bin, 2, 10);
714
                        $out = long2ip(intval($iplong));
715
                        $cache_bin2ip[$bin] = $out;
716
                        return $out;
717
                }
718
                if (strlen($bin) != 128) {
719
                        $cache_bin2ip[$bin] = false;
720
                        return false;
721
                }
46 daniel-mar 722
                //$bin = str_pad($bin, 128, '0', STR_PAD_LEFT);
2 daniel-mar 723
                $bits = 0;
724
                $ipv6 = ''; # added by vts to avoid warning
725
                while ($bits <= 7) {
726
                        $bin_part = substr($bin,($bits*16),16);
727
                        $ipv6 .= dechex(bindec($bin_part)) . ':';
728
                        $bits++;
729
                }
730
 
731
                $out = inet_ntop(inet_pton(substr($ipv6, 0, -1)));
732
                $cache_bin2ip[$bin] = $out;
733
                return $out;
734
        }
735
}
736
 
737
# --- TEST
738
 
739
/*
740
assert(ipv6_normalize('2001:0000:0000::1') == '2001::1');
741
 
742
assert(ipv6_distance('2001:1ae0::/27', '2001:1af8::/29') == -2);
743
assert(ipv6_distance('2001:1af0::/28', '2001:1af8::/29') == -1);
744
assert(ipv6_distance('2001:1af8::/29', '2001:1af8::/29') == 0);
745
assert(ipv6_distance('2001:1af8::/30', '2001:1af8::/29') == 1);
746
assert(ipv6_distance('2001:1af8::/31', '2001:1af8::/29') == 2);
747
 
748
assert(ipv6_distance('2001:1af8:4100:a061:0001::1336/127', '2001:1af8:4100:a061:0001::1335/127') === false);
749
assert(ipv6_distance('2001:1af8:4100:a061:0001::1336/128', '2001:1af8:4100:a061:0001::1337/128') === false);
750
assert(ipv6_distance('2001:1af8:4100:a061:0001::1336',     '2001:1af8:4100:a061:0001::1337')     === false);
751
*/
752
 
753
/*
754
$test = '2001:1af8:4100:a061:0001::1337';
755
$x = ipv6_trackdown($test);
756
foreach ($x as &$cidr) {
757
        $min = ipv6_cidr_min_ip($cidr);
758
        $max = ipv6_cidr_max_ip($cidr);
759
        echo "$cidr ($min - $max)\n";
760
}
761
*/