После тщательного поиска, я заметил небольшое отсутствие функций в PHP для обработки IPv6 . Для моего личного удовлетворения я создал несколько функций, чтобы помочь переходу.
Функция IPv6ToLong()
является временным решением, которое было IPv6ToLong()
здесь: Как сохранить IPv6-совместимый адрес в реляционной базе данных . Он разделит IP на два целых числа и вернет их в массив.
/** * 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); }
Для этих функций я обычно реализую их, сначала вызывая эту функцию:
/** * 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); }
Кто-то, пожалуйста, скажите мне, если я изобретаю колесо здесь, или я сделал что-то неправильно.
Эта реализация преобразует IPv4 в IPv6. Любой адрес IPv6, на который он не касается.
Как насчет inet_ntop()
? Затем вместо измельчения вещей в целые числа вы просто используете varbinary(16)
для его хранения.
Расширение фильтра PHP.net содержит некоторые константы для сопоставления адресов IPv4 и IPv6, которые могут быть полезны для проверки адреса. Однако я не видел никаких утилит конверсии.
Вы также можете сохранить адрес в двоичном формате (16) в mysql, поэтому у вас должна быть возможность вывести его в двоичном формате из IPv6ToLong ().
Это действительно что-то, что нужно добавить изначально на PHP, особенно когда многие веб-серверы с поддержкой IPv6 сообщают: FFFF: 1.2.3.4 как клиентский IP и несовместимы с ip2long, и будут ломать много вещей.
Вот альтернативная функция, использующая 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; }
Вернувшись, я написал две функции: dtr_pton
и dtr_ntop
которые работают как с IPv4, так и с IPv6. Он преобразует их обратно и вперед между печатными и двоичными.
Первая функция dtr_pton
будет проверять, является ли аргумент, приведенный в комплекте, действительным IPv4 или действительным IPv6. В зависимости от результата может быть выбрано исключение или может быть возвращено двоичное представление IP. Используя эту функцию, вы можете выполнить AND'ing или OR'ing против результата (для подсети / whathaveyou). Я предлагаю вам хранить их в своей базе данных как VARBINARY(39)
или VARCHAR(39)
.
/** * dtr_pton * * Converts a printable IP into an unpacked binary string * * @author Mike Mackintosh - mike@bakeryphp.com * @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; }
Вторая функция dtr_ntop
преобразует двоичное представление IP обратно в IP-адрес для печати.
/** * dtr_ntop * * Converts an unpacked binary string into a printable IP * * @author Mike Mackintosh - mike@bakeryphp.com * @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; }
Кроме того, здесь представлен быстрый способ расширения адресов IPv6, найденных в 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; }
Кроме того, хорошее чтение по этому вопросу можно найти в HighOnPHP: 5 советов по работе с IPv6 в PHP . В этой статье используются некоторые из описанных выше методов в объектно-ориентированном классе, который можно найти в GitHub: mikemackintosh \ dTR-IP