LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   How to check client IP against list of IP ranges in PHP? (https://www.linuxquestions.org/questions/programming-9/how-to-check-client-ip-against-list-of-ip-ranges-in-php-669539/)

win32sux 09-12-2008 02:48 PM

How to check client IP against list of IP ranges in PHP?
 
I'm using $_SERVER['REMOTE_ADDR'] to get the client's IP. I would like to set a certain variable to TRUE if the IP is within one of the ranges I've got in a text file (I guess I could put the ranges into the database if needed). My list is currently formatted like this:
Code:

123.123.123.123-123.123.200.200
124.129.0.123-124.129.200.200
231.160.124.1-231.255.255.255

The list consists of a few hundred IP ranges (one range per line, as shown).

How would you recommend I tackle this? Your help is greatly appreciated.

keefaz 09-12-2008 04:51 PM

http://www.php.net/manual/en/function.ip2long.php
Have a look at the in_ip_range() function posted by mhakopian in user comments

win32sux 09-12-2008 05:05 PM

Hey man that's awesome - thanks! I had looked at the ip2long() function before posting, but I didn't realize what it did. I thought it was for doing reverse DNS or something like that. I think what I'm gonna do is put my ranges in the database and then write a function which uses ip2long() to check whether $_SERVER['REMOTE_ADDR'] is in the range. I'll post whatever I come-up with tonight. Once again, thank you very much!

keefaz 09-12-2008 05:07 PM

Thank mhakopian, not me :)

win32sux 09-12-2008 07:08 PM

Quote:

Originally Posted by keefaz (Post 3278732)
Thank mhakopian, not me :)

Haha well I'll give him some credit because his example made it easy for me to learn to use ip2long(), but you're the one that pointed me toward the function so you were more vital. In any case, thanks to both of you! :)

Okay, so I put all my IP ranges in the database and wrote a function which takes an IP as input and checks the database to see if the IP is in the listed ranges. It returns TRUE if the IP is not in any of the ranges. The table where I put the ranges looks like:
Code:

+-----+-----------------+-----------------+
| ID  | StartIP        | EndIP          |
+-----+-----------------+-----------------+
|  1 | 123.123.123.123 | 123.123.200.200 |
|  2 | 124.129.0.123  | 124.129.200.200 |
|  3 | 231.160.124.1  | 231.255.255.255 |
+-----+-----------------+-----------------+


The PHP function which I wrote to do the lookup/check is:
PHP Code:

function check_ip($IP)
    {
        
$IP ip2long($IP);
        
$result mysql_query("SELECT * FROM IPRangeTable");
        while(
$row mysql_fetch_array($result))
            {
                
$StartIP ip2long($row['StartIP']);
                
$EndIP   ip2long($row['EndIP']);
                if (
$IP >= $StartIP && $IP <= $EndIP)
                    {
                        return 
FALSE;
                        break;
                    }
            }
        return 
TRUE;
    } 

Hopefully some Google user might find it useful.

BTW, any corrections are welcome (I haven't tested it that much yet).

win32sux 09-12-2008 07:20 PM

Is that break necessary there?

Or does the while loop stop running when return is executed?

jiml8 09-12-2008 07:31 PM

Something similar that is more efficient:

$sql = 'SELECT
startip,endip
FROM
iprangetable
WHERE
startip < INET_ATON("'.$_SERVER['REMOTE_ADDR'].'")
ORDER BY
startip DESC
LIMIT 0,1';



In this situation, the IP addresses are stored in the database as internet addresses (a 32 bit number - see the usage for inet_aton to understand), and the database is queried exactly once and returns exactly one value, which is the correct value.

You would make this the startIP, then you would only have to check once to see if your IP was less than the end IP, after you got your return.

jiml8 09-12-2008 07:40 PM

Quote:

Originally Posted by win32sux (Post 3278801)
Is that break necessary there?

Or does the while loop stop running when return is executed?

Returning from the middle of a while loop is bad form.

It is OK, so long as the scripting language cleans up its stack properly on function exit, but I have in the past encountered subtle and pernicious errors by doing this, when the scripting language did NOT clean up its stack properly on this kind of exit.

Better to exit from the top level only, in all cases. I don't know that PHP would have a problem with it, but why buy trouble?

win32sux 09-12-2008 08:26 PM

Thanks for that very educational feedback, jiml8. I'm reading up on INET_ATON right now, and will then read up on LIMIT. I haven't used either of those yet in my journey as a PHP/MySQL newbie. One question: Why did you choose to only select rows where StartIP is lower? In other words, is there a specific reason why you didn't just do the whole less than or equal to and greater than or equal to comparison with both EndIP and StartIP in the query? I'm thinking then one could have the function return TRUE/FALSE depending on whether the query returned an empty set or not.

EDIT: Wait, I haven't fully understood your query (yet) it seems. I need to do a lot more reading. :study:

graemef 09-12-2008 09:07 PM

Quote:

Originally Posted by win32sux (Post 3278801)
Is that break necessary there?

Or does the while loop stop running when return is executed?

The break is not necessary and the code will work without it. As a matter of style it is often considered poor form to have multiple return statements, however with a function as small as yours there is nothing particularly wrong with the return being where it is.

As for the SQL that jiml8 gave I think that it is possibly missing the check on the upper bound, maybe that was left as an exercise ;)

Since the limit was set to 1 I woudl suggest that the ORDER BY is not required.

jiml8 09-13-2008 12:09 AM

