在 PHP 中通过 CloudFlare 和日志访问者 IP 地址

我试图跟踪和记录用户/访客访问我的网站使用 PHP 的 $_SERVER['REMOTE_ADDR']这样做。PHP 中一种典型的 IP 地址跟踪方法。

然而,我使用 CloudFlare 进行缓存和接收他们的 IP 地址作为 CloudFlare 的:

108.162.212. *-108.162.239. *

在仍然使用 CloudFlare 的情况下,检索实际用户/访问者 IP 地址的正确方法是什么?

120706 次浏览

Extra server variables that are available to cloud flare are:

$_SERVER["HTTP_CF_CONNECTING_IP"] real visitor ip address, this is what you want

$_SERVER["HTTP_CF_IPCOUNTRY"] country of visitor

$_SERVER["HTTP_CF_RAY"]

$_SERVER["HTTP_CF_VISITOR"] this can help you know if its http or https

you can use it like this:

if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
$_SERVER['REMOTE_ADDR'] = $_SERVER["HTTP_CF_CONNECTING_IP"];
}

If you do this, and the validity of the visiting IP address is important, you might need to verify that the $_SERVER["REMOTE_ADDR"] contains an actual valid cloudflare IP address, because anyone can fake the header if he was able to connect directly to the server IP.

Since this question was asked and answered, CloudFlare has released mod_cloudflare for Apache, which logs & displays the actual visitor IP address rather than the CloudFlare address:

https://www.cloudflare.com/resources-downloads#mod_cloudflare

Cloudflare sends some additional request headers to your server including CF-Connecting-IP which we can store into $user_ip, if defined, using this simple one-liner:

$user_ip = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR'];

Update: CloudFlare has released a module mod_cloudflare for apache, the module will log and display the actual visitor IP Addresses rather than those accessed by cloudflare! https://www.cloudflare.com/resources-downloads#mod_cloudflare (Answer by: olimortimer)

If you dont have access to the apache runtime you can use the script below, this will allow you to check if the connection was through cloudflare and get the users ip.

I am rewriting my answer i used for another question "CloudFlare DNS / direct IP identifier"

Cloudflare's ips are stored in public so you can go view them here then check if the ip is from cloudflare (this will allow us to get the real ip from the http header HTTP_CF_CONNECTING_IP).

If you are using this to disable all non cf connections or vice versa, i recommend you to have a single php script file that gets called before every other script such as a common.php or pagestart.php etc.

function ip_in_range($ip, $range) {
if (strpos($range, '/') == false)
$range .= '/32';


// $range is in IP/CIDR format eg 127.0.0.1/24
list($range, $netmask) = explode('/', $range, 2);
$range_decimal = ip2long($range);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, (32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal));
}


function _cloudflare_CheckIP($ip) {
$cf_ips = array(
'199.27.128.0/21',
'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/12',
);
$is_cf_ip = false;
foreach ($cf_ips as $cf_ip) {
if (ip_in_range($ip, $cf_ip)) {
$is_cf_ip = true;
break;
}
} return $is_cf_ip;
}


function _cloudflare_Requests_Check() {
$flag = true;


if(!isset($_SERVER['HTTP_CF_CONNECTING_IP']))   $flag = false;
if(!isset($_SERVER['HTTP_CF_IPCOUNTRY']))       $flag = false;
if(!isset($_SERVER['HTTP_CF_RAY']))             $flag = false;
if(!isset($_SERVER['HTTP_CF_VISITOR']))         $flag = false;
return $flag;
}


function isCloudflare() {
$ipCheck        = _cloudflare_CheckIP($_SERVER['REMOTE_ADDR']);
$requestCheck   = _cloudflare_Requests_Check();
return ($ipCheck && $requestCheck);
}


// Use when handling ip's
function getRequestIP() {
$check = isCloudflare();


if($check) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
} else {
return $_SERVER['REMOTE_ADDR'];
}
}

To use the script it's quite simple:

$ip = getRequestIP();
$cf = isCloudflare();


if($cf) echo "Connection is through cloudflare: <br>";
else    echo "Connection is not through cloudflare: <br>";


echo "Your actual ip address is: ". $ip;
echo "The server remote address header is: ". $_SERVER['REMOTE_ADDR'];

This script will show you the real ip address and if the request is CF or not!

