Reputation: 12805
Actually this is a two parts question:
First, is ip2long a good IP validator? Suppose someone inserts an IP in a form, and I want to validate that it is correct. Is it OK to just check that ip2long doesn't return FALSE ?
Second: What do you think about checking the IP of the visitors, and denying access if it's not a valid IP? Looking at my visitor's ips, sometimes I find things like "1.1 TIBURON".. Wtf is that? I heard the expression 'spoofed ip', is that what it is? Is it related to spam bots?
Upvotes: 2
Views: 1229
Reputation: 4340
I had this code around. It's been a while and I can't quite remember some of the decisions behind it, but I assure you it has been thoroughly tested
(actually I just tweaked it a little, renaming variables and adding comments, so it's possible I just broke it :)
You may find the get_ip_addr
and is_private_ip_addr
functions useful.
get_ip_addr
can take both an IP address or a host name.
<?php
function get_ip_addr($host){
$long_ipaddr = my_ip2long($host);
$host = trim($host);
if( $long_ipaddr !== false ){
$str_ip = long2ip($long_ipaddr); // Because of e.g. 245.254.245
return $str_ip;
}
$ip_addr = @gethostbyname($host);
if( $ip_addr and $ip_addr != -1 ){
return $ip_addr;
}
return false;
}
function my_ip2long($ipaddr){
$long_ip = ip2long($ipaddr);
if( $long_ip === false ){
return false;
}
// http://php.net/manual/en/function.ip2long.php says:
// Note:
// Because PHP's integer type is signed, and many IP addresses
// will result in negative integers, you need to use
// the "%u" formatter of sprintf() or printf() to get
// the string representation of the unsigned IP address.
$long_ip = sprintf("%u", $long_ip);
return $long_ip;
}
function ip2bin($ip){
$octets = explode(".", $ip);
foreach($octets as $k => $v){
$octets[$k] = str_pad(decbin($v), 8, "0", STR_PAD_LEFT);
}
return implode('', $octets);
}
function ip_in_range($ip, $prefix, $mask_len){
$ip = ip2bin($ip);
$prefix = ip2bin($prefix);
$ip = substr($ip, 0, $mask_len);
$prefix = substr($prefix, 0, $mask_len);
// Watch out! Two numerical strings are converted to integers
// when you use ==. This is trouble for long integers.
// Using === skips this behaviour
return ($ip === $prefix);
}
function is_private_ip_addr($ipaddr){
if( "localhost" === $ipaddr ) return true;
$long_ipaddr = my_ip2long($ipaddr);
if( $long_ipaddr === false ){
return false; // Shouldn't be calling this!
}
// Info below obtained from http://bugs.php.net/bug.php?id=47435#c145655
// Not sure why 127.0.0.0/8 isn't mentioned ...?
// Also, in IPv6 there's the multicast address range: ff00::/8s
//
// IPv4 private ranges
// 10.0.0.0/8 // private use network (rfc1918)
// 172.16.0.0/12 // private use network (rfc1918)
// 192.168.0.0/16 // private use network (rfc1918)
//
// IPv4 reserved ranges
// 0.0.0.0/8 // "this" network (rfc1700)
// 169.254.0.0/16 // link local network (rfc3927)
// 192.0.2.0/24 // test net (rfc3330)
// 224.0.0.0/4 // Multicast (rfc3171)
// 240.0.0.0/4 // Reserved for Future Use (rfc1700)
//
// IPv6 Private range
// fc00::/7 // unique-local addresses (rfc4193)
//
// IPv6 Reserved ranges
// ::/128 // unspecified address (rfc4291)
// ::1/128 // loopback address (rfc4291)
// fe80::/10 // link local unicast (rfc4291)
// 2001:db8::/32 // documentation addresses (rfc3849)
// 5f00::/8 // 6Bone
// 3ffe::/16 // 6Bone
// ::ffff:0:0/96 // IPv4-Mapped addresses (rfc4291)
// 2001:10::/28 // ORCHID addresses (rfc4843)
// ::/0 // default unicast route address
//
// Anyways, this are the relevant RFCs:
// RFC 3330 (Sep 2002), "Special-Use IPv4 Addresses":
// see section 3 for a nice table
// RFC 5156 (Apr 2008), "Special-Use IPv6 Addresses"
//
// Also, this function currently only deals with IPv4 addresses,
// since PHP's ip2long simply doesn't support IPv6 anyway.
// You can't currently even trust long ints to have 64 bit,
// so a 128 bit long int is out of the question.
if( ip_in_range($ipaddr, "127.0.0.0", 8) ) return true; // Loopback
if( ip_in_range($ipaddr, "10.0.0.0", 8) ) return true; // Private addresses (Class A range)
if( ip_in_range($ipaddr, "172.16.0.0", 12) ) return true; // Private addresses (Class B range)
if( ip_in_range($ipaddr, "192.168.0.0", 16) ) return true; // Private addresses (Class C range)
if( ip_in_range($ipaddr, "169.254.0.0", 16) ) return true; // "This" network
if( ip_in_range($ipaddr, "192.0.2.0", 24) ) return true; // "TEST-NET" (documentation and examples)
if( ip_in_range($ipaddr, "224.0.0.0", 4) ) return true; // Multicast
if( ip_in_range($ipaddr, "240.0.2.0", 4) ) return true; // Reserved for future use
return false;
}
You can consider this code released under a permissive CC BY-SA.
Do whatever you want with it, but if you improve on it please tells us about it here.
I'm marking this post community wiki..
Upvotes: 0
Reputation: 10327
If you just need to valdiate that the IP address is correct in format you can use a regular expresión like the one bellow.
EDIT:
if (preg_match('/\A(?:^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)\Z/im', $subject)) {
# Successful match
} else {
# Match attempt failed
}
If you want to go further you can do a ping to the IP to discover if it is active.
About your second question, i don't know what to say, i've never seen the "1.1 TIBURON" thing,
HTH
Upvotes: 1
Reputation: 20721
Depends on how thorough you want the validation to be. A very simple check for well-formed IP addresses would be a regex along the lines of /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
. (Actually, SubniC's regex is probably much better). This would catch your 1.1. TIBURON entry, because it's not well-formed.
I'm not sure whether ip2long checks this or just silently discards the part of the string that doesn't look like an IP.
One step further you could block 'reserved' IP addresses and ranges, such as 127.0.0.1, 255.255.255.255, etc.
You might also want to add a blacklist to filter out requests from dubious networks, or from clients you have had trouble with in the past.
I'm curious though how that entry got there in the first place - an attacker shouldn't be able to get this value into $_SERVER['REMOTE_ADDR'] unless they have compromised the underlying OS, or at least apache (or whatever web server you're running).
You're not by any chance reading the IP on the client (using javascript)?
Upvotes: 0