Trabaja con IPv6 en PHP

Recientemente recibimos el estado de LIR y el bloque / 29 IPv6. Y luego era necesario hacer un seguimiento de las subredes designadas. Y dado que nuestra facturación se escribió en PHP, tuve que inspirarme un poco por el problema y darme cuenta de que este lenguaje no es el más amigable en términos de trabajo con IPv6. Under the cut: nuestra solución a los problemas de trabajar con direcciones y rangos. Quizás no sea el más elegante, pero realiza las tareas.



Poco de teoría


Descargo de responsabilidad. Si está familiarizado con lo que es IPv6 y con qué se come, esta parte puede ser aburrida para usted. Puede que no sea.

Las personas que primero ven la anotación IPv6 pueden desanimarse. Después del elegante 64.233.177.101, de repente nos encontramos con 2607: f8b0: 4002: c08 :: 8b y podemos confundirnos. Tanto eso como otro, solo una representación legible por humanos de 32 y 128 bits respectivamente. Cualquier paquete IP contiene un encabezado con una asignación estrictamente estandarizada de cada bit. Sin profundizar aún más en la estructura de los encabezados, necesitamos sacar una cosa de aquí: para operaciones con direcciones IP y rangos, generalmente es conveniente usar operaciones binarias de matemática y bit a bit. También es más conveniente almacenarlos en la base de datos como BINARY (4) para IPv4 y BINARY (16) para IPv6.

Otro aspecto importante que debe abordarse son las máscaras de red y la notación CIDR. CIDR es un acrónimo de enrutamiento entre dominios sin clase. Este concepto reemplazó a la clase uno para determinar qué parte de la dirección IP es el prefijo de red y qué parte es la dirección de interfaz de red dentro de esta red. En la práctica, los primeros n bits correspondientes al prefijo se establecerán en 1, el resto en 0.

En forma legible para humanos, esto se escribe como ip.add.re.ss. / cidr . Por ejemplo, 64.233.177.0/24 significa que los primeros 24 bits se refieren al prefijo. Los últimos 8 bits, son el último número en una entrada legible para humanos, se refieren a la dirección dentro de la subred. Un par de ejercicios más. 64.233.177.101/32 y 2607: f8b0: 4002: c08 :: 8b / 128 - una dirección específica. 2607: f8b0: 4002: c08 :: / 64 - los primeros 64 bits (los primeros 4 grupos) - el prefijo, los 64 bits restantes - la parte local. Por cierto, si alguien está avergonzado por el "::" en la entrada, un doble punto reemplaza un número arbitrario de secciones que contienen 0. Puede ocurrir en la anotación solo 1 vez. En otras palabras, 2607: f8b0: 4002: c08 :: 8b = 2607: f8b0: 4002: c08: 0: 0: 0: 8b .

¿Qué necesitamos aprender de todo esto? En primer lugar, la primera y la última dirección de subred se pueden obtener utilizando AND y OR binarios, conociendo la máscara en forma binaria. En segundo lugar, la siguiente subred de tamaño (es decir, con CIDR) n puede calcularse sumando 1 a la enésima posición en representación binaria. Por vista binaria, me refiero al resultado del uso de las funciones pack () e inet_pton () y el uso posterior de operadores bit a bit , por binario, una representación en el sistema binario, que se puede obtener, por ejemplo, usando base_convert () .

Antecedentes historicos
Segregación sin clase de direccionamiento precedido sin clase. En esos años distantes, nadie esperaba que hubiera tantas subredes; se distribuían a la derecha y a la izquierda en grandes bloques: clase A: los primeros 8 bits (es decir, el primer número) tenían el prefijo, con el bit inicial 0; clase B: los primeros 16 (primeros dos números), los bits iniciales de 10; clase C: los primeros 24 bits, los bits iniciales de 110. Estos mismos bits iniciales establecen los rangos en los que se emitió la dirección de una clase: 0.0.0.0 - 127.255.255.255 para la clase A, 128.0.0.0 - 191.255.255.255 - clase B, 192.0 .0.0 - 223.255.255.255 - clase C. A medida que Internet se extendió por todo el planeta, los reguladores se dieron cuenta de que habían fallado, y a principios de los años 90 desarrollaron un concepto sin clases, que les permitió no apegarse a los bits principales. Un poco más de detalles se pueden encontrar, por ejemplo, en el gran y omnisciente .


Pasemos a practicar


En la práctica, implementamos las tres tareas más probables, como me pareció a mí:

  1. obtener la primera y la última dirección del rango;
  2. obtener el siguiente rango de un tamaño dado (CIDR);
  3. comprobando que la dirección pertenece a un rango.

La implementación será para IPv6, pero si es necesario, la lógica se puede adaptar fácilmente. Obtuve algunas ideas de aquí , pero las implementé de manera un poco diferente. Además, en los ejemplos no hay verificación de errores de entrada. Entonces vamos.

Como ya mencioné, la primera y la última dirección de un rango se pueden determinar utilizando operaciones bit a bit, conociendo el comienzo del rango y la máscara de subred binaria. En consecuencia, lo primero que debemos hacer es convertir CIDR en una máscara binaria. Para hacer esto, recopile su representación hexadecimal y empaquétela en binario.

function cidrToMask ($cidr) { $mask = str_repeat('f', ceil($cidr / 4)); $mask .= dechex(4 * ($cidr % 4)); $mask = str_pad($mask, 32, '0'); return pack('H*', $mask); } 

Call pack ('H *', $ mask) empaqueta la representación hexadecimal de la misma manera que inet_pton () . La única diferencia es que cuando llama a pack (), todos los 0 deben estar en su lugar, y no debe haber dos puntos en la entrada, en contraste con la entrada legible por humanos.

