MDL-14123 Full IPv6 support - reimplemented address_in_subnet() + added more unittests
authorskodak <skodak>
Fri, 9 Jan 2009 21:16:26 +0000 (21:16 +0000)
committerskodak <skodak>
Fri, 9 Jan 2009 21:16:26 +0000 (21:16 +0000)
lib/moodlelib.php
lib/simpletest/testmoodlelib.php

index 1169490..fd29779 100644 (file)
@@ -7275,14 +7275,12 @@ function make_unique_id_code($extra='') {
  *
  * The parameter is a comma separated string of subnet definitions.
  * Subnet strings can be in one of three formats:
- *   1: xxx.xxx.xxx.xxx/xx
- *   2: xxx.xxx
- *   3: xxx.xxx.xxx.xxx-xxx   //a range of IP addresses in the last group.
+ *   1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn          (number of bits in net mask)
+ *   2: xxx.xxx.xxx.xxx-yyy or  xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group)
+ *   3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.                  (incomplete address, a bit non-technical ;-)
  * Code for type 1 modified from user posted comments by mediator at
  * {@link http://au.php.net/manual/en/function.ip2long.php}
  *
- * TODO one day we will have to make this work with IP6.
- *
  * @param string $addr    The address you are checking
  * @param string $subnetstr    The string of subnet addresses
  * @return bool
@@ -7292,37 +7290,206 @@ function address_in_subnet($addr, $subnetstr) {
     $subnets = explode(',', $subnetstr);
     $found = false;
     $addr = trim($addr);
+    $addr = cleanremoteaddr($addr, false); // normalise
+    if ($addr === null) {
+        return false;
+    }
+    $addrparts = explode(':', $addr);
+
+    $ipv6 = strpos($addr, ':');
 
     foreach ($subnets as $subnet) {
         $subnet = trim($subnet);
-        if (strpos($subnet, '/') !== false) { /// type 1
+        if ($subnet === '') {
+            continue;
+        }
+
+        if (strpos($subnet, '/') !== false) {
+        ///1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn
             list($ip, $mask) = explode('/', $subnet);
-            if ($mask === '' || $mask > 32) {
-                $mask = 32;
+            $mask = trim($mask);
+            if (!is_number($mask)) {
+                continue; // incorect mask number, eh?
+            }
+            $ip = cleanremoteaddr($ip, false); // normalise
+            if ($ip === null) {
+                continue;
             }
-            $mask = 0xffffffff << (32 - $mask);
-            $found = ((ip2long($addr) & $mask) == (ip2long($ip) & $mask));
-        } else if (strpos($subnet, '-') !== false)  {/// type 3
-            $subnetparts = explode('.', $subnet);
-            $addrparts = explode('.', $addr);
-            $subnetrange = explode('-', array_pop($subnetparts));
-            if (count($subnetrange) == 2) {
-                $lastaddrpart = array_pop($addrparts);
-                $found = ($subnetparts == $addrparts &&
-                        $subnetrange[0] <= $lastaddrpart && $lastaddrpart <= $subnetrange[1]);
+            if (strpos($ip, ':') !== false) {
+                // IPv6
+                if (!$ipv6) {
+                    continue;
+                }
+                if ($mask > 128 or $mask < 0) {
+                    continue; // nonsense
+                }
+                if ($mask == 0) {
+                    return true; // any address
+                }
+                if ($mask == 128) {
+                    if ($ip === $addr) {
+                        return true;
+                    }
+                    continue;
+                }
+                $ipparts = explode(':', $ip);
+                $modulo  = $mask % 16;
+                $ipnet   = array_slice($ipparts, 0, ($mask-$modulo)/16);
+                $addrnet = array_slice($addrparts, 0, ($mask-$modulo)/16);
+                if (implode(':', $ipnet) === implode(':', $addrnet)) {
+                    if ($modulo == 0) {
+                        return true;
+                    }
+                    $pos     = ($mask-$modulo)/16;
+                    $ipnet   = hexdec($ipparts[$pos]);
+                    $addrnet = hexdec($addrparts[$pos]);
+                    $mask    = 0xffff << (16 - $modulo);
+                    if (($addrnet & $mask) == ($ipnet & $mask)) {
+                        return true;
+                    }
+                }
+
+            } else {
+                // IPv4
+                if ($ipv6) {
+                    continue;
+                }
+                if ($mask > 32 or $mask < 0) {
+                    continue; // nonsense
+                }
+                if ($mask == 0) {
+                    return true;
+                }
+                if ($mask == 32) {
+                    if ($ip === $addr) {
+                        return true;
+                    }
+                    continue;
+                }
+                $mask = 0xffffffff << (32 - $mask);
+                if (((ip2long($addr) & $mask) == (ip2long($ip) & $mask))) {
+                    return true;
+                }
             }
-        } else { /// type 2
-            if ($subnet[strlen($subnet) - 1] != '.') {
-                $subnet .= '.';
+
+        } else if (strpos($subnet, '-') !== false)  {
+        /// 2: xxx.xxx.xxx.xxx-yyy or  xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy ...a range of IP addresses in the last group.
+            $parts = explode('-', $subnet);
+            if (count($parts) != 2) {
+                continue;
             }
-            $found = (strpos($addr . '.', $subnet) === 0);
-        }
 
-        if ($found) {
-            break;
+            if (strpos($subnet, ':') !== false) {
+                // IPv6
+                if (!$ipv6) {
+                    continue;
+                }
+                $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
+                if ($ipstart === null) {
+                    continue;
+                }
+                $ipparts = explode(':', $ipstart);
+                $start = hexdec(array_pop($ipparts));
+                $ipparts[] = trim($parts[1]);
+                $ipend = cleanremoteaddr(implode(':', $ipparts), false); // normalise
+                if ($ipend === null) {
+                    continue;
+                }
+                $ipparts[7] = '';
+                $ipnet = implode(':', $ipparts);
+                if (strpos($addr, $ipnet) !== 0) {
+                    continue;
+                }
+                $ipparts = explode(':', $ipend);
+                $end = hexdec($ipparts[7]);
+
+                $addrend = hexdec($addrparts[7]);
+
+                if (($addrend >= $start) and ($addrend <= $end)) {
+                    return true;
+                }
+
+            } else {
+                // IPv4
+                if ($ipv6) {
+                    continue;
+                }
+                $ipstart = cleanremoteaddr(trim($parts[0]), false); // normalise
+                if ($ipstart === null) {
+                    continue;
+                }
+                $ipparts = explode('.', $ipstart);
+                $ipparts[3] = trim($parts[1]);
+                $ipend = cleanremoteaddr(implode('.', $ipparts), false); // normalise
+                if ($ipend === null) {
+                    continue;
+                }
+
+                if ((ip2long($addr) >= ip2long($ipstart)) and (ip2long($addr) <= ip2long($ipend))) {
+                    return true;
+                }
+            }
+
+        } else {
+        /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.
+            if (strpos($subnet, ':') !== false) {
+                // IPv6
+                if (!$ipv6) {
+                    continue;
+                }
+                $parts = explode(':', $subnet);
+                $count = count($parts);
+                if ($parts[$count-1] === '') {
+                    unset($parts[$count-1]); // trim trailing :
+                    $count--;
+                    $subnet = implode('.', $parts);
+                }
+                $isip = cleanremoteaddr($subnet, false); // normalise
+                if ($isip !== null) {
+                    if ($isip === $addr) {
+                        return true;
+                    }
+                    continue;
+                } else if ($count > 8) {
+                    continue;
+                }
+                $zeros = array_fill(0, 8-$count, '0');
+                $subnet = $subnet.':'.implode(':', $zeros).'/'.($count*16);
+                if (address_in_subnet($addr, $subnet)) {
+                    return true;
+                }
+
+            } else {
+                // IPv4
+                if ($ipv6) {
+                    continue;
+                }
+                $parts = explode('.', $subnet);
+                $count = count($parts);
+                if ($parts[$count-1] === '') {
+                    unset($parts[$count-1]); // trim trailing .
+                    $count--;
+                    $subnet = implode('.', $parts);
+                }
+                if ($count == 4) {
+                    $subnet = cleanremoteaddr($subnet, false); // normalise
+                    if ($subnet === $addr) {
+                        return true;
+                    }
+                    continue;
+                } else if ($count > 4) {
+                    continue;
+                }
+                $zeros = array_fill(0, 4-$count, '0');
+                $subnet = $subnet.'.'.implode('.', $zeros).'/'.($count*8);
+                if (address_in_subnet($addr, $subnet)) {
+                    return true;
+                }
+            }
         }
     }
-    return $found;
+
+    return false;
 }
 
 /**
index f500776..8a37a16 100644 (file)
@@ -91,37 +91,92 @@ class moodlelib_test extends MoodleUnitTestCase {
     }
 
     function test_address_in_subnet() {
-        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.1'));
-        $this->assertFalse(address_in_subnet('123.121.234.2', '123.121.234.1'));
-        $this->assertFalse(address_in_subnet('123.121.134.1', '123.121.234.1'));
-        $this->assertFalse(address_in_subnet('113.121.234.1', '123.121.234.1'));
-        $this->assertTrue(address_in_subnet('123.121.234.0', '123.121.234.2/28'));
-        $this->assertTrue(address_in_subnet('123.121.234.15', '123.121.234.2/28'));
-        $this->assertFalse(address_in_subnet('123.121.234.16', '123.121.234.2/28'));
-        $this->assertFalse(address_in_subnet('123.121.234.255', '123.121.234.2/28'));
-        $this->assertTrue(address_in_subnet('123.121.234.0', '123.121.234.0/')); // / is like /32.
-        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.0/'));
-        $this->assertFalse(address_in_subnet('232.232.232.232', '123.121.234.0/0'));
-        $this->assertFalse(address_in_subnet('123.122.234.1', '123.121.'));
-        $this->assertFalse(address_in_subnet('223.121.234.1', '123.121.'));
-        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121'));
-        $this->assertFalse(address_in_subnet('123.122.234.1', '123.121'));
-        $this->assertFalse(address_in_subnet('223.121.234.1', '123.121'));
-        $this->assertFalse(address_in_subnet('123.121.234.100', '123.121.234.10'));
-        $this->assertFalse(address_in_subnet('123.121.234.9', '123.121.234.10-20'));
-        $this->assertTrue(address_in_subnet('123.121.234.10', '123.121.234.10-20'));
-        $this->assertTrue(address_in_subnet('123.121.234.15', '123.121.234.10-20'));
-        $this->assertTrue(address_in_subnet('123.121.234.20', '123.121.234.10-20'));
-        $this->assertFalse(address_in_subnet('123.121.234.21', '123.121.234.10-20'));
-        $this->assertTrue(address_in_subnet('  123.121.234.1  ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertTrue(address_in_subnet('  1.1.2.3 ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertTrue(address_in_subnet('  2.2.234.1  ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertTrue(address_in_subnet('  3.3.3.4  ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertFalse(address_in_subnet('  123.121.234.2  ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertFalse(address_in_subnet('  2.1.2.3 ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertFalse(address_in_subnet('  2.3.234.1  ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertFalse(address_in_subnet('  3.3.3.7  ', '  123.121.234.1  , 1.1.1.1/16,2.2.,3.3.3.3-6  '));
-        $this->assertFalse(address_in_subnet('172.16.1.142', '172.16.1.143/148'));
+    /// 1: xxx.xxx.xxx.xxx/nn or xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/nnn          (number of bits in net mask)
+        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.1/32'));
+        $this->assertFalse(address_in_subnet('123.121.23.1', '123.121.23.0/32'));
+        $this->assertTrue(address_in_subnet('10.10.10.100',  '123.121.23.45/0'));
+        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/24'));
+        $this->assertFalse(address_in_subnet('123.121.34.1', '123.121.234.0/24'));
+        $this->assertTrue(address_in_subnet('123.121.234.1', '123.121.234.0/30'));
+        $this->assertFalse(address_in_subnet('123.121.23.8', '123.121.23.0/30'));
+        $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128'));
+        $this->assertFalse(address_in_subnet('bab:baba::baba', 'bab:baba::cece/128'));
+        $this->assertTrue(address_in_subnet('baba:baba::baba', 'cece:cece::cece/0'));
+        $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba/128'));
+        $this->assertTrue(address_in_subnet('baba:baba::00ba', 'baba:baba::/120'));
+        $this->assertFalse(address_in_subnet('baba:baba::aba', 'baba:baba::/120'));
+        $this->assertTrue(address_in_subnet('baba::baba:00ba', 'baba::baba:0/112'));
+        $this->assertFalse(address_in_subnet('baba::aba:00ba', 'baba::baba:0/112'));
+        $this->assertFalse(address_in_subnet('aba::baba:0000', 'baba::baba:0/112'));
+
+        // fixed input
+        $this->assertTrue(address_in_subnet('123.121.23.1   ', ' 123.121.23.0 / 24'));
+        $this->assertTrue(address_in_subnet('::ffff:10.1.1.1', ' 0:0:0:000:0:ffff:a1:10 / 126'));
+
+        // incorrect input
+        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/-2'));
+        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/64'));
+        $this->assertFalse(address_in_subnet('123.121.234.x', '123.121.234.1/24'));
+        $this->assertFalse(address_in_subnet('123.121.234.0', '123.121.234.xx/24'));
+        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.1/xx0'));
+        $this->assertFalse(address_in_subnet('::1', '::aa:0/xx0'));
+        $this->assertFalse(address_in_subnet('::1', '::aa:0/-5'));
+        $this->assertFalse(address_in_subnet('::1', '::aa:0/130'));
+        $this->assertFalse(address_in_subnet('x:1', '::aa:0/130'));
+        $this->assertFalse(address_in_subnet('::1', '::ax:0/130'));
+
+
+    /// 2: xxx.xxx.xxx.xxx-yyy or  xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx::xxxx-yyyy (a range of IP addresses in the last group)
+        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12-14'));
+        $this->assertTrue(address_in_subnet('123.121.234.13', '123.121.234.12-14'));
+        $this->assertTrue(address_in_subnet('123.121.234.14', '123.121.234.12-14'));
+        $this->assertFalse(address_in_subnet('123.121.234.1', '123.121.234.12-14'));
+        $this->assertFalse(address_in_subnet('123.121.234.20', '123.121.234.12-14'));
+        $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.234.12-14'));
+        $this->assertFalse(address_in_subnet('123.12.234.12', '123.121.234.12-14'));
+        $this->assertTrue(address_in_subnet('baba:baba::baba', 'baba:baba::baba-babe'));
+        $this->assertTrue(address_in_subnet('baba:baba::babc', 'baba:baba::baba-babe'));
+        $this->assertTrue(address_in_subnet('baba:baba::babe', 'baba:baba::baba-babe'));
+        $this->assertFalse(address_in_subnet('bab:baba::bab0', 'bab:baba::baba-babe'));
+        $this->assertFalse(address_in_subnet('bab:baba::babf', 'bab:baba::baba-babe'));
+        $this->assertFalse(address_in_subnet('bab:baba::bfbe', 'bab:baba::baba-babe'));
+        $this->assertFalse(address_in_subnet('bfb:baba::babe', 'bab:baba::baba-babe'));
+
+        // fixed input
+        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12 - 14 '));
+        $this->assertTrue(address_in_subnet('bab:baba::babe', 'bab:baba::baba - babe  '));
+
+        // incorrect input
+        $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-234.14'));
+        $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12-256'));
+        $this->assertFalse(address_in_subnet('123.121.234.12', '123.121.234.12--256'));
+
+
+    /// 3: xxx.xxx or xxx.xxx. or xxx:xxx:xxxx or xxx:xxx:xxxx.                  (incomplete address, a bit non-technical ;-)
+        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.12'));
+        $this->assertFalse(address_in_subnet('123.121.23.12', '123.121.23.13'));
+        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234.'));
+        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121.234'));
+        $this->assertTrue(address_in_subnet('123.121.234.12', '123.121'));
+        $this->assertTrue(address_in_subnet('123.121.234.12', '123'));
+        $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234.'));
+        $this->assertFalse(address_in_subnet('123.121.234.1', '12.121.234'));
+        $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba::bab'));
+        $this->assertFalse(address_in_subnet('baba:baba::ba', 'baba:baba::bc'));
+        $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:baba'));
+        $this->assertTrue(address_in_subnet('baba:baba::bab', 'baba:'));
+        $this->assertFalse(address_in_subnet('bab:baba::bab', 'baba:'));
+
+
+    /// multiple subnets
+        $this->assertTrue(address_in_subnet('123.121.234.12', '::1/64, 124., 123.121.234.10-30'));
+        $this->assertTrue(address_in_subnet('124.121.234.12', '::1/64, 124., 123.121.234.10-30'));
+        $this->assertTrue(address_in_subnet('::2',            '::1/64, 124., 123.121.234.10-30'));
+        $this->assertFalse(address_in_subnet('12.121.234.12', '::1/64, 124., 123.121.234.10-30'));
+
+
+    /// other incorrect input
+        $this->assertFalse(address_in_subnet('123.123.123.123', ''));
     }
 
     /**