3

Most questions I've seen regarding validating private IPs in PHP have to do with validating if a specific IP address is private or not, or whether an IP exists within a specific range.

However, I want to be able to determine in PHP whether or not an IP range, given in the format of e.g. "X.X.X.X - Y.Y.Y.Y" is an exclusively private range. Just so it's clear, I want to see if the entire range is private or not. Examples:

10.0.0.1 - 10.0.0.14 would return true since all IPs in this range are internal.

10.0.0.1 - 127.0.0.16 would return false because not all of the IPs in the range are internal/private, even though the start and end points are.

My initial thought was to just validate the start and end IPs, and if they're internal, then all good. But, as I said above, if I had a range like $range = '10.0.0.1 - 127.0.0.16', while the start and end IP addresses are both considered to be private IP addresses, it spans IP addresses that are not internal, hence it's not an exclusively internal IP address range.

I could perhaps generate every single IP address within the range, and validate each one, but this seems incredibly inefficient.

Is there an easier and more efficient way of doing this?

Edit: Just to make it more explicitly clear to those flagging this as a duplicate: I am not trying to validate a single given IP and see if it is private. I want to check that every single possible IP in a given range of the format $range = '10.0.0.1 - 127.0.0.16' is private. Doing something like this is far too slow and inefficient (assuming I've exploded the string to get the start and end IP addresses):

<?php
function checkRange($start, $end)
{
    $start = ip2long($start);
    $end = ip2long($end);

    for ($i = $start; $i <= $end; $i++) {
        $ip = long2ip($i);
        if (!filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE | FILTER_FLAG_IPV4
        )) {
            continue;
        }
        return false;
    }
    return true;
}
Craig Sefton
  • 903
  • 11
  • 20
  • 1
    Online ipv4 addresses? if you split to the addresses you could then do `$sections = explode('.',$ip_range);` and `if (($sections[0] == 192 && $sections[1] == 168) || ($sections[0] == 172 && ($sections[1] >= 16 && $sections[1] <= 32)) || ($sections[0] == 10)) {` or you could put together a regex. – chris85 Aug 04 '17 at 14:51
  • 3
    Use http://php.net/manual/en/function.ip2long.php so it's just a couple less/greater-than checks against the defined private ranges. – ceejayoz Aug 04 '17 at 14:51
  • Possible duplicate of [Check if an IP address is private](https://stackoverflow.com/questions/13818064/check-if-an-ip-address-is-private) – iXCray Aug 04 '17 at 14:57
  • 1
    @iXCray As I said, I'm not wanting to check if an IP address is private, I want to check if the entire defined range in the format of `10.0.0.1 - 127.0.0.16` is private i.e. every possible IP in that range is private. – Craig Sefton Aug 04 '17 at 14:59
  • @ceejayoz could you elaborate how ip2long would help here? – Craig Sefton Aug 04 '17 at 15:15
  • 2
    Possible duplicate of [How to check an IP address is within a range of two IPs in PHP?](https://stackoverflow.com/questions/11121817/how-to-check-an-ip-address-is-within-a-range-of-two-ips-in-php) – bishop Aug 04 '17 at 15:16
  • @bishop As I noted in my edit, I am not asking how to check an IP address belongs to a specified range. – Craig Sefton Aug 04 '17 at 16:03

2 Answers2

3

All private ranges in the IP space are separated by public ranges (p for private, _ for public):

__ppp___pppp___ppp___

If you want a user defined range to be completely private, it needs to be fully contained in one of the private ranges (u for user-defined all-private ranges = hits, x for wrong ranges, fails):

__ppp___pppp___ppp___
   uu
  u
    xxxxxxx
          uu
               u

Thus, we need to check whether both input values ($start and $end) are not only private, but also in the same private range.

function checkRange($start, $end)
{
    $start = ip2long($start);
    $end   = ip2long($end);

    if (!$start || !$end)
        throw new Exception('Invalid input.');

    if ($start > $end)
        throw new Exception('Invalid range.'); // Alternative: switch $start and $end

    $range1_start = ip2long('10.0.0.0');
    $range1_end   = ip2long('10.255.255.255');
    $range2_start = ip2long('172.16.0.0');
    $range2_end   = ip2long('172.31.255.255');
    $range3_start = ip2long('192.168.0.0');
    $range3_end   = ip2long('192.168.255.255');

    return ($start >= $range1_start && $start <= $range1_end &&
            $end   >= $range1_start && $end   <= $range1_end) ||
           ($start >= $range2_start && $start <= $range2_end &&
            $end   >= $range2_start && $end   <= $range2_end) ||
           ($start >= $range3_start && $start <= $range3_end &&
            $end   >= $range3_start && $end   <= $range3_end);

}
Imanuel
  • 3,596
  • 4
  • 24
  • 46
  • In my context, there is no requirement that both IP addresses must be part of the same private range. – Craig Sefton Aug 04 '17 at 16:03
  • 1
    If they are part of different private ranges, there will be public ranges between them. Thus, the range between the two addresses is definitely not all private. This is the reason your second example would return `false`. – Imanuel Aug 04 '17 at 16:12
0

An IP address is a 32 bit number. It can be represented as four decimals separated by a period, each decimal being 8 bits of the 32 bit number in base 10. PHP's built-in ip2long converts from the IPv4 string notation to a standard 32 bit integer.

With this in mind (IPv4 string notation strings are just 32 bit integers) you can easily come up with an algorithm that checks if an integer is in a certain range or if a range is inside another.

<?php

function check($start, $end) {
    $r10Start = ip2long('10.0.0.0');
    $r10End = ip2long('10.255.255.255');

    $r127Start = ip2long('127.0.0.0');
    $r127End = ip2long('127.255.255.255');

    if (
        ($start >= $r10Start && $end <= $r10End)
        || ($start >= $r127Start && $end <= $r127End)
    ) {
        return true;
    }

    return false;
}

var_dump(check(ip2long('10.0.0.1'), ip2long('10.0.0.14')));
var_dump(check(ip2long('10.0.0.1'), ip2long('127.0.0.16')));
Sergiu Paraschiv
  • 9,929
  • 5
  • 36
  • 47