web-dev-qa-db-fra.com

Utilisation des adresses IPv6 dans PHP

Après une recherche assez approfondie, j'ai remarqué un léger manque de fonctions dans PHP pour la gestion IPv6 . Pour ma propre satisfaction personnelle, j'ai créé quelques fonctions pour faciliter la transition.

La fonction IPv6ToLong() est une solution temporaire à celle évoquée ici: Comment stocker une adresse compatible IPv6 dans une base de données relationnelle . Il divisera l'IP en deux entiers et les renverra dans un tableau.

/**
 * Convert an IPv4 address to IPv6
 *
 * @param string IP Address in dot notation (192.168.1.100)
 * @return string IPv6 formatted address or false if invalid input
 */
function IPv4To6($Ip) {
    static $Mask = '::ffff:'; // This tells IPv6 it has an IPv4 address
    $IPv6 = (strpos($Ip, '::') === 0);
    $IPv4 = (strpos($Ip, '.') > 0);

    if (!$IPv4 && !$IPv6) return false;
    if ($IPv6 && $IPv4) $Ip = substr($Ip, strrpos($Ip, ':')+1); // Strip IPv4 Compatibility notation
    elseif (!$IPv4) return $Ip; // Seems to be IPv6 already?
    $Ip = array_pad(explode('.', $Ip), 4, 0);
    if (count($Ip) > 4) return false;
    for ($i = 0; $i < 4; $i++) if ($Ip[$i] > 255) return false;

    $Part7 = base_convert(($Ip[0] * 256) + $Ip[1], 10, 16);
    $Part8 = base_convert(($Ip[2] * 256) + $Ip[3], 10, 16);
    return $Mask.$Part7.':'.$Part8;
}

/**
 * Replace '::' with appropriate number of ':0'
 */
function ExpandIPv6Notation($Ip) {
    if (strpos($Ip, '::') !== false)
        $Ip = str_replace('::', str_repeat(':0', 8 - substr_count($Ip, ':')).':', $Ip);
    if (strpos($Ip, ':') === 0) $Ip = '0'.$Ip;
    return $Ip;
}

/**
 * Convert IPv6 address to an integer
 *
 * Optionally split in to two parts.
 *
 * @see https://stackoverflow.com/questions/420680/
 */
function IPv6ToLong($Ip, $DatabaseParts= 2) {
    $Ip = ExpandIPv6Notation($Ip);
    $Parts = explode(':', $Ip);
    $Ip = array('', '');
    for ($i = 0; $i < 4; $i++) $Ip[0] .= str_pad(base_convert($Parts[$i], 16, 2), 16, 0, STR_PAD_LEFT);
    for ($i = 4; $i < 8; $i++) $Ip[1] .= str_pad(base_convert($Parts[$i], 16, 2), 16, 0, STR_PAD_LEFT);

    if ($DatabaseParts == 2)
            return array(base_convert($Ip[0], 2, 10), base_convert($Ip[1], 2, 10));
    else    return base_convert($Ip[0], 2, 10) + base_convert($Ip[1], 2, 10);
}

Pour ces fonctions, je les implémente généralement en appelant d'abord cette fonction:

/**
 * Attempt to find the client's IP Address
 *
 * @param bool Should the IP be converted using ip2long?
 * @return string|long The IP Address
 */
function GetRealRemoteIp($ForDatabase= false, $DatabaseParts= 2) {
    $Ip = '0.0.0.0';
    if (isset($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != '')
        $Ip = $_SERVER['HTTP_CLIENT_IP'];
    elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != '')
        $Ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
    elseif (isset($_SERVER['REMOTE_ADDR']) && $_SERVER['REMOTE_ADDR'] != '')
        $Ip = $_SERVER['REMOTE_ADDR'];
    if (($CommaPos = strpos($Ip, ',')) > 0)
        $Ip = substr($Ip, 0, ($CommaPos - 1));

    $Ip = IPv4To6($Ip);
    return ($ForDatabase ? IPv6ToLong($Ip, $DatabaseParts) : $Ip);
}

