[Challenge] IPv4 subnet mask validator

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: [Challenge] IPv4 subnet mask validator

Post by VladSun »

My solution is:

Code: Select all

/**
 * @param String $mask - decimal, dot separated representation of an IPv4 subnet mask - e.g. "255.255.255.0"
 * @return bool
 */
function isValidIPv4Mask($mask)
{
 	$mask = ip2long($mask);

	if ($mask == -1 || !$mask) return false;

	while (!($mask & 1))
		$mask >>= 1;

	return $mask == -1;
}
Probably it won't behave properly on 64 bit PHP versions:
on 64 bits system ip2long ONLY RETURNS POSITIVE VALUES

so

<?php
echo ip2long('200.200.200.200');
?>

will output -926365496 on a 32 bits system and 3368601800 on a 64 bits system
so it will need 0xFFFFFFFF instead of -1 comparison.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: [Challenge] IPv4 subnet mask validator

Post by Weirdan »

Probably it won't behave properly on 64 bit PHP versions:
I think this is easy to fix - just invert the mask and then invert the checks. Here's intsize-independent version:

Code: Select all

$tests = array(
    '255.255.255.0' => true,
    '255.255.0.255' => false,
    '0.0.0.0' => false,
    '255.255.255.255' => false,
    '0.255.255.255' => false,
);
function isValidIPv4Mask($mask) {
    $mask = ~ip2long($mask) & (int) 0xffffffff;

    if ($mask == (int) 0xffffffff || !$mask) return false;

    while (($mask & 1))
        $mask >>= 1;

    return $mask == 0;
}

var_dump('Int size: ' . PHP_INT_SIZE);
foreach ($tests as $mask => $expected) {
    assert(var_export($expected, true) . ' === isValidIPv4Mask(' . var_export($mask, true) . ')');
}
var_dump('done');
string(11) "Int size: 4"
string(4) "done"
string(11) "Int size: 8"
string(4) "done"
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: [Challenge] IPv4 subnet mask validator

Post by VladSun »

Nice fix, Weirdan :)
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
timWebUK
Forum Contributor
Posts: 239
Joined: Thu Oct 29, 2009 6:48 am
Location: UK

Re: [Challenge] IPv4 subnet mask validator

Post by timWebUK »

Weirdan wrote:
timWebUK wrote:Am I missing something but...
255.255.255. (0, 128, 192, 224, 240, 248, 252, 254)
Are all valid subnet masks...
They are. What made you think they are not considered as such?
I guess I misunderstood "A valid subnet mask value is a one that has leftmost bits set to '1' and the rest rightmost bits set to '0'."
User avatar
AbraCadaver
DevNet Master
Posts: 2572
Joined: Mon Feb 24, 2003 10:12 am
Location: The Republic of Texas
Contact:

Re: [Challenge] IPv4 subnet mask validator

Post by AbraCadaver »

VladSun wrote:Very creative, AbraCadaver :)

The first one fails on 0.255.255.0 mask.

The second one is OK and its time is ~0.8 sec :) It also shares the same idea as the one used in my solution :)

The third one fails on 255.255.255.254 mask :(
First one used decbin() that removes the preceding zeros. Replaced with sprintf():

Code: Select all

function isValidIPv4Mask($mask)
{
    return ($bin=sprintf('%032b',ip2long($mask))) && strpos($bin,'0') && !strpos($bin,'01');
}
Third one used a + instead of *, easy fix:

Code: Select all

function isValidIPv4Mask($mask)
{
    return (bool)preg_match('/^[1]+0[^1]*$/',sprintf('%032b',ip2long($mask)));
}
mysql_function(): WARNING: This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information.
minorDemocritus
Forum Commoner
Posts: 96
Joined: Thu Apr 01, 2010 7:28 pm
Location: Chicagoland, IL, USA

Re: [Challenge] IPv4 subnet mask validator

Post by minorDemocritus »

timWebUK wrote:
Weirdan wrote:
timWebUK wrote:Am I missing something but...
255.255.255. (0, 128, 192, 224, 240, 248, 252, 254)
Are all valid subnet masks...
They are. What made you think they are not considered as such?
I guess I misunderstood "A valid subnet mask value is a one that has leftmost bits set to '1' and the rest rightmost bits set to '0'."
(dots added for clarity)
11111111.11111111.11111111.00000000 = 255.255.255.0
11111111.11111111.00000000.00000000 = 255.255.0.0
11111111.11111111.11110000.00000000 = 255.255.240.0
11111111.11111111.11111111.11111110 = 255.255.255.254

These are all valid subnet masks. Notice the leftmost bits are 1, and rightmost bits are 0.

11111111.11111111.01111111.00000000 = 255.255.127.0 is NOT a valid mask.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: [Challenge] IPv4 subnet mask validator

Post by VladSun »

@AbraCadaver:

All tests passed :)