The purpose of that request is to sort in descending order through the startip index. Since the sort is in descending order, the startip address should be greater than the IP address you are testing, UNTIL you reach and pass your IP address as the count descends.

The very first IP address in the startip field that is LESS THAN your IP address is the ONLY IP address you are interested in (unless you have overlapping IP ranges, which shouldn't be the case).

So you return THAT ROW ONLY, then test the endip field IN THAT ROW to see if that address is greater than the address you are testing. If it is, you have a winner; this IP address is one you are interested in.

Let the database do the work; that is what it is for. This saves a scripted while loop and a whole bunch of database queries. You need the ORDER BY because you are specifying a descending search so you have to be explicit about the index.

graemef 09-13-2008 12:59 AM

I have just removed my comment since I was wrong.

Personally I think that it can be confusing (since I was initially) to mix the checking between the SQL and the code. Whilst it works as jiml8 stated I would prefer both the upper and lower checks to be in the SQL. That would also remove the need for the overlapping caveat.

jiml8 09-13-2008 01:53 AM

Checking both bounds in the query is certainly possible. I rather suspect it is faster to do it as I did it, but that could be wrong.

Also, I find it to be more readable to check the upper bound in the PHP after finding the right record with the query, but that is just me.

Simply a matter of opinion, I think.

rubadub 09-13-2008 02:49 PM

Sorry for dumping some code, but i've just written some such code today for a tutorial on my site, it'll look a little like this:
Code:

$host = 'localhost';
$user = 'user';
$pass = 'pass';
$db = 'db';

$conn = mysql_connect($host, $user, $pass) or die(mysql_error());
mysql_select_db($db, $conn) or die(mysql_error());

$s = "DROP TABLE test_ip_range";
mysql_query($s, $conn);

$s = "CREATE TABLE test_ip_range (id int not null primary key auto_increment, start_ip INT UNSIGNED, end_ip INT UNSIGNED, owner varchar(128) )";
if(mysql_query($s, $conn))
{
        $s = "INSERT INTO test_ip_range VALUES
                        ('', INET_ATON('209.85.128.0'), INET_ATON('209.85.255.255'), 'Google Inc.'),
                        ('', INET_ATON('86.160.0.0'), INET_ATON('86.171.255.255'), 'BT-CENTRAL-PLUS'),
                        ('', INET_ATON('202.88.128.0'), INET_ATON('202.88.191.255'), 'HATHWAY-NET'),
                        ('', INET_ATON('90.192.0.0'), INET_ATON('90.199.255.255'), 'BSKYB-BROADBAND') ";
        $res = mysql_query($s, $conn) or die(mysql_error());
}
else
{
        print "Table creation failed<br>";
        print mysql_error();
}

print "<br>";

$feedback = "";
$ip = "";
if(isset($_POST['SUBMIT']))
{
        $ip = $_POST['ip'];
       
        $s = "SELECT id, INET_NTOA(start_ip) as start_ip, INET_NTOA(end_ip) as end_ip, owner FROM test_ip_range WHERE start_ip <= INET_ATON('".$ip."') AND end_ip >= INET_ATON('".$ip."')";
        $res = mysql_query($s, $conn) or die(mysql_error());
        $feedback .= "<b>Results for: ".$ip."</b><br><table cellspacing='0px' cellpadding='3px' width='100%'>";
        if(mysql_num_rows($res)>0)
        {
                $feedback .= "<tr bgcolor='#efefef'><td>ID</td><td>START IP</td><td>END IP</td><td>OWNER</td></tr>";
                while ($a = mysql_fetch_array($res))
                {
                        $feedback .= "<tr><td>".$a['id']."</td><td>".$a['start_ip']."</td><td>".$a['end_ip']."</td><td>".$a['owner']."</td></tr>";
                }
        }
        else
        {
                $feedback .= "<tr><td>No match</td></tr>";
        }
        $feedback .= "</table>";
}
print "<form method='POST' action=''><table>";
print "<tr><td align='right'>IP Address</td><td><input type='text' name='ip' size='32' value='".$ip."'></td></tr>";
print "<tr><td align='right'></td><td><input type='submit' name='SUBMIT' value='Submit'></td></tr>";
print "</table></form><br>";
print "<br>".$feedback;

print "<br>";
print "<table cellspacing='0px' cellpadding='3px' width='100%'><tr bgcolor='#efefef'><td>ID</td><td>START IP</td><td>END IP</td><td>OWNER</td></tr>";
$s = "SELECT id, INET_NTOA(start_ip) as start_ip, INET_NTOA(end_ip) as end_ip, owner FROM test_ip_range ORDER BY start_ip";
$res = mysql_query($s, $conn) or die(mysql_error());
while ($a = mysql_fetch_array($res))
{
        print "<tr><td>".$a['id']."</td><td>".$a['start_ip']."</td><td>".$a['end_ip']."</td><td>".$a['owner']."</td></tr>";
}
print "</table>";

The choice of ip ranges were the first 4 in a recent log to whois thing.

rkane 04-28-2013 07:52 PM

INET_ATON mysql
 
I just setup a table in mysql that has a startip and endip associated with each user.
And in order to see if they are coming from the ip range I can do this.

SELECT * FROM `users` WHERE INET_ATON( '192.168.1.50' ) BETWEEN startip AND endip AND user='$user'

So if it returns a row then it is good.
You could easily flip the logic "NOT BETWEEN" and it would work for your case.


All times are GMT -5. The time now is 07:27 PM.