PHP класс для обработки IP адресов
В классе заложены все минимально необходимые функции для обработки IP адресов на PHP (Internet Protocol address (IP address)), по умолчанию приводит адреса к короткому(сжатому) виду, в том числе переводя IPv4 адреса зашифрованные в IPv6 адресе к чистому IPv4 виду. Дополнительно преобразовывает в long числа для удобного хранения и сравнения.
В конце страницы приведу функцию благодаря которой проверял работоспособность и сгенерировал диапазоны масок IP адресов разной версии, дополнительно в них объясню как банить IPv4 и IPv6 адреса. На странице преобразования IP адресов, демонстрирующих работу библиотеки, можно проверить как будет распознана та или иная запись..
PHP класс сделан в большей части статическим и самодостаточным, для возможности использования без создания объекта и в любом месте. Для сравнения диапазонов используется расширение PHP BCMath из-за диапазонов выходящих за пределы int64 (IPv6). Насколько могу судить, в данный момент BCMath по умолчанию установлено на большинстве серверов.
Функции класса ip
Подключение библиотеки:
require D.'deny/ip.php';
Служебные функции помечены таким цветом.
public переменные
$ip->cidr='' //CIDR $ip->first='' //IP v4 или v6 в сжатой форме $ip->last='' //Если задан диапазон - второй адрес (конец диапазона) $ip->firstlong='' //IP в виде long числа $ip->lastlong='' Переменные станут доступны после создания экземпляра класса $ip=new ip('127.0.0.1'); $ip=new ip('ffff::f0f0'); ссылка-якорь на переменные
new ip($s) - создание объекта
$s - строка содержащая IP адрес/а. Вызов метода __construct() класса. Всеяден, принимает как единичные IP адреса в любой форме, так и диапазоны через минус(тире) - или сидр(CIDR). Сжимает IPv6 к короткому виду, преобразует IPv4 закодированные в IPv6 к их IPv4 виду. Примеры: Равнозначны и приведут IP к виду 192.168.1.2 $ip=new ip('0000:0000:0000:0000:0000:ffff:c0a8:0102'); $ip=new ip('0:0:0:0:0:ffff:c0a8:102'); //Укороченный $ip=new ip('::ffff:c0a8:102'); //IPv6 Compressed address (short) сжатый уплотненный $ip=new ip('::ffff:192.168.1.2'); //Mapped address $ip=new ip('2002:c0a8:58fe::'); //Маршрутизация IPv6 в IPv4 - маршрутный адрес роутеров/маршрутизаторов.. echo $ip->first; //192.168.1.2 $ip=new ip('fe80:0000:0000:0000:c1f5:6fc7:eef9:3341'); echo $ip->first; //fe80::c1f5:6fc7:eef9:3341 Диапазон адресов $ip=new ip('192.168.0.0 - 192.168.255.255'); $ip=new ip('192.168.'); $ip=new ip('192.168'); echo $ip->first,' - ',$ip->last; //192.168.0.0 - 192.168.255.255 $ip=new ip('fe80::c1f5:6fc7:eef9:3341 - fe80::c1f5:6fc7:eef9:3341'); $ip=new ip('fe80:ff:c1f5:6fc7:* - fe80:ff:c1f5:6fc7:eef9:3341'); $ip=new ip('fe80:ff:c1f5:6fc7:*'); $ip=new ip('fe80:ff:c1f5:6fc7'); И в виде long $ip=new ip('2130706431 - 22312312312321334534534'); CIDR диапазон адресов $ip=new ip('192.168.1.2/16'); //mask = 255.255.0.0 $ip=new ip('192.168/16'); echo $ip->first,' - ',$ip->last; //192.168.0.0 - 192.168.255.255 $ip=new ip('fe80:1111:1222:1333:c1f5:6fc7:eef9:3341/64'); //mask = ffff:ffff:ffff:ffff:0000:0000:0000:0000 //fe80:1111:1222:1333:0000:0000:0000:0000 - fe80:1111:1222:1333:ffff:ffff:ffff:ffff echo $ip->first,' - ',$ip->last; //fe80:1111:1222:1333:: - fe80:1111:1222:1333:ffff:ffff:ffff:ffff Как уже заметили, IP адреса можно сокращать, избегайте употреблять с сокращением IPv6 :: функция не будет его дополнять до полной формы, что приведёт к ошибке в обработке. Например, если мы захотим заблокировать одну из подсетей Роскомнадзора, равнозначно можно записать это так: 217.106.0.0 - 217.106.255.255 = 217.106.0.0/16 = 217.106./16 = 217.106/16 = 217.106.* = 217.106 = 217.106.0 - 217.106.255 Ещё примеры: 10.0.0.0 - 10.255.255.255 = 10/8 = 10. = 10 192.168.0.0 - 192.168.255.255 = 192.168/16 = 192.168 1.0.0.15 - 1.255.255.25 = 1.* = 1. - 1. = 1.*.*.15 - 1.*.*.25 1.2.3.0 - 1.2.3.255 = 1.2.3.0/24 = 1.2.3/24 = 1.2.3.* 1.2.0.0 - 1.2.255.255 = 1.2.0.0/16 = 1.2/16 = 1.2.* 1.0.0.0 - 1.255.255.255 = 1.0.0.0/8 = 1/8 = 1.* ссылка-якорь на конструктор new ip
ip::cidr2bin($i,$v=4) - преобразует CIDR с бинарный вид
$i - число, CIDR. $v - число, версия протокола IPv4 или IPv6. CIDR - Classless Inter-Domain Routing - Бесклассовая междоменная маршрутизация echo ip::cidr2bin(16); //11111111111111110000000000000000 [32] echo ip::cidr2bin(16,6); //11111111111111110000...00000000 [128] ссылка-якорь на функцию cidr2bin
ip::cidr2mask($i,$v=4) - преобразует CIDR в маску сети
$i - число, CIDR. $v - число, версия протокола IPv4 или IPv6. CIDR - Classless Inter-Domain Routing - Бесклассовая междоменная маршрутизация echo ip::cidr2mask(16); //255.255.0.0 echo ip::cidr2mask(16,6); //ffff:: ссылка-якорь на функцию cidr2mask
ip::cidrverify($i,$v=4) - проверяет корректность CIDR
$i - число, CIDR. $v - число, версия протокола IPv4 или IPv6. Приводит и возвращает $i к int, проверяет диапазон возможных значений CIDR в зависимости от протокола. Необходима во избежание последующих ошибок при преобразовании, в случае некорректного CIDR - inet_pton начнёт сыпать предупреждениями. echo ip::cidrverify(16,4); //16 echo ip::cidrverify(160,4); //32 echo ip::cidrverify(16,6); //16 echo ip::cidrverify(160,6); //128 ссылка-якорь на функцию cidrverify
ip::clear($s,$f=0,$a=[]) - восстанавливает форму IP
$s - строка, IP адрес в виде строки или числа, или сокращённого IP адреса. $f - дополнить нулями в конце или 1 - максимальным значением 255 / ffff. $a - массив символов после которых обрезать адрес (включая символ). Приводит IP адрес к чистой/полной форме, если это IPv4 в форме IPv6 - вернёт IPv4. Если IP в виде числа, преобразует к обычной форме 127.0.0.1 итп. Если IP адрес сокращён, дополняет "хвост" .0.0.. или .255 / :ffff (параметр $f). Возвращает массив [0=>'восстановленный IP адрес', 1=>4/6 (версия протокола)]; $a=ip::clear('172.16.*',0,['/','*']); echo $a[0],' version = ',$a[1]; //172.16.0.0 version = 4 $a=ip::clear('ffff:ffff',0,['/']); echo $a[0],' version = ',$a[1]; //ffff:ffff:: version = 6 ссылка-якорь на функцию clear
ip::compress($s,$m=1) - сжимает IP адрес
$s - IP адрес в виде строки. $m - преобразовывать IPv4 в mapped address форму, (по умолчанию inet_ntop(inet_pton($s)) вернёт mapped - ::ffff:192.168.1.2) Вернёт строку - IPv6 Compressed address (short) сжатый уплотненный Допускает наличие CIDR в адресе. echo ip::compress('192.168.1.2/16'); //92.168.1.2/16 echo ip::compress('0000:0000:0000:0000:0000:ffff:b934:01ed'); //::ffff:192.168.1.2 echo ip::compress('0000:0000:0000:0000:0000:ffff:b934:01ed',0); //::ffff:c0a8:102 echo ip::compress('2002:b934:01ed::'); //2002:b934:1ed:: echo ip::compress('2002:b934:01ed::',0); //2002:b934:1ed:: echo ip::compress('0:0:0:0:0:ffff:b934:1ed',0); //::ffff:c0a8:102 echo ip::compress('2001:0:0:0:6dcd:0:0:0'); //2001::6dcd:0:0:0 ссылка-якорь на функцию compress
ip::count($f,$l=0) - подсчитывает количество адресов
$f - начальный IP адрес (или в виде числа long (в строке)). $l - конечный IP адрес. Возвращает строку с числом. //max: IPv4=4294967296; IPv6=340282366920938463463374607431768211456 echo ip::count('192.168.1.0'); //3232235777 echo ip::count('192.168.1.0','192.168.1.0'); //1 echo ip::count('192.168.1.1','192.168.1.0'); //2 echo ip::count('192.168.1.10','192.168.1.20'); //11 echo ip::count('::ffff:192.168.1.2','::ffff:192.168.1.8'); //7 echo ip::count('192.168.1.2','::ffff:192.168.1.3'); //281470681743362 echo ip::count('192.168.1.2','ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'); //340282366920938463463374607428535975678 echo ip::count('68719476736','125544568719476736'); //125544500000000001 ссылка-якорь на функцию count
ip::expand($s,$f=1) - разворачивает IP адрес
$s - IP адрес. $f - 1 в полной или 0 в сокращённой форме. echo ip::expand('192.168.1.2'); //192.168.1.2 echo ip::expand('192.168.1.2',0); //192.168.1.2 echo ip::expand('::ffff:192.168.1.2'); //0000:0000:0000:0000:0000:ffff:c0a8:0102 echo ip::expand('::ffff:192.168.1.2',0); //0:0:0:0:0:ffff:c0a8:102 echo ip::expand('ffff::c0a8:0102'); //ffff:0000:0000:0000:0000:0000:c0a8:0102 echo ip::expand('ffff::c0a8:0102',0); //ffff:0:0:0:0:0:c0a8:102 //преобразование к маршрутному типу 2002:... IPv4 не делал, если необходимо, то: echo str_replace('0:0:0:0:0:ffff','2002',ip::expand(ip::v4to6('192.168.1.2'),0)).'::'; //2002:c0a8:102:: ссылка-якорь на функцию expand
ip::fill($s,$f=0) - заполняет IP адрес
$s - IP адрес. $f - дополнить конец 0 = 0; 1 = 255|ffff. Возвращает массив [0=>'ip адрес', 1=>0|1 изменён ли ip]; Функция дополняет до полного количества разрядов IP адрес. Допускает наличие CIDR в адресе. $a=ip::fill('10/8'); echo $a[0],', ip изменён: ',$a[1]; //10.0.0.0/8, изменён: 1 $a=ip::fill('192.168/8',1); echo $a[0],', ip изменён: ',$a[1]; //192.168.255.255/8, изменён: 1 $a=ip::fill('ffff::c0a8:0102'); echo $a[0],', ip изменён: ',$a[1]; //ffff::c0a8:0102, изменён: 0 $a=ip::fill('ffff:c0a8'); echo $a[0],', ip изменён: ',$a[1]; //ffff:c0a8:0:0:0:0:0:0, изменён: 1 $a=ip::fill('ffff:c0a8',1); echo $a[0],', ip изменён: ',$a[1]; //ffff:c0a8:ffff:ffff:ffff:ffff:ffff:ffff, изменён: 1 ссылка-якорь на функцию fill
ip::frombin($s) - преобразует бинарную запись в IP
$s - IP адрес в бинарной форме. echo ip::frombin('11111111111100111111111111111100'); //255.243.255.252 ссылка-якорь на функцию frombin
ip::fromlong($s) - преобразует число в IP
$s - IP адрес в виде числа long. echo ip::fromlong('3232235778'); //192.168.1.2 echo ip::fromlong('281473913979138'); //::ffff:192.168.1.2 echo ip::fromlong('281473913979138'); //::ffff:192.168.1.2 echo ip::fromlong('28147391123434273453979138'); //::17:4871:215a:c5d2:71f1:3602 ссылка-якорь на функцию fromlong
$ip->in($s) - сравнение диапазонов адресов
$s - IP адрес, диапазон итп. Через запятую , или точку с запятой ; Функция станет доступна после создания экземпляра класса $ip=new ip('127.0.0.1'); echo $ip->in('10000000 - 281473911234342734, 128.0.0.0 - 10.255.255.255; 10/8'); //1 echo $ip->in('100.100./16'); //0 echo $ip->in('127.0-::17:4871:215a:c5d2:71f1:3602'); //1 ссылка-якорь на функцию in
ip::inlong($i,$f,$l='') - сравнение адресов
Все адреса в виде числовой строки в виде long. $i - проверяемый IP адрес. $f - начало диапазона. $l - конец диапазона, не обязателен. echo ip::inlong('23434567676756','234563453734567','45634565678696789'); //0 Более быстрый способом проверки - хранить в long все IP адреса: require D.'deny/ip.php'; $banned=0; $testip=ip::tolong(IP); $banips='2130706431 - 22312312312321334534534; 3434523453452345'; $a=explode(';',str_replace(' ','',$banips)); foreach($a as $k=>$v){ if(ip::sp($v,'-'))$b=explode('-',$v); else $b=[0=>$v,1=>'']; if(ip::inlong($testip,$b[0],$b[1])){$banned=1;break;} } if($banned){header('HTTP/1.1 403 Forbidden');exit('banned');} ссылка-якорь на функцию inlong
ip::isint($v) - является ли переменная целым числом
$v - проверяемая переменная. echo var_dump(ip::isint('23434567676756')); //bool(true) echo var_dump(ip::isint(23434567676756)); //bool(true) echo var_dump(ip::isint('2343456.00')); //bool(false) echo var_dump(ip::isint(2343456.00)); //bool(true), хвост .00 срежет PHP (особенности хранения переменных). ссылка-якорь на функцию isint
$ip->get($f=1,$l=0) - вернуть ip адрес
$f - 1 первый адрес $ip->first. 0|2 - Последний $ip->last. $l - вернуть числом - $ip->firstlong, $ip->lastlong; Возвращает строку, пустую если ip нет. Функция станет доступна после создания экземпляра класса $ip=new ip('127.0.0.1 - ffff::'); echo $ip->get(); //127.0.0.1 echo $ip->get(2); //ffff:: echo $ip->get(1,1); //2130706433 echo $ip->get(2,1); //340277174624079928635746076935438991360 ссылка-якорь на функцию get
ip::long2bin($s,$v=4) - преобразует число в бинарный вид
$s - число, long IP адрес. $v - версия протокола, можно пропускать - необходима исключительно для правильного отображения 0 адреса. //127.0.0.1 echo ip::long2bin('2130706433',4); //01111111000000000000000000000001 echo ip::long2bin('2130706433',6); //01111111000000000000000000000001 echo ip::frombin(ip::long2bin('340277174624079928635746076935438991360',4)); //ffff:: echo ip::frombin(ip::long2bin('340277174624079928635746076935438991360',6)); //ffff:: echo ip::long2bin(0); //00000000000000000000000000000000 [32] echo ip::long2bin('',4); //00000000000000000000000000000000 [32] echo ip::long2bin('',6); //00000000000000...000000000000000 [128] ссылка-якорь на функцию long2bin
ip::tobin($s,$v=4) - преобразует IP в бинарный вид
$s - IP адрес. $v - версия протокола, можно пропускать - необходима исключительно для правильного отображения 0 адреса. echo ip::tobin('127.0.0.1'); //01111111000000000000000000000001 echo ip::tobin(0); //00000000000000000000000000000000 [32] echo ip::tobin('',4); //00000000000000000000000000000000 [32] echo ip::tobin('',6); //00000000000000...000000000000000 [128] ссылка-якорь на функцию tobin
ip::tolong($s,$v=0) - преобразует IP в число
$s - IP адрес. $v - версия протокола, если не задана - функция сама определит, оставлена для внешней оптимизации. echo ip::tolong('192.168.1.2'); //3232235778 echo ip::tolong('::ffff:192.168.1.2'); //281473913979138 echo ip::tolong('0000:0000:0000:0000:0000:ffff:c0a8:0102'); //281473913979138 echo ip::tolong('ffff::'); //340277174624079928635746076935438991360 ссылка-якорь на функцию tolong
ip::shorten($s) - преобразует IP в укороченную форму
$s - IP адрес. Производит подвызов функции ip::expand($s,0); echo ip::shorten('192.168.1.2'); //192.168.1.2 echo ip::shorten('::ffff:192.168.1.2'); //0:0:0:0:0:ffff:c0a8:102 echo ip::shorten('0000:0000:0000:0000:0000:ffff:c0a8:0102'); //0:0:0:0:0:ffff:c0a8:102 echo ip::shorten('0:0:0:0:0:ffff:c0a8:102'); //0:0:0:0:0:ffff:c0a8:102 echo ip::shorten('::ffff:b934:1ed'); //0:0:0:0:0:ffff:b934:1ed ссылка-якорь на функцию shorten
ip::sp($s,$f) - проверяет вхождение строки в другую
$s - строка. $f - что ищем. Производит подвызов mb_strpos. echo var_dump(ip::sp('string b','%b')); //bool(false) echo var_dump(ip::sp('string %b','%b')); //bool(true) ссылка-якорь на функцию sp
ip::v4to6($s,$m=1) - преобразует IPv4 в IPv6
$s - IP адрес. $m - преобразовывать IPv4 в mapped address форму echo ip::v4to6('192.168.1.2'); //::ffff:192.168.1.2 echo ip::v4to6('192.168.1.2',0); //::ffff:c0a8:102 //По факту система адресов разная и выделение провайдером/роутером дополнительного IPv6 адреса, вовсе не значит что он хоть как-то будет равен IPv4. ссылка-якорь на функцию v4to6
ip::v6to4($s) - преобразует IPv6 в IPv4
$s - IP адрес. Вернёт пустую строку в случае неудачи. echo ip::v6to4('ffff:b934:1ed:ffc::'); //'' echo ip::v6to4('::ffff:192.168.1.2'); //192.168.1.2 echo ip::v6to4('2002:c0a8:102:0000:0000:0000:0000:0000'); //192.168.1.2 echo ip::v6to4('2002:c0a8:102:0:0:0:0:0'); //192.168.1.2 2002: = Маршрутизация IPv6 в IPv4 - маршрутный адрес роутеров.. //По факту система адресов разная и выделение провайдером/роутером дополнительного IPv6 адреса, вовсе не значит что он хоть как-то будет равен IPv4. ссылка-якорь на функцию v6to4
ip::ver($s) - определяет версию протокола IP
$s - IP адрес в виде строки или long. Вернёт пустую строку в случае неудачи. echo ip::ver('abracadabra'); //0 echo ip::ver('192.168.1.2'); //4 echo ip::ver('3232235778'); //4 echo ip::ver('ffff:b934:1ed:ffc::'); //6 echo ip::ver('::ffff:192.168.1.2'); //6 echo ip::ver('281473913979138'); //6 echo ip::ver(281473913979138); //6 echo ip::ver('::ffff:c0a8:0102'); //6 echo ip::ver('281473913979138'); //6 ссылка-якорь на функцию ver
В SQL базе данных можно хранить ip адреса в запакованном(inet_pton) виде, в виде бинарных данных BINARY(16), но при этом необходимо понимать в битовых операциях для выборок и сравнения, плюс встаёт вопрос дампов базы данных и возможном попадании туда некорректных данных и последующих проблем "на ровном месте". И это противоречит заложенному постулату о простоте движка, поэтому хранить ip в базе рекомендую как DECIMAL(39,0), при этом можно будет делать выборку по диапазонам средствами SQL и используя long функции (ip::tolong(), ip::fromlong()).
ip DECIMAL(39,0) NOT NULL DEFAULT 0,
136111847226264995686342697210517200896 =39
Ещё вариант хранения в VAR/CHAR(39), число или IPv6 поместится:
ip char(39) NOT NULL DEFAULT '',
0000:0000:0000:0000:0000:0000:0000:0000 =39
Функция генерации диапазонов масок IP
require D.'deny/ip.php'; //$v=ver(4|6); f=format (d - symbols space); $r=revers function maskmap($v=4,$f='/%i %s = %b; %m; %n<br>',$r=1){ $b=ip::sp($f,'%b');//bin $c=ip::sp($f,'%c');//compacted mask v6 $l=ip::sp($f,'%l');//long $m=ip::sp($f,'%m');//mask $n=ip::sp($f,'%n');//ip count if(4==$v){$z=33;$q='';}else{$z=129;$q=' ';} $k=$r?$z-1:0; $a=$t=$s=$u=$p=''; for($i=0;$i<$z;$i++){ if(10>$k)$s=$q.' '; elseif(100>$k)$s=$q; else $s=''; $u=str_replace('%i',$k,$f); $u=str_replace('%s',$s,$u); $a=ip::cidr2bin($k,$v); if($b)$u=str_replace('%b',$a,$u); $p=ip::frombin($a); if($c)$u=str_replace('%c',$p,$u); $p=ip::expand(ip::frombin($a)); if($m)$u=str_replace('%m',ip::expand($p),$u); if($n){ $y=ip::tolong($p); $j=inet_pton($p); $e=ip::tolong(inet_ntop($j|~$j)); $u=str_replace('%n',bcadd(bcsub($e,$y),1),$u); } if($l)$u=str_replace('%l',ip::tolong($p,$v),$u); $t.=$u; $k=$r?$k-1:$k+1; } return $t; } echo '<h2>Таблица сетевых масок IPv4</h2>',maskmap(4); echo '<h2>Таблица сетевых масок IPv6</h2>',maskmap(6);