Speed test results:
- first function : ~ 1.08 sec;
- second one : ~ 1.08 sec :)

I expected you would write a binary logic solution ;)
"There are only 10 types of people in the world: Those who understand binary, and those who don't."
:twisted:
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: [Challenge] IPv4 subnet mask validator

Post by VladSun »

Weirdan wrote:
Probably it won't behave properly on 64 bit PHP versions:
I think this is easy to fix - just invert the mask and then invert the checks.
I've just tested your function and it appears it has a minor problem - if ip2long() returns false (i.e. improper IPv4 format) a fatal error is raised - "Unsupported operand types".

I don't have a 64 bit PHP version to test right now, but it seems to me that one doesn't need to invert the logic - I think the (int)0xFFFFFFFF typecast is just enough (it will return -1 on 32 and 0xFFFFFFFF on 64 bit platforms):

Code: Select all

function isValidIPv4Mask($mask)
{
        $mask = ip2long($mask);

        if ($mask == (int) 0xffffffff || !$mask) return false;

        while (!($mask & 1))
                $mask >>= 1;

        return $mask == (int) 0xffffffff;
}
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
timWebUK
Forum Contributor
Posts: 239
Joined: Thu Oct 29, 2009 6:48 am
Location: UK

Re: [Challenge] IPv4 subnet mask validator

Post by timWebUK »

minorDemocritus wrote:
timWebUK wrote:
Weirdan wrote: They are. What made you think they are not considered as such?
I guess I misunderstood "A valid subnet mask value is a one that has leftmost bits set to '1' and the rest rightmost bits set to '0'."
(dots added for clarity)
11111111.11111111.11111111.00000000 = 255.255.255.0
11111111.11111111.00000000.00000000 = 255.255.0.0
11111111.11111111.11110000.00000000 = 255.255.240.0
11111111.11111111.11111111.11111110 = 255.255.255.254

These are all valid subnet masks. Notice the leftmost bits are 1, and rightmost bits are 0.

11111111.11111111.01111111.00000000 = 255.255.127.0 is NOT a valid mask.
Thanks for the clarification!
User avatar
AbraCadaver
DevNet Master
Posts: 2572
Joined: Mon Feb 24, 2003 10:12 am
Location: The Republic of Texas
Contact:

Re: [Challenge] IPv4 subnet mask validator

Post by AbraCadaver »

VladSun wrote:@AbraCadaver:

All tests passed :)

Speed test results:
- first function : ~ 1.08 sec;
- second one : ~ 1.08 sec :)

I expected you would write a binary logic solution ;)
"There are only 10 types of people in the world: Those who understand binary, and those who don't."
:twisted:
Not until I can think of one without a loop, or maybe just a loop with no other code outside the loop.
mysql_function(): WARNING: This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information.
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: [Challenge] IPv4 subnet mask validator

Post by Weirdan »

VladSun wrote: I don't have a 64 bit PHP version to test right now, but it seems to me that one doesn't need to invert the logic - I think the (int)0xFFFFFFFF typecast is just enough (it will return -1 on 32 and 0xFFFFFFFF on 64 bit platforms):
No, that wouldn't work, because the algorithm you use relies on right shift operator extending the sign bit, and ip2long on 64bit platform returns positive numbers only. On 32bit platform for valid ip mask it always return negative number. See for yourself:

Code: Select all

string(11) "Int size: 8"

Warning: assert(): Assertion "true === isValidIPv4Mask('255.255.255.0')" failed in /home/weirdan/q.php on line 23
string(4) "done"
using these tests:

Code: Select all

