An optimized method to compare IP addresses with wildcards in PHP?
Anyone know of an effective and secure method to see if this input:
$_SERVER['REMOTE_ADDR']
matches against something similar to this array of inconsistent filters (note that 200.100.*.* could be expressed as just 200.100.*) with wildcards indicated by *'s:
array(
'192.168.1.*',
'192.168.2.1*',
'10.0.0.*',
'200.100.*.*',
'300.200.*',
)
Update
Thoughts?
foreach($instanceSettings['accessControl']['allowedIpV4Addresses'] as $ipV4Address) {
echo 'Now checking against '.$ipV4Address.'.';
// Compare each octet
$ipV4开发者_如何转开发AddressOctets = String::explode('.', $ipV4Address);
$remoteIpV4AddressOctets = String::explode('.', $_SERVER['REMOTE_ADDR']);
$remoteIpV4AddressIsAllowed = true;
for($i = 0; $i < Arr::size($ipV4AddressOctets); $i++) {
echo 'Comparing '.$ipV4AddressOctets[$i].' against '.$remoteIpV4AddressOctets[$i].'.';
if($ipV4AddressOctets[$i] != $remoteIpV4AddressOctets[$i] && $ipV4AddressOctets[$i] != '*') {
echo 'No match.';
$remoteIpV4AddressIsAllowed = false;
break;
}
}
// Get out of the foreach if we've found a match
if($remoteIpV4AddressIsAllowed) {
break;
}
}
I haven't bench-marked this, but I would opt to use the method that networking hardware/software uses...
Replace any * with 0 and 255. Convert the IPs to integers
So if 255.255.255.* becomes 255.255.255.0 and 255.255.255.255 Then do ip2long function on these two ips.
Then you can convert the given ip into long ip. for example 255.255.50.51 into long ip.
Then you can compare whether the long ip for this given ip is between the converted long ips in the blacklist. If it is then it is not allowed else it is.
$ips = array("ip1", "ip2");
foreach($ips as $ip){
$ip1 = str_replace("*", "0", $ip);
$ip2 = str_replace("*", "255", $ip);
$ip1 = ip2long($ip1);
$ip2 = ip2long($ip2);
$givenip = $_GET["ip"];
$givenip = ip2long($givenip);
if($givenip >= $ip1 && $ip <= $givenip){
echo "blacklist ip hit between {$ip1} and {$ip2} on {$ip}";
}
}
Remove the asterisks and just do:
$ips = array('192.168.1.', '10.0.0.');
foreach ($ips as $ip) {
if (strpos($_SERVER['REMOTE_ADDR'], $ip) === 0) {
// match
}
}
This one allows all cases in the question plus short masks with no asterisks like 123.123.
/**
* Checks given IP against array of masks like 123.123.123.123, 123.123.*.101, 123.123., 123.123.1*.*
*
* @param $ip
* @param $masks
* @return bool
*/
public static function checkIp($ip, $masks)
{
if (in_array($ip, $masks)) {
return true; // Simple match
} else {
foreach ($masks as $mask) {
if (substr($mask, -1) == '.' AND substr($ip, 0, strlen($mask)) == $mask) {
return true; // Case for 123.123. mask
}
if (strpos($mask, '*') === false) {
continue; // No simple matching and no wildcard in the mask, leaves no chance to match
}
// Breaking into triads
$maskParts = explode('.', $mask);
$ipParts = explode('.', $ip);
foreach ($maskParts as $key => $maskPart) {
if ($maskPart == '*') {
continue; // This triad is matching, continue with next triad
} elseif (strpos($maskPart, '*') !== false) {
// Case like 1*, 1*2, *1
// Let's use regexp for this
$regExp = str_replace('*', '\d{0,3}', $maskPart);
if (preg_match('/^' . $regExp . '$/', $ipParts[$key])) {
continue; // Matching, go to check next triad
} else {
continue 2; // Not matching, Go to check next mask
}
} else {
if ($maskPart != $ipParts[$key]) {
continue 2; // If triad has no wildcard and not matching, check next mask
}
// otherwise just continue
}
}
// We checked all triads and all matched, hence this mask is matching
return true;
}
// We went through all masks and none has matched.
return false;
}
}
Why not just use a regular expression?
preg_match("((192\\.168\\.1)|(10\\.0\\.0)|(127\\.0\\.0)\\.[012]\\d{0,2}|(\\:\\:1))",$_SERVER['REMOTE_ADDR'])
Just for fun, I'm going to over-engineer this. Well, unless you have a fairly long list to match against.
Assuming you're only using wildcards to mean "I don't care about this octet", then you can parse each entry in your array into four values (one per octet). Say that you use -1 to mean wildcard, 0–255 means to match that value exactly. (If you need better performance than O(n), where n is the size of the match list, then there are better data structures you can use here—a trie, for example.) Call this array L. Of course, you only need do this once—not per-request.
You can then parse the remote address the same way (except without wildcards). You can also catch REMOTE_ADDR not being in the expected format here It now becomes fairly trivial to check matches:
has_match(ip) =
for n in [0 … L.length)
if (-1 == L.n.0 || L.n.0 = ip.0) && (-1 == L.n.1 || L.n.1 == ip.1) && …
return true
return false
(That's pseudo-code, of course)
This one is based on preg_match(), you provide the pattern and the subject to test it against.
/**
* ip_match("172.30.20.*", "172.30.20.162"); // true
* ip_match("172.30.20", "172.30.20.162"); // true; works if incomplete
* ip_match("172.30.*.12", "172.30.20.12"); // true
* ip_match("172.30.*.12", "172.30.20.11"); // false
* ip_match("172.30.20.*", "172.30.20.*"); // true; wildcards in the subject will match with wildcards in the pattern
* ip_match("172.30.20.12", "172.30.*.*"); // false
*
* @param $pattern The pattern to test against as a string
* @param $subject The input string
*
* @return bool
*
*/
function ip_match($pattern, $subject) {
$pattern = explode(".", trim($pattern, "."));
$pattern = array_pad($pattern, 4, "*");
$subject = explode(".", trim($subject, "."));
$subject = array_pad($subject, 4, "1");
foreach ($pattern as $i => $octet) {
if ($octet != "*" && $subject[$i] != $octet) {
return false;
}
}
return true;
}
<?php
function ipArrCheck($allowedIPArr = [], $IP = '')
{
$IP_ARR = explode('.', $IP);
$resultArr = [];
foreach ($IP_ARR as $IPkey => $IPvalue) {
foreach ($allowedIPArr as $IPArrKey => $IPArrValue) {
$checkIPArr = explode('.', $IPArrValue);
$resultArr[$IPArrKey][$IPkey] = $checkIPArr[$IPkey] == $IP_ARR[$IPkey] || $checkIPArr[$IPkey] == '*';
}
}
foreach ($resultArr as $value) {
if (count(array_unique($value)) == 1 && current($value)) {
return TRUE;
}
}
return FALSE;
}
$MY_IP = '192.168.52.10';
$ALLOWED_IP_ARR = ['127.0.0.1', '192.168.*.*'];
var_dump(ipArrCheck($ALLOWED_IP_ARR, $MY_IP));
精彩评论