SELECT クエリから直接 IP を IP+CIDR と照合する方法はありますか?
質問
何かのようなもの
SELECT COUNT(*) AS c FROM BANS WHERE typeid=6 AND (SELECT ipaddr,cidr FROM BANS) MATCH AGAINST 'this_ip';
したがって、最初に DB からすべてのレコードを取得してから、それらを 1 つずつ照合する必要はありません。
c > 0 の場合は一致しました。
BANSテーブル:
id int auto incr PK
typeid TINYINT (1=hostname, 4=ipv4, 6=ipv6)
ipaddr BINARY(128)
cidr INT
host VARCHAR(255)
DB:MySQL 5
IP および IPv タイプ (4 または 6) は、クエリ時に判明します。
たとえば、IP はバイナリ形式の ::1 です。
禁止された IP は、たとえば ::1/64 です。
解決
IP はテキストのアドレスではなく、数値 ID であることに注意してください。私も同様の状況(地域 IP ルックアップを行っています)があり、すべての IP アドレスを整数として保存すると(たとえば、私の IP アドレスは 192.115.22.33 なので、3228767777 として保存されます)、IP をルックアップできます。右シフト演算子を使用することで簡単に実行できます。
これらすべてのタイプのルックアップの欠点は、インデックスのメリットが得られず、ルックアップを実行するたびにテーブル全体のスキャンを実行する必要があることです。上記のスキームは、CIDR ネットワークのネットワーク IP アドレス (範囲の先頭) とブロードキャスト アドレス (範囲の末尾) の両方を保存することで改善できます。たとえば、192.168.1.0/24 を保存するには、2 つのアドレスを保存できます。列:
network broadcast
3232235776, 3232236031
そして、それを単純に一致させることができます
SELECT count(*) FROM bans WHERE 3232235876 >= network AND 3232235876 <= broadcast
これにより、CIDR ネットワークをデータベースに保存し、迅速な数値インデックスを利用して IP アドレスと迅速かつ効率的に照合できるようになります。
以下の議論のメモ:
MySQL 5.0 には、「」と呼ばれる範囲クエリの最適化が含まれています。インデックスマージ交差これにより、次の条件を満たす限り、そのようなクエリを高速化できます (そしてテーブル全体のスキャンを回避できます)。
- クエリ内の列を順番に正確に一致させる複数列インデックスがあります。したがって、上記のクエリの例では、インデックスは次のようにする必要があります。
(network, broadcast)
. - すべてのデータはインデックスから取得できます。これは次の場合に当てはまります
COUNT(*)
, 、しかし、それは当てはまりませんSELECT * ... LIMIT 1
.
MySQL 5.6 には、完全な行の取得を高速化する MRR と呼ばれる最適化が含まれていますが、それはこの回答の範囲外です。
他のヒント
IPv4アドレス、ネットワークアドレスとネットマスクは、すべてのUINT32の数値であり、「ドット付きクワッド」として、人間が読める形式で提示されています。アドレスは、所与のネットワーク空間(ネットワーク/ネットマスク)にあるかどうかをチェックするときに、カーネル内のルーティングテーブルのコードは、非常に高速なビット単位の比較を行います。ここでのトリックはUINT32としてあなたのテーブルにドット付きクワッドのIPアドレス、ネットワークアドレスとネットマスクを保存し、同じ32ビットのビット単位のANDあなたのマッチングのために実行することです。例えば、
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 |
+----------------+
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
うーん。あなたは、CIDRマスクのテーブルを構築し、それに参加した後、禁止ブロックのIPアドレスとマスクでAND IP(MySQLで&
)を比較することができます。それはあなたがやりたいだろうか?
、あなたは-1 << (x-cidr)
またはx = 64
依存と32
としてマスクを計算することができます。
の生成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;