Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Selecione CIDR que está no intervalo de IP


Armazenando endereços IP em notação quádrupla em um VARCHAR não é a maneira mais ideal de armazená-los, já que dotted-quad é uma representação humana amigável de um inteiro sem sinal de 32 bits que não se presta à indexação de banco de dados. Mas às vezes é fundamentalmente mais conveniente e, em pequena escala, o fato de as consultas exigirem uma verificação de tabela geralmente não é um problema.

As funções armazenadas do MySQL são uma boa maneira de encapsular uma lógica relativamente complexa por trás de uma função simples que pode ser referenciada em uma consulta, potencialmente levando a consultas mais fáceis de entender e reduzindo erros de copiar/colar.

Então, aqui está uma função armazenada que escrevi chamada find_ip4_in_cidr4() . Ele funciona de maneira semelhante à função interna FIND_IN_SET() -- você dá um valor e dá um "conjunto" (especificação CIDR) e ele retorna um valor para indicar se o valor está no conjunto.

Primeiro, uma ilustração da função em ação:

Se o endereço estiver dentro do bloco, retorne o comprimento do prefixo. Por que retornar o comprimento do prefixo? Inteiros diferentes de zero são "true", então podemos retornar 1 , mas se você quiser classificar os resultados correspondentes para encontrar o menor ou o maior de vários prefixos correspondentes, você pode ORDER BY o valor de retorno da função.
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)

Não está no bloco? Isso retorna 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)

Há um caso especial para o endereço de zeros, retornamos -1 (ainda "true", mas preserva a ordem de classificação):
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)

Argumentos sem sentido retornam null:
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)

Agora, o 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 ;

Um problema que não é específico para funções armazenadas, mas que se aplica à maioria das funções na maioria das plataformas RDBMS é que quando uma coluna é usada como argumento para uma função em WHERE , o servidor não pode "olhar para trás" por meio da função para usar um índice para otimizar a consulta.