Danny Connolly
Danny Connolly

Reputation: 899

How can I check if incoming IP is within a subnet mask using PHP?

I have a Laravel app where I have created middleware for an API route to check the request IP against a whitelist of IP addresses stored in the .env file (I would like to store the IPs in a database table but not sure of best way to store the IP addresses with subnet but that's a question for another day).

I now need to add an IP with subnet mask to the whitelist. How can I modify the code below to check if the request IP is within the subnet range?

$requestIP = '12.23.34.56';

// Original array of IP addresses
// $whitelist = collect([
//   '127.0.0.1',
// ]);

$whitelist = collect([
   '127.0.0.1',
   '12.23.34.0/27'
]);

if ($whitelist->contains($request->ip())) {
    return $next($request);
}

abort(response('Unauthorized IP', 403));

Upvotes: 0

Views: 67

Answers (1)

Wiimm
Wiimm

Reputation: 3570

Here is a solution, but only for IPv4. I'm note sure if this code runs at 32-bit systems.

<?PHP

$testlist = [
    '127.0.0.1',
    '127.0.0.2',
    '12.23.34.1',
    '12.23.34.56',
    '12.23.35.56',
];

$whitelist = [
    '127.0.0.1',
    '12.23.34.0/27',
];

function checkip ( $ip )
{
    global $whitelist;

    //--- convert whilelist to a data structure for faster checks
    static $reflist = NULL;
    if (!isset($reflist))
    {
        $reflist = [];
        foreach ( $whitelist as $allow )
        {
            $p = strpos($allow,'/');
            if ( $p === false )
            {
                $num = ip2long($allow);
                $bits = 32;
            }
            else
            {
                $num = ip2long(substr($allow,0,$p));
                $bits = intval(substr($allow,$p+1));
            }
            if ( $num !== false && $bits > 0 && $bits <= 32 )
            {
                $mask = ( 0xffffffff<<(32-$bits) ) & 0xffffffff;
                printf("%08x %08x\n",$num&$mask,$mask);
                $reflist[] = [$num&$mask,$mask];
            }
        }
    }

    //---check ipv4
    $ip = ip2long($ip);
    if ( $ip !== false )
    {
        foreach ( $reflist as $d )
            if ( $d[0] == ( $ip & $d[1] ))
                return true;
    }

    return false;
}

foreach ( $testlist as $ip )
    printf("%15s : %d\n",$ip,checkip($ip));

?>

Change && $bits > 0 && to && $bits >= 0 && if you want to allow 0.0.0.0/0 for all ipv4 addresses.

Upvotes: 1

Related Questions