For magento 1.x users (I haven't try magento 2.0 yet), check https://tall-paul.co.uk/2012/03/13/magento-show-remote-ip-in-cloudflare-the-right-way/ which needs to change app/etc/local.xml and add: HTTP_CF_CONNECTING_IP

HTTP_CF_CONNECTING_IP is only working if you are using cloudflare maybe you transfer your site or remove cloudflare you will forget the value so use this code .

$ip=$_SERVER["HTTP_CF_CONNECTING_IP"];
if (!isset($ip)) {
$ip = $_SERVER['REMOTE_ADDR'];
}

When you are using CloudFlare all your requests between your server and users are routed through CloudFlare servers.

In this case there are two methods to get User's Real IP Address:

  1. Through Extra Server Headers added by CloudFlare Servers
  2. Adding a CloudFlare Apache/NGINX Module on your server.

Method 1: Get IP though extra Server Headers

You can use the following code to get user's IP Address:

$user_ip = (isset($_SERVER["HTTP_CF_CONNECTING_IP"]) $_SERVER["HTTP_CF_CONNECTING_IP"]:$_SERVER['REMOTE_ADDR']);

How it is working?

CloudFlare adds some extra server variables in the request as follows:

$_SERVER["HTTP_CF_CONNECTING_IP"] - Real IP Address of user

$_SERVER["HTTP_CF_IPCOUNTRY"] - ISO2 Country of the User

$_SERVER["HTTP_CF_RAY"] A Special string for loggin purpose

In the above code, we are checking if $_SERVER["HTTP_CF_CONNECTING_IP"] is set or not. If it is there we will consider that as user's IP Address else we will use the default code as $_SERVER['REMOTE_ADDR']

Method 2: Installing Cloudflare Module on your server

It would be hard to convert HTTP_CF_CONNECTING_IP to REMOTE_ADDR. So you can use apache (.htaccess) auto prepending to do that. So that you do not need to think about whether the $_SERVER['REMOTE_ADDR'] has the correct value in all the PHP scripts.

.htaccess code

php_value auto_prepend_file "/path/to/file.php"

php code (file.php)

<?php


define('CLIENT_IP', isset($_SERVER['HTTP_CF_CONNECTING_IP']) ? $_SERVER['HTTP_CF_CONNECTING_IP'] : $_SERVER['REMOTE_ADDR']);

Learn More here

In Laravel add the following line to AppServiceProvider:

...
use Symfony\Component\HttpFoundation\Request;




...
public function boot()
{
Request::setTrustedProxies(['REMOTE_ADDR'], Request::HEADER_X_FORWARDED_FOR);
}

Now you can get real client IP using request()->ip().

Read more here.

Cloudflare has an option to use a "Pseudo IPv4" address in the headers on the Network Management page for the domain. You have the option of adding or overwriting the headers that are sent to your server.

Cloudflare Dashboard | Network

From the documentation:

What is Pseudo IPv4?

As a stopgap to accelerate the adoption of IPv6, Cloudflare offers Pseudo IPv4 which supports IPv6 addresses in legacy applications expecting IPv4 addresses. The goal is to provide a nearly unique IPv4 address for each IPv6 address, using Class E IPv4 address space, which is designated as experimental and would not normally see traffic. To learn more see here.

Options

  • Add header: Add additional Cf-Pseudo-IPv4 header only
  • Overwrite headers: Overwrite the existing Cf-Connecting-IP and X-Forwarded-For headers with a pseudo IPv4 address.

Note: We recommend leaving this set to “Off” unless you have a specific need.

You can learn more about this feature from this article from Cloudflare, where it goes into a little more detail about how they try to accommodate the 128-bit address space of IPv6 in the 32-bit space found in IPv4.

In the event that you would like to know the IPv6 address along with the pseudo IPv4, Cloudflare adds a Cf-Connecting-IPv6 header when Pseudo IPv4 is enabled. This can be used to measure how much of your traffic is originating from devices that are on IPv6 networks, which can be useful if you need a solid number to show management before investments are made in updating systems that are still bound to the IPv4 limitations.

It's better to check cloudflare ip ranges online (because it may change anytime) then check it's from cloudflare or not.

Cloudflare IP txt

If the source is Cloudflare you can use $_SERVER['HTTP_CF_CONNECTING_IP'] to get your client request ip-address but it's not safe to use it for all requests because it can send by any user in request header to trick you.

You can use following code to get real ip-address of your client request:

function _getUserRealIP() {
$ipaddress = '';
if(isset($_SERVER['REMOTE_ADDR']))
$ipaddress = $_SERVER['REMOTE_ADDR'];
else if (isset($_SERVER['HTTP_CLIENT_IP']))
$ipaddress = $_SERVER['HTTP_CLIENT_IP'];
else if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))
$ipaddress = $_SERVER['HTTP_X_FORWARDED_FOR'];
else if(isset($_SERVER['HTTP_X_FORWARDED']))
$ipaddress = $_SERVER['HTTP_X_FORWARDED'];
else if(isset($_SERVER['HTTP_X_CLUSTER_CLIENT_IP']))
$ipaddress = $_SERVER['HTTP_X_CLUSTER_CLIENT_IP'];
else if(isset($_SERVER['HTTP_FORWARDED_FOR']))
$ipaddress = $_SERVER['HTTP_FORWARDED_FOR'];
else if(isset($_SERVER['HTTP_FORWARDED']))
$ipaddress = $_SERVER['HTTP_FORWARDED'];
else
$ipaddress = 'UNKNOWN';
return $ipaddress;
}


function _readCloudflareIps()
{
$file = file("https://www.cloudflare.com/ips-v4",FILE_IGNORE_NEW_LINES);
return $file;
}


function _checkIpInRange($ip, $range) {
if (strpos($range, '/') == false)
$range .= '/32';


// $range is in IP/CIDR format eg 127.0.0.1/24
list($range, $netmask) = explode('/', $range, 2);
$range_decimal = ip2long($range);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, (32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return (($ip_decimal & $netmask_decimal) == ($range_decimal & $netmask_decimal));
}


function _checkIsCloudflare($ip) {
$cf_ips = _readCloudflareIps();
$is_cf_ip = false;
foreach ($cf_ips as $cf_ip) {
if (_checkIpInRange($ip, $cf_ip)) {
$is_cf_ip = true;
break;
}
}
return $is_cf_ip;
}


function getRealIp()
{
$httpIp = _getUserRealIP();
$check = _checkIsCloudflare($httpIp);
if ($check) {
return $_SERVER['HTTP_CF_CONNECTING_IP'];
}else{
return $httpIp;
}
}

After importing this functions to your code you just need to call getRealIp() function like:

$userIp = getRealIp();
echo $userIp();

another way to get it in Laravel is simply by reading HTTP_CF_CONNECTING_IP header

$request->server('HTTP_CF_CONNECTING_IP')

you can read more about it here: How to get real client IP behind Cloudflare in Laravel / PHP