Quelqu'un, s'il vous plaît, dites-moi si je réinvente la roue ici ou si j'ai fait quelque chose de mal.

Cette implémentation convertit IPv4 en IPv6. Toute adresse IPv6 qu'elle ne touche pas.

31
matpie

Que diriez-vous inet_ntop() ? Ensuite, au lieu de découper les choses en nombres entiers, vous utilisez simplement une varbinary(16) pour la stocker.

17
user42092

Voici une fonction alternative utilisant filter_var (PHP> = 5.2)

function IPv4To6($ip) {
 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === true) {
  if (strpos($ip, '.') > 0) {
   $ip = substr($ip, strrpos($ip, ':')+1);
  } else { //native ipv6
   return $ip;
  }
 }
 $is_v4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
 if (!$is_v4) { return false; }
 $iparr = array_pad(explode('.', $ip), 4, 0);
    $Part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16);
    $Part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16);
    return '::ffff:'.$Part7.':'.$Part8;
}
1
datahell

Vous pouvez également stocker l'adresse dans un binaire (16) dans mysql, vous devriez donc avoir une option pour la sortir en binaire depuis IPv6ToLong ().

C'est vraiment quelque chose qui doit être ajouté nativement en PHP, en particulier lorsque de nombreux serveurs Web compatibles IPv6 signalent :: FFFF: 1.2.3.4 comme IP client et il est incompatible avec ip2long, et cassera beaucoup de choses.

1
Fredrik

Pour revenir en arrière, j'ai écrit deux fonctions, dtr_pton Et dtr_ntop Qui fonctionnent avec IPv4 et IPv6. Il les convertira d'avant en arrière entre imprimable et binaire.

La première fonction, dtr_pton Vérifiera si l'argument fourni est IPv4 valide ou IPv6 valide. Selon le résultat, une exception peut être levée ou la représentation binaire de l'adresse IP peut être renvoyée. En utilisant cette fonction, vous pouvez ensuite effectuer AND'ing ou OR'ing contre le résultat (pour le sous-réseau/whathaveyou). Je vous suggère de les stocker dans votre base de données en tant que VARBINARY(39) ou VARCHAR(39).

/**
* dtr_pton
*
* Converts a printable IP into an unpacked binary string
*
* @author Mike Mackintosh - [email protected]
* @param string $ip
* @return string $bin
*/
function dtr_pton( $ip ){

    if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){
        return current( unpack( "A4", inet_pton( $ip ) ) );
    }
    elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){
        return current( unpack( "A16", inet_pton( $ip ) ) );
    }

    throw new \Exception("Please supply a valid IPv4 or IPv6 address");

    return false;
}

La deuxième fonction, dtr_ntop Convertira la représentation binaire de l'adresse IP en une adresse IP imprimable.

/**
* dtr_ntop
*
* Converts an unpacked binary string into a printable IP
*
* @author Mike Mackintosh - [email protected]
* @param string $str
* @return string $ip
*/
function dtr_ntop( $str ){
    if( strlen( $str ) == 16 OR strlen( $str ) == 4 ){
        return inet_ntop( pack( "A".strlen( $str ) , $str ) );
    }

    throw new \Exception( "Please provide a 4 or 16 byte string" );

    return false;
}

En outre, voici un moyen rapide d'étendre les adresses IPv6 trouvé sur StackOverflow

function expand($ip){
    $hex = unpack("H*hex", inet_pton($ip));         
    $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);

    return $ip;
}

En outre, une bonne lecture sur le sujet peut être trouvée sur mon blog à HighOnPHP: 5 conseils pour travailler avec IPv6 en PHP . Cet article utilise certaines des méthodes décrites ci-dessus dans une classe orientée objet qui peut être trouvée à GitHub: mikemackintosh\dTR-IP

1
Mike Mackintosh

L'extension de filtre de PHP.net contient quelques constantes pour faire correspondre les adresses IPv4 et IPv6 , ce qui peut être utile pour vérifier l'adresse. Je n'ai cependant vu aucun utilitaire de conversion.

1
Ross