I know there are a plethora of $_SERVER variables headers available for IP address retrieval. I was wondering if there is a general consensus as to how to most accurately retrieve a user's real IP address (well knowing no method is perfect) using said variables?
I spent some time trying to find an in depth solution and came up with the following code based on a number of sources. I would love it if somebody could please poke holes in the answer or shed some light on something perhaps more accurate.
edit includes optimizations from @Alix
/**
* Retrieves the best guess of the client's actual IP address.
* Takes into account numerous HTTP proxy headers due to variations
* in how different ISPs handle IP addresses in headers between hops.
*/
public function get_ip_address() {
// Check for shared internet/ISP IP
if (!empty($_SERVER['HTTP_CLIENT_IP']) && $this->validate_ip($_SERVER['HTTP_CLIENT_IP']))
return $_SERVER['HTTP_CLIENT_IP'];
// Check for IPs passing through proxies
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
// Check if multiple IP addresses exist in var
$iplist = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
foreach ($iplist as $ip) {
if ($this->validate_ip($ip))
return $ip;
}
}
}
if (!empty($_SERVER['HTTP_X_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_X_FORWARDED']))
return $_SERVER['HTTP_X_FORWARDED'];
if (!empty($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']) && $this->validate_ip($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
return $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
if (!empty($_SERVER['HTTP_FORWARDED_FOR']) && $this->validate_ip($_SERVER['HTTP_FORWARDED_FOR']))
return $_SERVER['HTTP_FORWARDED_FOR'];
if (!empty($_SERVER['HTTP_FORWARDED']) && $this->validate_ip($_SERVER['HTTP_FORWARDED']))
return $_SERVER['HTTP_FORWARDED'];
// Return unreliable IP address since all else failed
return $_SERVER['REMOTE_ADDR'];
}
/**
* Ensures an IP address is both a valid IP address and does not fall within
* a private network range.
*
* @access public
* @param string $ip
*/
public function validate_ip($ip) {
if (filter_var($ip, FILTER_VALIDATE_IP,
FILTER_FLAG_IPV4 |
FILTER_FLAG_IPV6 |
FILTER_FLAG_NO_PRIV_RANGE |
FILTER_FLAG_NO_RES_RANGE) === false)
return false;
self::$ip = $ip;
return true;
}
Words of Warning (update)
REMOTE_ADDR
still represents the most reliable source of an IP address. The other $_SERVER
variables mentioned here can be spoofed by a remote client very easily. The purpose of this solution is to attempt to determine the IP address of a client sitting behind a proxy. For your general purposes, you might consider using this in combination with the IP address returned directly from $_SERVER['REMOTE_ADDR']
and storing both.
For 99.9% of users this solution will suit your needs perfectly. It will not protect you from the 0.1% of malicious users looking to abuse your system by injecting their own request headers. If relying on IP addresses for something mission critical, resort to REMOTE_ADDR
and don't bother catering to those behind a proxy.
My answer is basically just a polished, fully-validated, and fully-packaged, version of @AlixAxel's answer:
Changes:
It simplifies the function name (with 'camelCase' formatting style).
It includes a check to make sure the function isn't already declared in another part of your code.
It takes into account 'CloudFlare' compatibility.
It initializes multiple "IP-related" variable names to the returned value, of the 'getClientIP' function.
It ensures that if the function doesn't return a valid IP address, all the variables are set to a empty string, instead of
null
.It's only (45) lines of code.
Here's a modified version if you use CloudFlare caching layer Services
Just a VB.NET version of the answer:
The biggest question is for what purpose?
Your code is nearly as comprehensive as it could be - but I see that if you spot what looks like a proxy added header, you use that INSTEAD of the CLIENT_IP, however if you want this information for audit purposes then be warned - its very easy to fake.
Certainly you should never use IP addresses for any sort of authentication - even these can be spoofed.
You could get a better measurement of the client ip address by pushing out a flash or java applet which connects back to the server via a non-http port (which would therefore reveal transparent proxies or cases where the proxy-injected headers are false - but bear in mind that, where the client can ONLY connect via a web proxy or the outgoing port is blocked, there will be no connection from the applet.
C.
Thanks for this, very useful.
It would help though if the code were syntactically correct. As it is there's a { too many around line 20. Which I'm afraid means nobody actually tried this out.
I may be crazy, but after trying it on a few valid and invalid addresses, the only version of validate_ip() that worked was this:
Here is a shorter, cleaner way to get the IP address:
I hope it helps!
Your code seems to be pretty complete already, I cannot see any possible bugs in it (aside from the usual IP caveats), I would change the
validate_ip()
function to rely on the filter extension though:Also your
HTTP_X_FORWARDED_FOR
snippet can be simplified from this:To this:
You may also want to validate IPv6 addresses.