$tests = array(
    '255.255.255.0' => true,
    '255.255.0.255' => false,
    '0.0.0.0' => false,
    '255.255.255.255' => false,
    '0.255.255.255' => false,
);
var_dump('Int size: ' . PHP_INT_SIZE);
foreach ($tests as $mask => $expected) {
    assert(var_export($expected, true) . ' === isValidIPv4Mask(' . var_export($mask, true) . ')');
}
var_dump('done');
User avatar
Mordred
DevNet Resident
Posts: 1579
Joined: Sun Sep 03, 2006 5:19 am
Location: Sofia, Bulgaria

Re: [Challenge] IPv4 subnet mask validator

Post by Mordred »

Considering this:
In this challenge, these subnet masks are also considered invalid:
255.255.255.255 (11111111 11111111 11111111 11111111)
0.0.0.0 (00000000 00000000 00000000 00000000)
"Trick" solution:

Code: Select all

function isValidIPv4Mask($mask)
{
    $n = ip2long($mask);
    return $n===false ? $n : !(~($n-1) & ~($n));
}
But of course the "boring" solution is faster:

Code: Select all

$s_ValidMasks = Array(
'255.255.255.255' => true,
'255.255.255.254' => true,
'255.255.255.252' => true,
'255.255.255.248' => true,
'255.255.255.240' => true,
'255.255.255.224' => true,
'255.255.255.192' => true,
'255.255.255.128' => true,
'255.255.255.0' => true,
'255.255.254.0' => true,
'255.255.252.0' => true,
'255.255.248.0' => true,
'255.255.240.0' => true,
'255.255.224.0' => true,
'255.255.192.0' => true,
'255.255.128.0' => true,
'255.255.0.0' => true,
'255.254.0.0' => true,
'255.252.0.0' => true,
'255.248.0.0' => true,
'255.240.0.0' => true,
'255.224.0.0' => true,
'255.192.0.0' => true,
'255.128.0.0' => true,
'255.0.0.0' => true,
'254.0.0.0' => true,
'252.0.0.0' => true,
'248.0.0.0' => true,
'240.0.0.0' => true,
'224.0.0.0' => true,
'192.0.0.0' => true,
'128.0.0.0' => true,
'0.0.0.0' => true,
);

function isValidIPv4Mask($mask)
{
    global $s_ValidMasks;
    return isset($s_ValidMasks[$mask]);
}
User avatar
AbraCadaver
DevNet Master
Posts: 2572
Joined: Mon Feb 24, 2003 10:12 am
Location: The Republic of Texas
Contact:

Re: [Challenge] IPv4 subnet mask validator

Post by AbraCadaver »

Mordred wrote:Considering this:
In this challenge, these subnet masks are also considered invalid:
255.255.255.255 (11111111 11111111 11111111 11111111)
0.0.0.0 (00000000 00000000 00000000 00000000)
"Trick" solution:

Code: Select all

function isValidIPv4Mask($mask)
{
    $n = ip2long($mask);
    return $n===false ? $n : !(~($n-1) & ~($n));
}
Unfortunately the "trick" is failing on several:
[text]
255.255.255.0 true
255.255.0.255 false
0.0.0.0 true
255.255.255.255 true
0.255.255.255 false
255 false
0 true[/text]
mysql_function(): WARNING: This extension is deprecated as of PHP 5.5.0, and will be removed in the future. Instead, the MySQLi or PDO_MySQLextension should be used. See also MySQL: choosing an API guide and related FAQ for more information.
User avatar
Apollo
Forum Regular
Posts: 794
Joined: Wed Apr 30, 2008 2:34 am

Re: [Challenge] IPv4 subnet mask validator

Post by Apollo »

Bit trickery FTW:

Code: Select all

function isValidIPv4Mask($mask)
{
  $m = ~ip2long($mask);	
  return $m && ~$m && !($m&($m+1));
}
User avatar
Weirdan
Moderator
Posts: 5978
Joined: Mon Nov 03, 2003 6:13 pm
Location: Odessa, Ukraine

Re: [Challenge] IPv4 subnet mask validator

Post by Weirdan »

Mordred wrote:But of course the "boring" solution is faster
Are you sure? In my tests table lookup was slower than Vlad's solution.
Post Reply