Existe-t-il un moyen de faire correspondre IP avec IP+CIDR ​​directement à partir d'une requête SELECT ?

StackOverflow https://stackoverflow.com/questions/595748

Question

Quelque chose comme

SELECT COUNT(*) AS c FROM BANS WHERE typeid=6 AND (SELECT ipaddr,cidr FROM BANS) MATCH AGAINST 'this_ip';

Vous ne récupérez donc pas d’abord tous les enregistrements de la base de données, puis ne les faites pas correspondre un par un.

Si c > 0, alors ils correspondent.

Tableau des interdictions :

id int auto incr PK
typeid TINYINT (1=hostname, 4=ipv4, 6=ipv6)
ipaddr BINARY(128)
cidr INT
host VARCHAR(255)

BD :MySQL 5

Le type IP et IPv (4 ou 6) est connu lors de l’interrogation.

IP est par exemple ::1 au format binaire

L'IP INTERDITE est par exemple ::1/64

Était-ce utile?

La solution

N'oubliez pas que les adresses IP ne sont pas une adresse textuelle, mais un identifiant numérique.J'ai une situation similaire (nous effectuons des recherches géo-IP), et si vous stockez toutes vos adresses IP sous forme d'entiers (par exemple, mon adresse IP est 192.115.22.33, elle est donc stockée sous 3228767777), alors vous pouvez rechercher des adresses IP. facilement en utilisant des opérateurs de décalage à droite.

L'inconvénient de tous ces types de recherche est que vous ne pouvez pas bénéficier des index et que vous devez effectuer une analyse complète de la table à chaque fois que vous effectuez une recherche.Le schéma ci-dessus peut être amélioré en stockant à la fois l'adresse IP du réseau CIDR (le début de la plage) et l'adresse de diffusion (la fin de la plage), ainsi par exemple pour stocker 192.168.1.0/24, vous pouvez stocker deux Colonnes:

network     broadcast
3232235776, 3232236031 

Et puis vous pouvez le faire correspondre, vous le faites simplement

SELECT count(*) FROM bans WHERE 3232235876 >= network AND 3232235876 <= broadcast

Cela vous permettrait de stocker les réseaux CIDR dans la base de données et de les comparer aux adresses IP rapidement et efficacement en tirant parti d'index numériques rapides.

Note de la discussion ci-dessous:

MySQL 5.0 inclut une optimisation de requêtes à distance appelée "intersection de fusion d'index" qui permet d'accélérer de telles requêtes (et d'éviter des analyses de tables complètes), à condition que :

  • Il existe un index multi-colonnes qui correspond exactement aux colonnes de la requête, dans l'ordre.Donc, pour l'exemple de requête ci-dessus, l'index devrait être (network, broadcast).
  • Toutes les données peuvent être récupérées à partir de l'index.Ceci est vrai pour COUNT(*), mais ce n'est pas vrai pour SELECT * ... LIMIT 1.

MySQL 5.6 inclut une optimisation appelée MRR qui accélérerait également la récupération complète des lignes, mais cela sort du cadre de cette réponse.

Autres conseils

adresses IPv4, les adresses réseau et sont tous netmask UINT32 numéros et sont présentés sous forme lisible par l'homme comme « pointillées-quadriceps ». Le code de table de routage dans le noyau effectue un peu sage et comparaison très rapide lors de la vérification si une adresse est dans un espace de réseau donné (réseau / masque de réseau). L'astuce est de stocker ici les adresses IP quadruplet, les adresses réseau et dans vos tables netmask comme UINT32, puis effectuer le même sage bits 32 bits et pour votre correspondant. par exemple,

SET @test_addr = inet_aton('1.2.3.4');
SET @network_one = inet_aton('1.2.3.0');
SET @network_two = inet_aton('4.5.6.0');
SET @network_netmask = inet_aton('255.255.255.0');

SELECT (@test_addr & @network_netmask) = @network_one AS IS_MATCHED;
+------------+
| IS_MATCHED |
+------------+
|          1 |
+------------+

SELECT (@test_addr & @network_netmask) = @network_two AS IS_NOT_MATCHED;
+----------------+
| IS_NOT_MATCHED |
+----------------+
|              0 |
+----------------+

Pour IPv4, vous pouvez utiliser:

SET @length = 4;

SELECT  INET_NTOA(ipaddr), INET_NTOA(searchaddr), INET_NTOA(mask)
FROM  (
  SELECT
        (1 << (@length * 8)) - 1 & ~((1 << (@length * 8 - cidr)) - 1) AS mask,
        CAST(CONV(SUBSTR(HEX(ipaddr), 1, @length * 2), 16, 10) AS DECIMAL(20)) AS ipaddr,
        CAST(CONV(SUBSTR(HEX(@myaddr), 1, @length * 2), 16, 10) AS DECIMAL(20)) AS searchaddr
  FROM  ip
) ipo
WHERE ipaddr & mask = searchaddr & mask

Hmmm. Vous pouvez construire une table des masques CIDR, joignez-vous, puis comparer l'ip anded (& MySQL) avec le masque avec le bloc d'interdiction IPADDRESS. Est-ce que faire ce que vous voulez?

Si vous ne voulez pas construire une table de masque, vous pourriez calculer le masque -1 << (x-cidr) avec fonction x = 64 ou 32.

Génération plages d'adresses IP comme Entiers

  

Si votre base de données ne prend pas en charge les opérations de fantaisie au niveau du bit, vous pouvez utiliser une approche basée entier simplifiée.

L'exemple suivant utilise PostgreSQL:

select (cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 1) as bigint) * (256 * 256 * 256) +
        cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 2) as bigint) * (256 * 256      ) +
        cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 3) as bigint) * (256            ) +
        cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 4) as bigint)) 
        as network,

       (cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 1) as bigint) * (256 * 256 * 256) +
        cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 2) as bigint) * (256 * 256      ) +
        cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 3) as bigint) * (256            ) +
        cast(split_part(split_part('4.0.0.0/8', '/', 1), '.', 4) as bigint)) + cast(
          pow(256, (32 - cast(split_part('4.0.0.0/8', '/', 2) as bigint)) / 8) - 1 as bigint
        ) as broadcast;
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top