sql >> Base de Datos >  >> RDS >> Mysql

Seleccione CIDR que esté en el rango de IP

Almacenamiento de direcciones IP en notación cuádruple con puntos en un VARCHAR no es la forma más óptima de almacenarlos, ya que dotted-quad es una representación humana de un entero sin signo de 32 bits que no se presta a la indexación de la base de datos. Pero a veces es fundamentalmente más conveniente y, a pequeña escala, el hecho de que las consultas requieran un escaneo de tabla no suele ser un problema.

Las funciones almacenadas de MySQL son una buena manera de encapsular una lógica relativamente compleja detrás de una función simple a la que se puede hacer referencia en una consulta, lo que puede conducir a consultas más fáciles de entender y reducir los errores de copiar/pegar.

Entonces, aquí hay una función almacenada que escribí llamada find_ip4_in_cidr4() . Funciona de manera similar a la función integrada FIND_IN_SET() -- le das un valor y le das un "conjunto" (especificación CIDR) y devuelve un valor para indicar si el valor está en el conjunto.

Primero, una ilustración de la función en acción:

Si la dirección está dentro del bloque, devuelve la longitud del prefijo. ¿Por qué devolver la longitud del prefijo? Los enteros distintos de cero son "verdaderos", por lo que podríamos devolver 1 , pero si desea ordenar los resultados coincidentes para encontrar el más corto o el más largo de varios prefijos coincidentes, puede ORDER BY el valor de retorno de la función.

mysql> SELECT find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('203.0.113.123','203.0.113.0/24') |
+-----------------------------------------------------+
|                                                  24 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/16') |
+-----------------------------------------------------+
|                                                  16 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

¿No estás en el bloque? Eso devuelve 0 (falso).

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','203.0.113.0/24') |
+-----------------------------------------------------+
|                                                   0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','192.168.0.0/24') |
+-----------------------------------------------------+
|                                                   0 |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Hay un caso especial para la dirección de todos los ceros, devolvemos -1 (sigue siendo "verdadero", pero conserva el orden de clasificación):

mysql> SELECT find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0');
+------------------------------------------------+
| find_ip4_in_cidr4('192.168.100.1','0.0.0.0/0') |
+------------------------------------------------+
|                                             -1 |
+------------------------------------------------+
1 row in set (0.00 sec)

Los argumentos sin sentido devuelven nulo:

mysql> SELECT find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24');
+-----------------------------------------------------+
| find_ip4_in_cidr4('234.467.891.0','192.168.0.0/24') |
+-----------------------------------------------------+
|                                                NULL |
+-----------------------------------------------------+
1 row in set (0.00 sec)

Ahora, el código:

DELIMITER $$

DROP FUNCTION IF EXISTS `find_ip4_in_cidr4` $$
CREATE DEFINER=`mezzell`@`%` FUNCTION `find_ip4_in_cidr4`(
  _address VARCHAR(15), 
  _block VARCHAR(18)
) RETURNS TINYINT
DETERMINISTIC /* for a given input, this function always returns the same output */
CONTAINS SQL /* the function does not read from or write to tables */
BEGIN

-- given an IPv4 address and a cidr spec,
-- return -1 for a valid address inside 0.0.0.0/0
-- return prefix length if the address is within the block,
-- return 0 if the address is outside the block,
-- otherwise return null

DECLARE _ip_aton INT UNSIGNED DEFAULT INET_ATON(_address);
DECLARE _cidr_aton INT UNSIGNED DEFAULT INET_ATON(SUBSTRING_INDEX(_block,'/',1));
DECLARE _prefix TINYINT UNSIGNED DEFAULT SUBSTRING_INDEX(_block,'/',-1);
DECLARE _bitmask INT UNSIGNED DEFAULT (0xFFFFFFFF << (32 - _prefix)) & 0xFFFFFFFF;

RETURN CASE /* the first match, not "best" match is used in a CASE expression */
  WHEN _ip_aton IS NULL OR _cidr_aton IS NULL OR /* sanity checks */
       _prefix  IS NULL OR _bitmask IS NULL OR
       _prefix NOT BETWEEN 0 AND 32 OR
       (_prefix = 0 AND _cidr_aton != 0) THEN NULL
  WHEN _cidr_aton = 0 AND _bitmask = 0 THEN -1
  WHEN _ip_aton & _bitmask = _cidr_aton & _bitmask THEN _prefix /* here's the only actual test needed */
  ELSE 0 END;

END $$
DELIMITER ;

Un problema que no es específico de las funciones almacenadas, sino que se aplica a la mayoría de las funciones en la mayoría de las plataformas RDBMS es que cuando una columna se usa como argumento para una función en WHERE , el servidor no puede "mirar hacia atrás" a través de la función para usar un índice para optimizar la consulta.