2011-09-30 2 views
8

Quelqu'un sait d'une méthode efficace et sûre pour voir si cette entrée:Une méthode optimisée pour comparer les adresses IP avec des caractères génériques en PHP?

$_SERVER['REMOTE_ADDR'] 

matchs contre quelque chose de similaire à ce tableau de filtres incohérents (notez que 200,100 * * pourrait être exprimé en seulement 200,100 *...) avec des caractères génériques indiqués par * 's:

array(
    '192.168.1.*', 
    '192.168.2.1*', 
    '10.0.0.*', 
    '200.100.*.*', 
    '300.200.*', 
) 

Mise à jour

pensées?

foreach($instanceSettings['accessControl']['allowedIpV4Addresses'] as $ipV4Address) { 
    echo 'Now checking against '.$ipV4Address.'.'; 

    // Compare each octet 
    $ipV4AddressOctets = 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; 
    } 
} 
+0

est-il possible pour vous de spécifier également netmask de chaque adresse IP? c'est-à-dire: «192.168.100.251/26» ou «192.168.100».251 '=>' 26'' (ce masque de 26 bits peut même ne pas être valide pour l'adresse IP donnée, par exemple) Si vous pouvez spécifier des masques de réseau, alors calculer la validité de l'adresse IP est aussi simple que si ($ first_addr_of_mask> $ ip && $ last_addr_of_mask <$ ip) ' – Oerd

+0

Je ne le ferais pas en PHP mais plutôt sur le pare-feu du serveur. – hornetbzz

Répondre

1

Pourquoi ne pas simplement utiliser une expression régulière?

preg_match("((192\\.168\\.1)|(10\\.0\\.0)|(127\\.0\\.0)\\.[012]\\d{0,2}|(\\:\\:1))",$_SERVER['REMOTE_ADDR']) 
+0

Je suppose que je pourrais générer dynamiquement une regex à partir du contenu du tableau. J'ai mis à jour la question pour rendre plus évident que le tableau de filtre sera dynamique. –

+0

cette solution n'est pas nécessaire "optimisée" comme la question l'exige. Généralement, les adresses IP sont mieux traitées comme entiers (non signés ou longs) et comparer un entier à un tableau d'entiers est plus rapide que faire correspondre un tableau d'expressions régulières avec une chaîne (ie ip) – Oerd

5

Supprimer les astérisques et juste faire:

$ips = array('192.168.1.', '10.0.0.'); 

foreach ($ips as $ip) { 
    if (strpos($_SERVER['REMOTE_ADDR'], $ip) === 0) { 
     // match 
    } 
} 
+0

vient d'écrire ce que je voulais écrire (belle solution!). – Anush

+0

qu'en est-il de '192.168. *. *'? Vous devez également supprimer '..' de la chaîne $ ip. – Oerd

+0

N'attraperait pas quelque chose comme ça: 255. *. 255.255. – MasterCassim

0

Juste pour le plaisir, je vais à plus-ingénieur cela. Eh bien, sauf si vous avez une liste assez longue à comparer. En supposant que vous n'utilisez que des caractères génériques pour signifier "Je ne me soucie pas de cet octet", vous pouvez analyser chaque entrée de votre tableau en quatre valeurs (une par octet). Supposons que vous utilisiez -1 pour désigner un caractère générique, 0-255 signifie qu'il correspond exactement à cette valeur. (Si vous avez besoin de meilleures performances que O (n), où n est la taille de la liste de correspondance, alors il y a de meilleures structures de données que vous pouvez utiliser ici-un trie, par exemple.) Appelez ce tableau L. Bien sûr, vous avez seulement besoin de faire cela une fois, pas par demande.

Vous pouvez ensuite analyser l'adresse distante de la même manière (sauf sans caractères génériques). Vous pouvez également prendre REMOTE_ADDR ne pas être dans le format attendu ici, il devient assez trivial de vérifier matchs:

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 

(C'est pseudo-code, bien sûr)

+0

@stereofrog: Je crains de ne jamais avoir utilisé Refal (ni entendu parler de ça avant que vous le mentionniez). C'est tout à fait possible que j'ai lu le pseudo-code de quelqu'un qui a, cependant. Je ne suis pas tout à fait sûr comment cela ressemble à mon pseudo-code, cependant ... autre que '. 'Pour les indices. – derobert

8

Je n'ai pas marqué banc ce, mais je choisir d'utiliser la méthode qui utilise du matériel/logiciel de réseau ...

Remplacer tout * avec 0 et 255. Convertir les adresses IP aux entiers

donc, si 255.255.255. * devient 255.255.255.0 et 255.255.255.255 Ensuite, faites la fonction ip2long sur ces deux ips.

Ensuite, vous pouvez convertir l'adresse IP donnée en IP long. par exemple 255.255.50.51 en IP long.

Ensuite, vous pouvez comparer si l'ip longue pour cette adresse IP donnée est entre les ips longs convertis dans la liste noire. Si c'est le cas, ce n'est pas autorisé.

$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}"; 
} 
} 
+1

+1 pour ip2long et la comparaison numérique au lieu de la comparaison de chaînes –

+2

deuxième IP dans la liste casserait votre solution: '192.168.2.1 *' deviendra '192.168.2.10' et' 192.168.2.1255'. Alors que le premier n'est pas exactement ce que nous voulions, le second est une adresse IP complètement illégale :) – Oerd

+1

Devrait pas '$ ip <= $ givenip' devenir' $ givenip <= $ ip2' afin de détecter la plage xxx0 - xxx255? – Lekensteyn

2

Celui-ci permet tous les cas dans la question plus les masques courts sans astérisques comme 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; 
    } 
} 
Questions connexes