2017-08-13 22 views
0

Donc j'ai une adresse IP comme 45.76.255.14, et j'ai une table avec des lignes de CIDR stockées comme un seul varchar, comment je sélectionne CIDR qui sont dans la gamme de cette IP adresse. Par exemple 45.76.255.14/31Sélectionnez CIDR qui est dans la plage de IP

Donc, en théorie: sélectionnez CIDR où dans la gamme de la propriété intellectuelle

+0

Il ne fait pas vraiment de sens .. Ip est pas une plage. Un cidr peut être ... – Dekel

+0

@Dekel, Vous avez raison, une IP n'est pas une gamme, mais je demande d'aller chercher des CIDR qui sont dans la gamme de l'IP, aka comprend l'IP – RumbleFrog

+0

En théorie: 'select * de cidrlist où @ip entre IPDébut (CIDR) et endip (CIDR) '. Pratiquement, cela dépendra de la façon dont vous avez stocké vos données. Si par exemple vous stocker des gammes comme varchars ('45.76.255.14/31'), il sera plus difficile de faire la comparaison que si vous les stockez comme entiers pour le début et la fin d'une plage. Mais puisque vous ne nous avez pas donné votre modèle de données, la théorie devrait suffire. Un conseil pour le faire pratiquement: MySQL a une fonction pour convertir une chaîne ip (sans sous-réseau) en int: 'INET_ATON()'. Selon vos données, vous devrez peut-être écrire des fonctions comme 'startip()'/'endip()' pour convertir une plage. – Solarflare

Répondre

1

Le stockage d'adresses IP en notation quadrilatérale pointée dans un VARCHAR n'est pas la manière la plus optimale de les stocker, car dotted-quad est une représentation conviviale d'un entier non signé de 32 bits qui ne se prête pas à l'index de base de données ng Mais parfois, il est fondamentalement plus pratique, et à petite échelle, le fait que les requêtes nécessitent une analyse de table ne sont généralement pas un problème.

Les fonctions stockées MySQL sont un bon moyen d'encapsuler une logique relativement complexe derrière une simple fonction pouvant être référencée dans une requête, ce qui peut conduire à des requêtes plus faciles à comprendre et à réduire les erreurs de copier/coller.

Alors, voici une fonction stockée que j'ai écrit appelé find_ip4_in_cidr4(). Cela fonctionne un peu comme la fonction intégrée FIND_IN_SET() - vous lui donnez une valeur et vous lui donnez un "set" (spécification CIDR) et il retourne une valeur pour indiquer si la valeur est dans l'ensemble.

D'abord, une illustration de la fonction en action:

Si l'adresse est à l'intérieur du bloc, retourner la longueur du préfixe. Pourquoi retourner la longueur du préfixe? Les entiers non nuls sont "true", donc nous pouvons juste retourner 1, mais si vous voulez trier les résultats correspondants pour trouver le plus court ou le plus long des préfixes correspondants, vous pouvez ORDER BY la valeur de retour de la fonction.

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) 

Pas dans le bloc? Cela renvoie 0 (faux).

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) 

Il y a un cas particulier pour le tout-zéros adresse, nous reviendrons -1 (toujours "vrai", mais conserve l'ordre de tri):

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) 

arguments Nonsense retour 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) 

maintenant, Teh codez:

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 problème qui n'est pas spécifique aux fonctions stockées, mais s'applique plutôt à la plupart des fonctions sur la plupart des plateformes RDBMS, est que lorsqu'une colonne est utilisée comme argument d'une fonction dans WHERE, le serveur ne peut pas "regarder en arrière" utiliser un index pour optimiser la requête.

+0

Merci, je ne stocke pas les adresses IP, je stocke uniquement le CIDR et correspondant à l'entrée avec la base de données. Cela explique beaucoup. – RumbleFrog

1

Avec l'aide de cette question: MySQL query to convert CIDR into IP range

est ici la solution qui fonctionne pour moi:

SELECT 
    `cidr` 
FROM 
    cidr_list 
WHERE 
    INET_ATON('IP') BETWEEN(
     INET_ATON(SUBSTRING_INDEX(`cidr`, '/', 1)) & 0xffffffff ^(
      (
       0x1 <<(
        32 - SUBSTRING_INDEX(`cidr`, '/', -1) 
       ) 
      ) -1 
     ) 
    ) AND(
     INET_ATON(SUBSTRING_INDEX(`cidr`, '/', 1)) |(
      (
       0x100000000 >> SUBSTRING_INDEX(`cidr`, '/', -1) 
      ) -1 
     ) 
    ) 
+0

Vous n'avez pas vraiment besoin d'un test 'BETWEEN', car tout ce que vous devez vraiment évaluer, c'est si network & mask == address & mask. Le masque lui-même fournit le "entre" en masquant les bits insignifiants. –

+0

@ Michael-sqlbot, Que proposez-vous que j'utilise à la place? – RumbleFrog

+0

Rien à redire à ce que vous faites, je dis juste que vous n'avez pas à faire le calcul de fin de gamme, parce que c'est le point du masque de réseau. J'utilise une fonction stockée pour encapsuler la logique. –