El siguiente paso es calcular el comienzo y el final del rango. Y aquí hay matices. Las operaciones bit a bit están limitadas por la capacidad del procesador. En consecuencia, en mi CubieTruck de 32 bits, que a veces uso para cualquier prueba de mimo, los 128 bits de la dirección no se pueden procesar en una sola operación. Sin embargo, nada nos impide dividirlo en grupos de 32 bits (por si acaso, quién sabe qué procesadores ejecutaremos).

 function getRangeBoundary ($ip, $cidr, $which, $ipIsBin = false, $returnBin = false) { $mask = cidrToMask($cidr); if (!$ipIsBin) { $ip = inet_pton($ip); } $ipParts = str_split($ip, 4); $maskParts = str_split($mask, 4); $rangeParts = []; for ($i = 0; $i < count($ipParts); $i++) { if ($which == 'start') { /*  &       . */ $rangeParts[$i] = $ipParts[$i] & $maskParts[$i]; } else { /*  |    (~)           1. */ $rangeParts[$i] = $ipParts[$i] | ~$maskParts[$i]; } } $rangeBoundary = implode($rangeParts); if ($returnBin) { return $rangeBoundary; } else { return inet_ntop($rangeBoundary); } } 

Para uso futuro, proporcionaremos la capacidad de transmitir IP y obtener el resultado tanto en formato binario como legible para humanos. El parámetro $ which establece aquí si queremos obtener el principio o el final del rango (los valores son 'inicio' o 'final', respectivamente).

La siguiente tarea (además de la más práctica para nuestra empresa) es calcular el siguiente rango. Para esta tarea, no se me ocurrió nada mejor, excepto cómo descomponer la dirección en una cadena binaria y agregar 1 en la posición deseada, luego colapsar todo de nuevo. Para evitar que aparezcan artefactos en cualquier lugar, decidí dividir la dirección por bytes durante la descomposición y el ensamblaje.

 function getNextBlock ($ipStart, $cidr, $ipIsBin = false, $returnBin = false) { if (!$ipIsBin) { $ipStart = inet_pton($ipStart); } $ipParts = str_split($ipStart, 1); $ipBin = ''; foreach ($ipParts as $ipPart) { $ipBin .= str_pad(base_convert(unpack('H*', $ipPart)[1], 16, 2), 8, '0', STR_PAD_LEFT); } /*  1       "" :) */ $i = $cidr - 1; while ($i >= 0) { if ($ipBin[$i] == '0') { $ipBin[$i] = '1'; break; } else { $ipBin[$i] = '0'; } $i--; } $ipBinParts = str_split($ipBin, 8); foreach ($ipBinParts as $key => $ipBinPart) { $ipParts[$key] = pack('H*', str_pad(base_convert($ipBinPart, 2, 16), 2, '0', STR_PAD_LEFT)); } $nextIp = implode($ipParts); if ($returnBin) { return $nextIp; } else { return inet_ntop($nextIp); } } 

En la salida, obtenemos el prefijo del siguiente rango de tamaño especificado en $ cidr . Con esta función, asignamos bloques de direcciones a nuestros clientes.

Finalmente, verifique si la dirección pertenece al rango. Por ejemplo, asignamos un bloque / 48 para la distribución de bloques / 64 a los clientes, y debemos asegurarnos de que durante la cita no vayamos más allá del bloque asignado (en la práctica esto sucederá pronto, pero todavía hay una posibilidad). Todo es simple aquí. Obtenemos el comienzo y el final del rango en forma binaria y verificamos si la dirección está dentro.

 function ipInRange ($ip, $rangeStart, $cidr) { $start = getRangeBoundary($rangeStart, $cidr, 'start',false, true); $end = getRangeBoundary($rangeStart, $cidr, 'end',false, true); $ipBin = inet_pton($ip); return ($ipBin >= $start && $ipBin <= $end); } 

Espero que haya sido útil. ¿Qué otras funciones de direccionamiento pueden resultarle útiles? Cualquier adición, comentario y revisión de código son bienvenidos en los comentarios.

Si ya es nuestro cliente o está pensando en convertirse en uno, con ocasión del lanzamiento de este artículo, le sugerimos que reciba el bloque / 64 de forma gratuita para todos los servicios vps o un servidor dedicado en el centro de datos Equinix Tier IV, Países Bajos, previa solicitud al departamento de ventas, proporcionando un enlace a Este artículo en el boleto. La oferta es válida hasta marzo de 2020.

Un poco de publicidad :)


Gracias por quedarte con nosotros. ¿Te gustan nuestros artículos? ¿Quieres ver más materiales interesantes? Apóyenos haciendo un pedido o recomendando a sus amigos VPS basado en la nube para desarrolladores desde $ 4.99 , un análogo único de servidores de nivel básico que inventamos para usted: toda la verdad sobre VPS (KVM) E5-2697 v3 (6 núcleos) 10GB DDR4 480GB SSD 1Gbps desde $ 19 o cómo dividir el servidor? (las opciones están disponibles con RAID1 y RAID10, hasta 24 núcleos y hasta 40GB DDR4).

Dell R730xd 2 veces más barato en el centro de datos Equinix Tier IV en Amsterdam? ¡Solo tenemos 2 x Intel TetraDeca-Core Xeon 2x E5-2697v3 2.6GHz 14C 64GB DDR4 4x960GB SSD 1Gbps 100 TV desde $ 199 en los Países Bajos! Dell R420 - 2x E5-2430 2.2Ghz 6C 128GB DDR3 2x960GB SSD 1Gbps 100TB - ¡desde $ 99! Lea sobre Cómo construir un edificio de infraestructura. clase utilizando servidores Dell R730xd E5-2650 v4 que cuestan 9,000 euros por un centavo?

Source: https://habr.com/ru/post/484586/


All Articles