Есть ли способ сопоставить IP с IP + CIDR прямо из запроса SELECT? - PullRequest
19 голосов
/ 27 февраля 2009

Что-то вроде

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

Таким образом, вы сначала не получаете все записи из БД, а затем сопоставляете их одну за другой.

Если c> 0, то были сопоставлены.

Стол BANS:

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

БД: MySQL 5

Тип IP и IPv (4 или 6) известен при запросе.

IP, например: 1 в двоичном формате

ЗАПРЕЩЕННЫЙ IP, например: 1/64

Ответы [ 5 ]

25 голосов
/ 27 февраля 2009

Помните, что IP-адреса - это не текстовый адрес, а числовой идентификатор. У меня похожая ситуация (мы выполняем поиск по гео-ip), и если вы храните все свои IP-адреса в виде целых чисел (например, мой IP-адрес 192.115.22.33, поэтому он хранится как 3228767777), то вы можете искать IP-адреса легко с помощью операторов правого сдвига.

Недостатком всех этих типов поиска является то, что вы не можете извлечь выгоду из индексов, и вы должны выполнять полное сканирование таблицы всякий раз, когда вы выполняете поиск. Приведенную выше схему можно улучшить, сохранив как сетевой IP-адрес сети CIDR (начало диапазона), так и широковещательный адрес (конец диапазона), поэтому, например, для хранения 192.168.1.0/24 можно сохранить два столбцы:

network     broadcast
3232235776, 3232236031 

И тогда вы можете соответствовать этому, вы просто делаете

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

Это позволит вам хранить сети CIDR в базе данных и быстро и эффективно сопоставлять их с IP-адресами, используя быстрые числовые индексы.

Заметка из обсуждения ниже :

MySQL 5.0 включает в себя оптимизацию ранжированных запросов, которая называется " index merge intersect ", что позволяет ускорить такие запросы (и избежать полного сканирования таблицы), если:

  • Существует многостолбцовый индекс, который точно соответствует столбцам в запросе по порядку. Итак, для приведенного выше примера запроса индекс должен быть (network, broadcast).
  • Все данные можно извлечь из индекса. Это верно для COUNT(*), но не верно для SELECT * ... LIMIT 1.

MySQL 5.6 включает в себя оптимизацию под названием MRR, которая также ускорит поиск всей строки, но это выходит за рамки этого ответа.

3 голосов
/ 30 марта 2016

IPv4-адреса, сетевые адреса и маски сети - это числа UINT32, представленные в удобочитаемой форме в виде «точечных квадратов». Код таблицы маршрутизации в ядре выполняет очень быстрое побитовое сравнение И при проверке, находится ли адрес в данном сетевом пространстве (маска сети / сети). Хитрость заключается в том, чтобы сохранить IP-адреса, сетевые адреса и сетевые маски в точках в виде таблиц как UINT32, а затем выполнить то же 32-разрядное побитовое И для вашего соответствия. например,

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 |
+----------------+
2 голосов
/ 27 февраля 2009

Для IPv4 вы можете использовать:

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
0 голосов
/ 14 мая 2019

Генерация диапазонов IP-адресов в виде целых чисел

Если ваша база данных не поддерживает причудливые побитовые операции, вы можете использовать упрощенный целочисленный подход.

В следующем примере используется 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;
0 голосов
/ 27 февраля 2009

Хммм. Вы можете создать таблицу масок cidr, присоединиться к ней, а затем сравнить ip anded (& в MySQL) с маской с ipaddress блока запрета. Будет ли это делать то, что вы хотите?

Если вы не хотите создавать таблицу масок, вы можете вычислить маску как -1 << (x-cidr) с x = 64 или 32 в зависимости.

...