How can I get the client IP address using PHP?
I want to keep record of the user who logged into my website through his/her IP address.
How can I get the client IP address using PHP?
I want to keep record of the user who logged into my website through his/her IP address.
This function should work as expected
function get_user_ip() { // List of server variables that may contain the client's IP address $ip_sources = [ 'HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR' ]; $ip = false; // Iterate over each source to find a valid IP address foreach ($ip_sources as $source) { if (getenv($source)) { $ip = getenv($source); // Break the loop if a valid IP is found if (filter_var($ip, FILTER_VALIDATE_IP)) { break; } } } // Final validation: ensure the IP is not the server's IP if ($ip && ($ip == getenv('SERVER_ADDR') || !filter_var($ip, FILTER_VALIDATE_IP))) { $ip = false; } return $ip; } Here's a simple one liner
$ip = $_SERVER['HTTP_X_FORWARDED_FOR']?: $_SERVER['HTTP_CLIENT_IP']?: $_SERVER['REMOTE_ADDR']; EDIT:
Above code may return reserved addresses (like 10.0.0.1), a list of addresses of all proxy servers on the way, etc. To handle these cases use the following code:
function valid_ip($ip) { // for list of reserved IP addresses, see https://en.wikipedia.org/wiki/Reserved_IP_addresses return $ip && substr($ip, 0, 4) != '127.' && substr($ip, 0, 4) != '127.' && substr($ip, 0, 3) != '10.' && substr($ip, 0, 2) != '0.' ? $ip : false; } function get_client_ip() { // using explode to get only client ip from list of forwarders. see https://en.wikipedia.org/wiki/X-Forwarded-For return @$_SERVER['HTTP_X_FORWARDED_FOR'] ? explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'], 2)[0] : @$_SERVER['HTTP_CLIENT_IP'] ? explode(',', $_SERVER['HTTP_CLIENT_IP'], 2)[0] : valid_ip(@$_SERVER['REMOTE_ADDR']) ?: 'UNKNOWN'; } echo get_client_ip(); As many people said, "anti-proxies" to find real people IP is a little hard to create because you need IP list of proxies that you trust. Cvolton, a Geometry Dash player, has recreated the whole game server in PHP and MySQL and has a function to get user IP that bypasses Cloudflare and 7mPl "localhost bug". Here is the code (since original code was fragmented in 2 files here is the fusioned file) (also the get IP function is at the end of the programm):
/* * ip_in_range.php - Function to determine if an IP is located in a * specific range as specified via several alternative * formats. * * Network ranges can be specified as: * 1. Wildcard format: 1.2.3.* * 2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0 * 3. Start-End IP format: 1.2.3.0-1.2.3.255 * * Return value BOOLEAN : ip_in_range($ip, $range); * * Copyright 2008: Paul Gregg <[email protected]> * 10 January 2008 * Version: 1.2 * * Source website: http://www.pgregg.com/projects/php/ip_in_range/ * Version 1.2 * * This software is Donationware - if you feel you have benefited from * the use of this tool then please consider a donation. The value of * which is entirely left up to your discretion. * http://www.pgregg.com/donate/ * * Please do not remove this header, or source attibution from this file. */ /* * Modified by James Greene <[email protected]> to include IPV6 support * (original version only supported IPV4). * 21 May 2012 */ class ipInRange { // decbin32 // In order to simplify working with IP addresses (in binary) and their // netmasks, it is easier to ensure that the binary strings are padded // with zeros out to 32 characters - IP addresses are 32 bit numbers public static function decbin32 ($dec) { return str_pad(decbin($dec), 32, '0', STR_PAD_LEFT); } // ipv4_in_range // This function takes 2 arguments, an IP address and a "range" in several // different formats. // Network ranges can be specified as: // 1. Wildcard format: 1.2.3.* // 2. CIDR format: 1.2.3/24 OR 1.2.3.4/255.255.255.0 // 3. Start-End IP format: 1.2.3.0-1.2.3.255 // The function will return true if the supplied IP is within the range. // Note little validation is done on the range inputs - it expects you to // use one of the above 3 formats. public static function ipv4_in_range($ip, $range) { if (strpos($range, '/') !== false) { // $range is in IP/NETMASK format list($range, $netmask) = explode('/', $range, 2); if (strpos($netmask, '.') !== false) { // $netmask is a 255.255.0.0 format $netmask = str_replace('*', '0', $netmask); $netmask_dec = ip2long($netmask); return ( (ip2long($ip) & $netmask_dec) == (ip2long($range) & $netmask_dec) ); } else { // $netmask is a CIDR size block // fix the range argument $x = explode('.', $range); while(count($x)<4) $x[] = '0'; list($a,$b,$c,$d) = $x; $range = sprintf("%u.%u.%u.%u", empty($a)?'0':$a, empty($b)?'0':$b,empty($c)?'0':$c,empty($d)?'0':$d); $range_dec = ip2long($range); $ip_dec = ip2long($ip); # Strategy 1 - Create the netmask with 'netmask' 1s and then fill it to 32 with 0s #$netmask_dec = bindec(str_pad('', $netmask, '1') . str_pad('', 32-$netmask, '0')); # Strategy 2 - Use math to create it $wildcard_dec = pow(2, (32-$netmask)) - 1; $netmask_dec = ~ $wildcard_dec; return (($ip_dec & $netmask_dec) == ($range_dec & $netmask_dec)); } } else { // range might be 255.255.*.* or 1.2.3.0-1.2.3.255 if (strpos($range, '*') !==false) { // a.b.*.* format // Just convert to A-B format by setting * to 0 for A and 255 for B $lower = str_replace('*', '0', $range); $upper = str_replace('*', '255', $range); $range = "$lower-$upper"; } if (strpos($range, '-')!==false) { // A-B format list($lower, $upper) = explode('-', $range, 2); $lower_dec = (float)sprintf("%u",ip2long($lower)); $upper_dec = (float)sprintf("%u",ip2long($upper)); $ip_dec = (float)sprintf("%u",ip2long($ip)); return ( ($ip_dec>=$lower_dec) && ($ip_dec<=$upper_dec) ); } return false; } } public static function ip2long6($ip) { if (substr_count($ip, '::')) { $ip = str_replace('::', str_repeat(':0000', 8 - substr_count($ip, ':')) . ':', $ip); } $ip = explode(':', $ip); $r_ip = ''; foreach ($ip as $v) { $r_ip .= str_pad(base_convert($v, 16, 2), 16, 0, STR_PAD_LEFT); } return base_convert($r_ip, 2, 10); } // Get the ipv6 full format and return it as a decimal value. public static function get_ipv6_full($ip) { $pieces = explode ("/", $ip, 2); $left_piece = $pieces[0]; $right_piece = $pieces[1]; // Extract out the main IP pieces $ip_pieces = explode("::", $left_piece, 2); $main_ip_piece = $ip_pieces[0]; $last_ip_piece = $ip_pieces[1]; // Pad out the shorthand entries. $main_ip_pieces = explode(":", $main_ip_piece); foreach($main_ip_pieces as $key=>$val) { $main_ip_pieces[$key] = str_pad($main_ip_pieces[$key], 4, "0", STR_PAD_LEFT); } // Check to see if the last IP block (part after ::) is set $last_piece = ""; $size = count($main_ip_pieces); if (trim($last_ip_piece) != "") { $last_piece = str_pad($last_ip_piece, 4, "0", STR_PAD_LEFT); // Build the full form of the IPV6 address considering the last IP block set for ($i = $size; $i < 7; $i++) { $main_ip_pieces[$i] = "0000"; } $main_ip_pieces[7] = $last_piece; } else { // Build the full form of the IPV6 address for ($i = $size; $i < 8; $i++) { $main_ip_pieces[$i] = "0000"; } } // Rebuild the final long form IPV6 address $final_ip = implode(":", $main_ip_pieces); return ip2long6($final_ip); } // Determine whether the IPV6 address is within range. // $ip is the IPV6 address in decimal format to check if its within the IP range created by the cloudflare IPV6 address, $range_ip. // $ip and $range_ip are converted to full IPV6 format. // Returns true if the IPV6 address, $ip, is within the range from $range_ip. False otherwise. public static function ipv6_in_range($ip, $range_ip) { $pieces = explode ("/", $range_ip, 2); $left_piece = $pieces[0]; $right_piece = $pieces[1]; // Extract out the main IP pieces $ip_pieces = explode("::", $left_piece, 2); $main_ip_piece = $ip_pieces[0]; $last_ip_piece = $ip_pieces[1]; // Pad out the shorthand entries. $main_ip_pieces = explode(":", $main_ip_piece); foreach($main_ip_pieces as $key=>$val) { $main_ip_pieces[$key] = str_pad($main_ip_pieces[$key], 4, "0", STR_PAD_LEFT); } // Create the first and last pieces that will denote the IPV6 range. $first = $main_ip_pieces; $last = $main_ip_pieces; // Check to see if the last IP block (part after ::) is set $last_piece = ""; $size = count($main_ip_pieces); if (trim($last_ip_piece) != "") { $last_piece = str_pad($last_ip_piece, 4, "0", STR_PAD_LEFT); // Build the full form of the IPV6 address considering the last IP block set for ($i = $size; $i < 7; $i++) { $first[$i] = "0000"; $last[$i] = "ffff"; } $main_ip_pieces[7] = $last_piece; } else { // Build the full form of the IPV6 address for ($i = $size; $i < 8; $i++) { $first[$i] = "0000"; $last[$i] = "ffff"; } } // Rebuild the final long form IPV6 address $first = ip2long6(implode(":", $first)); $last = ip2long6(implode(":", $last)); $in_range = ($ip >= $first && $ip <= $last); return $in_range; } } /* These two functions are from Cvolton (github.com/Cvolton) */ /* https://github.com/Cvolton/GMDprivateServer/blob/master/incl/lib/mainLib.php#L511 */ function isCloudFlareIP($ip) { $cf_ips = array( '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22', '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20', '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13', '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22' ); foreach ($cf_ips as $cf_ip) { if (ipInRange::ipv4_in_range($ip, $cf_ip)) { return true; } } return false; } function getIP(){ if (isset($_SERVER['HTTP_CF_CONNECTING_IP']) && $this->isCloudFlareIP($_SERVER['REMOTE_ADDR'])) //CLOUDFLARE REVERSE PROXY SUPPORT return $_SERVER['HTTP_CF_CONNECTING_IP']; if(isset($_SERVER['HTTP_X_FORWARDED_FOR']) && ipInRange::ipv4_in_range($_SERVER['REMOTE_ADDR'], '127.0.0.0/8')) //LOCALHOST REVERSE PROXY SUPPORT (7m.pl) return $_SERVER['HTTP_X_FORWARDED_FOR']; return $_SERVER['REMOTE_ADDR']; } I know that code is really long (and maybe there is some useless things in it, I did not wrote the programm, I just fusionned them) so you could try to optimize it (like fusionnate the 3 functions into only one and keep only what you need for the functions) but this (normally) works. Code is from https://github.com/Cvolton/GMDprivateServer/ The Cvolton's functions: https://github.com/Cvolton/GMDprivateServer/blob/master/incl/lib/mainLib.php#L511 The ip_in_range class: https://github.com/Cvolton/GMDprivateServer/blob/master/incl/lib/ip_in_range.php
I have gone thorugh many answer and I decided to give improved code to get Visitor IP address.
function GetIP() { // Compatible with CloudFlare // Get real visitor IP behind CloudFlare network if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) { $_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"]; $_SERVER['HTTP_CLIENT_IP'] = $_SERVER["HTTP_CF_CONNECTING_IP"]; } foreach (array('HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR') as $key) { if (array_key_exists($key, $_SERVER) === true) { // Handle the case where there are multiple proxies involved // HTTP_X_FORWARDED_FOR can have multiple ip like '1.1.1.1,2.2.2.2' foreach (array_map('trim', explode(',', $_SERVER[$key])) as $ip) { // Filter private and/or reserved IPs; if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } else { // Work if $_SERVER was not available. foreach (array_map('trim', explode(',', getenv($key))) as $ip) { // Filter private and/or reserved IPs; if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !== false) { return $ip; } } } } } It should work also with subnet and IPv6.
You can construct it passing a comma-separeted authorized proxies or a boolean value.
<?php class ClientIPGetter { private $authorized_proxies = []; function __construct($proxies = '') { if (is_string($proxies) && !empty(trim($proxies))) { $this->authorized_proxies = array_map('trim',explode(',',$proxies)); } else if (is_array($proxies) && !empty($proxies)) { foreach ($proxies as $p) { if (is_string($p)) { $p = trim($p); if (static::get_ip_version($p)) { $this->authorized_proxies[] = $p; } } } } else { $this->authorized_proxies = !!$proxies; } } public function get_proxies() { return $this->authorized_proxies; } public static function get_ip_version(string $ip) { $ip = explode('/', $ip)[0]; if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { return 4; } if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { return 6; } return 0; } public static function ip_compare($ip, $other) { $ip_ver = static::get_ip_version($ip); if ($ip_ver != static::get_ip_version($other)) { return false; } switch ($ip_ver) { case 4: return $ip == $other; case 6: return inet_pton($ip) == inet_pton($other); default: return false; } } public static function inet_to_bits($inet) { $splitted = str_split($inet); $binaryip = ''; foreach ($splitted as $char) { $binaryip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT); } return $binaryip; } public static function ip_in_range(string $ip, string $range) { try { $ip_ver = static::get_ip_version($ip); if ($ip_ver != static::get_ip_version($range)) { return false; } switch ($ip_ver) { case 4: list ($subnet, $bits) = explode('/', $range); if ($bits === null) { $bits = 32; } $ip = ip2long($ip); $subnet = ip2long($subnet); $mask = -1 << (32 - $bits); $subnet &= $mask; return ($ip & $mask) == $subnet; case 6: $ip = inet_pton($ip); $binaryip= static::inet_to_bits($ip); list($net,$maskbits)= explode('/',$range); $net=inet_pton($net); $binarynet = static::inet_to_bits($net); $ip_net_bits = substr($binaryip,0,$maskbits); $net_bits = substr($binarynet,0,$maskbits); return $ip_net_bits==$net_bits; default: return false; } } catch (Exception $e) { return false; } } public function get_client_ip() { $ip = ''; $authorized_proxy = false; if (is_bool($this->authorized_proxies)) { $authorized_proxy = $this->authorized_proxies; } else if (!empty($this->authorized_proxies)) { foreach($this->authorized_proxies as $p) { if (strpos($p, '/') !== false) { if (static::ip_in_range($_SERVER['REMOTE_ADDR'], $p)) { $authorized_proxy = true; break; } } else if (static::ip_compare($_SERVER['REMOTE_ADDR'], $p)) { $authorized_proxy = true; break; } } } if ($authorized_proxy) { if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { $fwd_ips = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] ); $ip = trim(end($fwd_ips)); } else if (!empty($_SERVER['HTTP_CLIENT_IP'])) { $ip = $_SERVER['HTTP_CLIENT_IP']; } } if (!$ip) {$ip = $_SERVER['REMOTE_ADDR'];} return $ip; } } Like the following?
if (($ip=filter_input(INPUT_SERVER, 'REMOTE_ADDR', validate_ip)) === false or empty($ip)) { exit; } echo $ip; PS
if (($ip=filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_VALIDATE_IP|FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE)) === false) { header('HTTP/1.0 400 Bad Request'); exit; } All headers beginning with 'HTTP_' or 'X-' may be spoofed, respectively is user defined. If you want to keep track, use cookies, etc.