Match IPv4 address given IP range/mask?

后端 未结 6 667
一生所求
一生所求 2020-12-28 12:06

Either with PHP or a RegExp (or both), how do I match a range of IP addresses?

Sample Incoming IPs

10.210.12.12
10.253.12.12
10.210.12.254
10.210.1         


        
相关标签:
6条回答
  • 2020-12-28 12:11

    Regex really doesn't sound like the right tool to deal with subnet masks (at least not in decimal). It can be done, but it will be ugly.

    I strongly suggest parsing the string into 4 integers, combining to a 32-bit int, and then using standard bitwise operations (basically a bitwise-AND, and then a comparison).

    0 讨论(0)
  • 2020-12-28 12:13

    Convert to 32 bit unsigned and use boolean/bitwise operations.

    For example, convert 192.168.25.1 to 0xC0A81901.

    Then, you can see if it matches the mask 192.168.25/24 by converting the dotted-decimal portion of the mask, i.e., 0xC0A81900, and creating a 24 bit mask, i.e., 0xFFFFFF00.

    Perform a bitwise AND between the address in question and the mask and compare to the dotted decimal portion of the mask specification. For example,

    0xC0A81901 AND 0xFFFFFF00 ==> 0xC0A81900 (result)
    
    compare 0xC0A81900 (result) to 0xC0A81900.
    

    I don't know PHP, but google tells me that PHP has inet_pton(), which is what I would use in C to perform the conversion from dotted-decimal to n-bit unsigned. See http://php.net/manual/en/function.inet-pton.php

    0 讨论(0)
  • 2020-12-28 12:23

    I adapted an answer from php.net and made it better.

    function netMatch($network, $ip) {
        $network=trim($network);
        $orig_network = $network;
        $ip = trim($ip);
        if ($ip == $network) {
            echo "used network ($network) for ($ip)\n";
            return TRUE;
        }
        $network = str_replace(' ', '', $network);
        if (strpos($network, '*') !== FALSE) {
            if (strpos($network, '/') !== FALSE) {
                $asParts = explode('/', $network);
                $network = @ $asParts[0];
            }
            $nCount = substr_count($network, '*');
            $network = str_replace('*', '0', $network);
            if ($nCount == 1) {
                $network .= '/24';
            } else if ($nCount == 2) {
                $network .= '/16';
            } else if ($nCount == 3) {
                $network .= '/8';
            } else if ($nCount > 3) {
                return TRUE; // if *.*.*.*, then all, so matched
            }
        }
    
        echo "from original network($orig_network), used network ($network) for ($ip)\n";
    
        $d = strpos($network, '-');
        if ($d === FALSE) {
            $ip_arr = explode('/', $network);
            if (!preg_match("@\d*\.\d*\.\d*\.\d*@", $ip_arr[0], $matches)){
                $ip_arr[0].=".0";    // Alternate form 194.1.4/24
            }
            $network_long = ip2long($ip_arr[0]);
            $x = ip2long($ip_arr[1]);
            $mask = long2ip($x) == $ip_arr[1] ? $x : (0xffffffff << (32 - $ip_arr[1]));
            $ip_long = ip2long($ip);
            return ($ip_long & $mask) == ($network_long & $mask);
        } else {
            $from = trim(ip2long(substr($network, 0, $d)));
            $to = trim(ip2long(substr($network, $d+1)));
            $ip = ip2long($ip);
            return ($ip>=$from and $ip<=$to);
        }
    }
    
    function ech($b) {
        if ($b) {
            echo "MATCHED\n";
        } else {
            echo "DID NOT MATCH\n";
        }
    }
    
    echo "CLASS A TESTS\n";
    ech(netMatch('10.168.1.0-10.168.1.100', '10.168.1.90'));
    ech(netMatch('10.168.*.*', '10.168.1.90'));
    ech(netMatch('10.168.0.0/16', '10.168.1.90'));
    ech(netMatch('10.169.1.0/24', '10.168.1.90'));
    ech(netMatch('10.168.1.90', '10.168.1.90'));
    echo "\nCLASS B TESTS\n";
    ech(netMatch('130.168.1.0-130.168.1.100', '130.168.1.90'));
    ech(netMatch('130.168.*.*', '130.168.1.90'));
    ech(netMatch('130.168.0.0/16', '130.168.1.90'));
    ech(netMatch('130.169.1.0/24', '130.168.1.90'));
    ech(netMatch('130.168.1.90', '130.168.1.90'));
    echo "\nCLASS C TESTS\n";
    ech(netMatch('192.168.1.0-192.168.1.100', '192.168.1.90'));
    ech(netMatch('192.168.*.*', '192.168.1.90'));
    ech(netMatch('192.168.0.0/16', '192.168.1.90'));
    ech(netMatch('192.169.1.0/24', '192.168.1.90'));
    ech(netMatch('192.168.1.90', '192.168.1.90'));
    echo "\nCLASS D TESTS\n";
    ech(netMatch('230.168.1.0-230.168.1.100', '230.168.1.90'));
    ech(netMatch('230.168.*.*', '230.168.1.90'));
    ech(netMatch('230.168.0.0/16', '230.168.1.90'));
    ech(netMatch('230.169.1.0/24', '230.168.1.90'));
    ech(netMatch('230.168.1.90', '230.168.1.90'));
    echo "\nCLASS E TESTS\n";
    ech(netMatch('250.168.1.0-250.168.1.100', '250.168.1.90'));
    ech(netMatch('250.168.*.*', '250.168.1.90'));
    ech(netMatch('250.168.0.0/16', '250.168.1.90'));
    ech(netMatch('250.169.1.0/24', '250.168.1.90'));
    ech(netMatch('250.168.1.90', '250.168.1.90'));
    

    This results with:

    CLASS A TESTS
    from orig network (10.168.1.0-10.168.1.100) used network (10.168.1.0-10.168.1.100) for (10.168.1.90)
    MATCHED
    from orig network (10.168.*.*) used network (10.168.0.0/16) for (10.168.1.90)
    MATCHED
    from orig network (10.168.0.0/16) used network (10.168.0.0/16) for (10.168.1.90)
    MATCHED
    from orig network (10.169.1.0/24) used network (10.169.1.0/24) for (10.168.1.90)
    DID NOT MATCH
    used network (10.168.1.90) for (10.168.1.90)
    MATCHED
    
    CLASS B TESTS
    from orig network (130.168.1.0-130.168.1.100) used network (130.168.1.0-130.168.1.100) for (130.168.1.90)
    MATCHED
    from orig network (130.168.*.*) used network (130.168.0.0/16) for (130.168.1.90)
    MATCHED
    from orig network (130.168.0.0/16) used network (130.168.0.0/16) for (130.168.1.90)
    MATCHED
    from orig network (130.169.1.0/24) used network (130.169.1.0/24) for (130.168.1.90)
    DID NOT MATCH
    used network (130.168.1.90) for (130.168.1.90)
    MATCHED
    
    CLASS C TESTS
    from orig network (192.168.1.0-192.168.1.100) used network (192.168.1.0-192.168.1.100) for (192.168.1.90)
    MATCHED
    from orig network (192.168.*.*) used network (192.168.0.0/16) for (192.168.1.90)
    MATCHED
    from orig network (192.168.0.0/16) used network (192.168.0.0/16) for (192.168.1.90)
    MATCHED
    from orig network (192.169.1.0/24) used network (192.169.1.0/24) for (192.168.1.90)
    DID NOT MATCH
    used network (192.168.1.90) for (192.168.1.90)
    MATCHED
    
    CLASS D TESTS
    from orig network (230.168.1.0-230.168.1.100) used network (230.168.1.0-230.168.1.100) for (230.168.1.90)
    MATCHED
    from orig network (230.168.*.*) used network (230.168.0.0/16) for (230.168.1.90)
    MATCHED
    from orig network (230.168.0.0/16) used network (230.168.0.0/16) for (230.168.1.90)
    MATCHED
    from orig network (230.169.1.0/24) used network (230.169.1.0/24) for (230.168.1.90)
    DID NOT MATCH
    used network (230.168.1.90) for (230.168.1.90)
    MATCHED
    
    CLASS E TESTS
    from orig network (250.168.1.0-250.168.1.100) used network (250.168.1.0-250.168.1.100) for (250.168.1.90)
    MATCHED
    from orig network (250.168.*.*) used network (250.168.0.0/16) for (250.168.1.90)
    MATCHED
    from orig network (250.168.0.0/16) used network (250.168.0.0/16) for (250.168.1.90)
    MATCHED
    from orig network (250.169.1.0/24) used network (250.169.1.0/24) for (250.168.1.90)
    DID NOT MATCH
    used network (250.168.1.90) for (250.168.1.90)
    MATCHED
    
    0 讨论(0)
  • 2020-12-28 12:24

    Use strpos to match them as strings.

    <?php
    $ips = array();
    $ips[0] = "10.210.12.12";
    $ips[1] = "10.253.12.12";
    $ips[2] = "10.210.12.254";
    $ips[3] = "10.210.12.95";
    $ips[4] = "10.210.12.60";
    
    $matches = array();
    
    foreach($ips as $ip){
        if(strpos($ip, "10.253.") === 0){
            $matches[] = $ip;
        }
    }
    
    print_r($matches);
    ?>
    
    0 讨论(0)
  • 2020-12-28 12:26

    I've improved on the above example (I have a netmask with /29 so it doesn't work).

    function check_netmask($mask, $ip) {
        @list($net, $bits) = explode('/', $mask);
        $bits = isset($bits) ? $bits : 32;
        $bitmask = -pow(2, 32-$bits) & 0x00000000FFFFFFFF;
        $netmask = ip2long($net) & $bitmask;
        $ip_bits = ip2long($ip)  & $bitmask;
        return (($netmask ^ $ip_bits) == 0);
    }
    

    If you want to see it in action, add this:

    print("IP Bits: " . str_pad(decbin(ip2long($ip)), 32, '0', STR_PAD_LEFT));
    print "\n";
    print("Bitmask: " . str_pad(decbin($bitmask), 32, '0', STR_PAD_LEFT));
    print "\n";
    print("Netmask: " . str_pad(decbin($netmask), 32, '0', STR_PAD_LEFT));
    print "\n";
    print("Match:   " . str_pad(decbin($netmask ^ $ip_bits), 32, '0', STR_PAD_LEFT));
    print "\n";
    

    Run it with something like this:

    print var_dump(check_netmask($argv[1], $argv[2]));
    
    0 讨论(0)
  • 2020-12-28 12:27

    Use this library: https://github.com/S1lentium/IPTools

    //Check if IP is within Range:
    
    echo Range::parse('192.168.1.1-192.168.1.254')->contains(new IP('192.168.1.5')); // true
    echo Range::parse('::1-::ffff')->contains(new IP('::1234')); // true
    
    0 讨论(0)
提交回复
热议问题