mirror of
https://github.com/Lastorder-DC/rhymix.git
synced 2026-01-09 03:32:00 +09:00
Merge pull request #190 from kijin/pr/cloudflare-ip-fix
클라우드플레어 사용시 실제 방문자 IP를 자동 감지
This commit is contained in:
commit
0cdaf31745
5 changed files with 316 additions and 66 deletions
|
|
@ -6,87 +6,29 @@ class IpFilter
|
|||
public function filter($ip_list, $ip = NULL)
|
||||
{
|
||||
if(!$ip) $ip = $_SERVER['REMOTE_ADDR'];
|
||||
$long_ip = ip2long($ip);
|
||||
foreach($ip_list as $filter_ip)
|
||||
{
|
||||
$range = explode('-', $filter_ip);
|
||||
if(!$range[1]) // single address type
|
||||
{
|
||||
$star_pos = strpos($filter_ip, '*');
|
||||
if($star_pos !== FALSE ) // wild card exist
|
||||
{
|
||||
if(strncmp($filter_ip, $ip, $star_pos)===0) return true;
|
||||
}
|
||||
else if(strcmp($filter_ip, $ip)===0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if(ip2long($range[0]) <= $long_ip && ip2long($range[1]) >= $long_ip)
|
||||
foreach($ip_list as $filter)
|
||||
{
|
||||
if(Rhymix\Framework\IpFilter::inRange($ip, $filter))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* public function filter2($ip_list, $ip)
|
||||
{
|
||||
$long_ip = ip2long($ip);
|
||||
foreach($ip_list as $filter_ip)
|
||||
{
|
||||
$range = explode('-', $filter_ip);
|
||||
if(!$range[1]) // single address type
|
||||
{
|
||||
$range[1] = str_replace('*', '255', $range[0]);
|
||||
$range[0] = str_replace('*', '0', $range[0]);
|
||||
}
|
||||
|
||||
if(ip2long($range[0]) <= $long_ip && ip2long($range[1]) >= $long_ip)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} */
|
||||
|
||||
|
||||
public function validate($ip_list = array())
|
||||
{
|
||||
/* 사용가능한 표현
|
||||
192.168.2.10 - 4자리의 정확한 ip주소
|
||||
192.168.*.* - 와일드카드(*)가 사용된 4자리의 ip주소, a클래스에는 와일드카드 사용불가,
|
||||
와일드카드 이후의 아이피주소 허용(단, filter()를 쓸 경우 와일드카드 이후 주소는 무시됨
|
||||
192.168.1.1-192.168.1.10 - '-'로 구분된 정확한 4자리의 ip주소 2개
|
||||
*/
|
||||
$regex = "/^
|
||||
(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
|
||||
(?:
|
||||
(?:
|
||||
(?:\.(?: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]?)){1}
|
||||
(?:\.(?: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]?)|\*)){3}
|
||||
)
|
||||
)
|
||||
$/";
|
||||
$regex = str_replace(array("\r\n", "\n", "\r","\t"," "), '', $regex);
|
||||
|
||||
foreach($ip_list as $i => $ip)
|
||||
foreach($ip_list as $filter)
|
||||
{
|
||||
preg_match($regex, $ip, $matches);
|
||||
if(!count($matches)) return false;
|
||||
if(!Rhymix\Framework\IpFilter::validateRange($filter))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* End of file : IpFilter.class.php */
|
||||
/* Location: ./classes/security/IpFilter.class.php */
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ else
|
|||
/**
|
||||
* RX_CLIENT_IP_VERSION and RX_CLIENT_IP contain information about the current visitor's IP address.
|
||||
*/
|
||||
if (isset($_SERVER['HTTP_CF_CONNECTING_IP']))
|
||||
{
|
||||
include_once __DIR__ . '/framework/ipfilter.php';
|
||||
Rhymix\Framework\IpFilter::getCloudFlareRealIP();
|
||||
}
|
||||
if (isset($_SERVER['REMOTE_ADDR']) && preg_match('/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/', $_SERVER['REMOTE_ADDR'], $matches))
|
||||
{
|
||||
define('RX_CLIENT_IP_VERSION', 4);
|
||||
|
|
|
|||
28
common/defaults/cloudflare.php
Normal file
28
common/defaults/cloudflare.php
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* List of IP addresses belonging to CloudFlare
|
||||
*
|
||||
* See: https://www.cloudflare.com/ips
|
||||
*/
|
||||
return array(
|
||||
'103.21.244.0/22',
|
||||
'103.22.200.0/22',
|
||||
'103.31.4.0/22',
|
||||
'104.16.0.0/12',
|
||||
'108.162.192.0/18',
|
||||
'141.101.64.0/18',
|
||||
'162.158.0.0/15',
|
||||
'172.64.0.0/13',
|
||||
'173.245.48.0/20',
|
||||
'188.114.96.0/20',
|
||||
'190.93.240.0/20',
|
||||
'197.234.240.0/22',
|
||||
'198.41.128.0/17',
|
||||
'199.27.128.0/21',
|
||||
'2400:cb00::/32',
|
||||
'2405:8100::/32',
|
||||
'2405:b500::/32',
|
||||
'2606:4700::/32',
|
||||
'2803:f800::/32',
|
||||
);
|
||||
198
common/framework/ipfilter.php
Normal file
198
common/framework/ipfilter.php
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
<?php
|
||||
|
||||
namespace Rhymix\Framework;
|
||||
|
||||
/**
|
||||
* The IP filter class.
|
||||
*/
|
||||
class IpFilter
|
||||
{
|
||||
/**
|
||||
* Check whether the given IP address belongs to a range.
|
||||
*
|
||||
* @param string $ip
|
||||
* @param string $range
|
||||
* @return bool
|
||||
*/
|
||||
public static function inRange($ip, $range)
|
||||
{
|
||||
// Determine the type of the IP address.
|
||||
if (preg_match('/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/', $ip, $matches))
|
||||
{
|
||||
$ip = $matches[0];
|
||||
$ip_type = 4;
|
||||
}
|
||||
elseif (preg_match('/^[0-9a-f:]+$/i', $ip))
|
||||
{
|
||||
$ip = strtolower($ip);
|
||||
$ip_type = 6;
|
||||
}
|
||||
|
||||
// Determine the type of the range.
|
||||
if ($ip_type === 6 && strpos($range, ':') !== false)
|
||||
{
|
||||
$range_type = 'ipv6_cidr';
|
||||
}
|
||||
elseif ($ip_type === 4 && strpos($range, '*') !== false)
|
||||
{
|
||||
$range_type = 'ipv4_wildcard';
|
||||
}
|
||||
elseif ($ip_type === 4 && strpos($range, '-') !== false)
|
||||
{
|
||||
$range_type = 'ipv4_hyphen';
|
||||
}
|
||||
elseif ($ip_type === 4 && strpos($range, '.') !== false)
|
||||
{
|
||||
$range_type = 'ipv4_cidr';
|
||||
}
|
||||
else
|
||||
{
|
||||
$range_type = 'unknown';
|
||||
}
|
||||
|
||||
// Check!
|
||||
switch ($range_type)
|
||||
{
|
||||
case 'ipv4_cidr':
|
||||
return self::_checkIPv4CIDR($ip, $range);
|
||||
case 'ipv6_cidr':
|
||||
return self::_checkIPv6CIDR($ip, $range);
|
||||
case 'ipv4_wildcard':
|
||||
return self::_checkIPv4Wildcard($ip, $range);
|
||||
case 'ipv4_hyphen':
|
||||
return self::_checkIPv4Hyphen($ip, $range);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a range definition is valid.
|
||||
*
|
||||
* @param string $range
|
||||
* @return bool
|
||||
*/
|
||||
public static function validateRange($range)
|
||||
{
|
||||
$regexes = array(
|
||||
'/^\d+\.\d+\.\d+\.\d+(\/\d+)?$/',
|
||||
'/^\d+\.(\d+|\*)(\.(\d+|\*)(\.(\d+|\*))?)?$/',
|
||||
'/^\d+\.\d+\.\d+\.\d+-\d+\.\d+\.\d+\.\d+$/',
|
||||
'/^[0-9a-f:]+(\/\d+)?$/i',
|
||||
);
|
||||
|
||||
foreach ($regexes as $regex)
|
||||
{
|
||||
if (preg_match($regex, $range))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get real IP from CloudFlare headers.
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
public static function getCloudFlareRealIP()
|
||||
{
|
||||
if (!isset($_SERVER['HTTP_CF_CONNECTING_IP']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$cloudflare_ranges = (include RX_BASEDIR . 'common/defaults/cloudflare.php');
|
||||
foreach ($cloudflare_ranges as $cloudflare_range)
|
||||
{
|
||||
if (self::inRange($_SERVER['REMOTE_ADDR'], $cloudflare_range))
|
||||
{
|
||||
return $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given IPv4 address belongs to a IPv4 CIDR range with mask.
|
||||
*
|
||||
* Example: 172.16.0.0/12
|
||||
*
|
||||
* @param string $ip
|
||||
* @param string $range
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _checkIPv4CIDR($ip, $range)
|
||||
{
|
||||
if (strpos($range, '/') === false) $range .= '/32';
|
||||
list($range, $mask) = explode('/', $range);
|
||||
$ip = ip2long($ip) & (0xffffffff << (32 - $mask));
|
||||
$range = ip2long($range) & (0xffffffff << (32 - $mask));
|
||||
return $ip === $range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given IPv4 address belongs to a IPv6 CIDR range with mask.
|
||||
*
|
||||
* Example: 2400:cb00::/32
|
||||
*
|
||||
* @param string $ip
|
||||
* @param string $range
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _checkIPv6CIDR($ip, $range)
|
||||
{
|
||||
if (function_exists('inet_pton'))
|
||||
{
|
||||
if (strpos($range, '/') === false) $range .= '/128';
|
||||
list($range, $mask) = explode('/', $range);
|
||||
$ip = substr(bin2hex(inet_pton($ip)), 0, intval($mask / 4));
|
||||
$range = substr(bin2hex(inet_pton($range)), 0, intval($mask / 4));
|
||||
return $ip === $range;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given IPv4 address belongs to a IPv4 wildcard range.
|
||||
*
|
||||
* Example: 192.168.*.*
|
||||
*
|
||||
* @param string $ip
|
||||
* @param string $range
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _checkIPv4Wildcard($ip, $range)
|
||||
{
|
||||
$count = count(explode('.', $range));
|
||||
if ($count < 4)
|
||||
{
|
||||
$range .= str_repeat('.*', 4 - $count);
|
||||
}
|
||||
$range = str_replace(array('.', '*'), array('\\.', '\\d+'), trim($range));
|
||||
var_dump($ip, $range);
|
||||
return preg_match("/^$range$/", $ip) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given IPv4 address belongs to a IPv4 hyphen range.
|
||||
*
|
||||
* Example: 192.168.0.0-192.168.255.255
|
||||
*
|
||||
* @param string $ip
|
||||
* @param string $range
|
||||
* @return bool
|
||||
*/
|
||||
protected static function _checkIPv4Hyphen($ip, $range)
|
||||
{
|
||||
$ip = sprintf('%u', ip2long($ip));
|
||||
list($range_start, $range_end) = explode('-', $range);
|
||||
$range_start = sprintf('%u', ip2long(trim($range_start)));
|
||||
$range_end = sprintf('%u', ip2long(trim($range_end)));
|
||||
return ($ip >= $range_start && $ip <= $range_end);
|
||||
}
|
||||
}
|
||||
77
tests/Unit/framework/IpFilterTest.php
Normal file
77
tests/Unit/framework/IpFilterTest.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
class IpFilterTest extends \Codeception\TestCase\Test
|
||||
{
|
||||
public function testIPv4CIDR()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('10.0.127.191', '10.0.127.191'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('10.1.131.177', '10.1.131.178'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('127.0.0.1', '127.0.0.0/8'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('172.34.0.0', '172.16.0.0/12'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.18.214', '192.168.16.0/22'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.18.214', '192.168.16.0/23'));
|
||||
}
|
||||
|
||||
public function testIPv6CIDR()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('::1', '::1/128'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('::1', '::2'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('2400:cb00::1234', '2400:cb00::/32'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('2405:8100::1234', '2400:cb00::/32'));
|
||||
}
|
||||
|
||||
public function testIPv4Wildcard()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.134.*'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.*.*'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.136.*'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.172.*.*'));
|
||||
}
|
||||
|
||||
public function testIPv4Hyphen()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.134.0-192.168.134.255'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.128.16-192.168.145.0'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.134.242-192.168.244.7'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::inRange('192.168.134.241', '192.168.100.255-192.168.133.19'));
|
||||
}
|
||||
|
||||
public function testValidator()
|
||||
{
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.0.1'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.0.0/16'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.*.*'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.*'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('192.168.0.0-192.168.255.255'));
|
||||
$this->assertTrue(Rhymix\Framework\IpFilter::validateRange('2400:cb00::/32'));
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::validateRange('192.168.0.0~192.168.255.255'));
|
||||
}
|
||||
|
||||
public function testLegacy()
|
||||
{
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.241'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.0-192.168.134.255'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('127.0.0.1', '192.168.134.241'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.134.*'), '192.168.134.241'));
|
||||
$this->assertTrue(\IpFilter::filter(array('192.168.*'), '192.168.134.241'));
|
||||
$this->assertFalse(\IpFilter::filter(array('127.0.0.1'), '192.168.134.241'));
|
||||
}
|
||||
|
||||
public function testCloudFlareRealIP()
|
||||
{
|
||||
$_SERVER['HTTP_CF_CONNECTING_IP'] = '192.168.134.241';
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = '192.168.10.1';
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.10.1', $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
$_SERVER['REMOTE_ADDR'] = '108.162.192.121';
|
||||
$this->assertEquals('192.168.134.241', Rhymix\Framework\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.134.241', $_SERVER['REMOTE_ADDR']);
|
||||
|
||||
unset($_SERVER['HTTP_CF_CONNECTING_IP']);
|
||||
$_SERVER['REMOTE_ADDR'] = '192.168.10.1';
|
||||
$this->assertFalse(Rhymix\Framework\IpFilter::getCloudFlareRealIP());
|
||||
$this->assertEquals('192.168.10.1', $_SERVER['REMOTE_ADDR']);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue