🕸️

PostgreSQL で IP アドレスをスマートに扱う cidr と inet 型の使い方と活用例

に公開

はじめに

PostgreSQL には、ネットワークアドレスや単一の IP アドレスを格納するための cidr 型と inet 型があります。これらは 標準 SQL には存在しない PostgreSQL 独自のデータ型 です。

そのため、PostgreSQL 経験が浅い開発者や、IP アドレス管理をあまり行わないプロジェクトでは存在を知らないこともあります。また、PostgreSQL を使っていても varchar 型で IP アドレスを保存しているケースも見られます。

この記事では、cidrinet の違い・使い方・演算子などをまとめます。

cidr

cidr 型は IPv4 または IPv6 ネットワークアドレスを保持します。格納時にネットワーク部以外のビットは自動的にゼロ化され、必ずネットワーク境界に揃えられます。単一の IP アドレスを格納した場合でも、IPv4 は /32、IPv6 は /128 のネットワークとして扱われます。

基本

-- テーブル作成
CREATE TABLE networks (address cidr);

-- IPv4ネットワーク
INSERT INTO networks VALUES ('192.168.0.0/24');

-- IPv4単一IP(/32 扱いになる)
INSERT INTO networks VALUES ('192.168.0.1');

-- IPv6ネットワーク
INSERT INTO networks VALUES ('2001:db8::/48');

-- IPv6単一IP(/128 扱いになる)
INSERT INTO networks VALUES ('2001:db8::1');

-- データ取得
SELECT * FROM networks;

address        |
---------------+
192.168.0.0/24 |
192.168.0.1/32 |
2001:db8::/48  |
2001:db8::1/128|

バリデーション

  • 無効な IP アドレスは登録できません
  • サブネット指定時はホスト部がゼロでないと登録不可
INSERT INTO networks VALUES ('192.168.0.999');     -- NG: 無効なIPv4
INSERT INTO networks VALUES ('2001:db8::zzzz');    -- NG: 無効なIPv6
INSERT INTO networks VALUES ('192.168.0.1/24');    -- NG: ホスト部がゼロでない
INSERT INTO networks VALUES ('2001:db8::1234/48'); -- NG: ホスト部がゼロでない

inet

inet 型は IPv4 または IPv6 の単一 IP アドレス、または IP アドレス+プレフィックス長を保持できます。cidr 型と異なり、サブネット指定時にホスト部がゼロでなくても格納可能です。

基本

-- テーブル作成
CREATE TABLE hosts (address inet);

-- IPv4単一IP
INSERT INTO hosts VALUES ('192.168.0.1');

-- IPv4アドレス + サブネット
INSERT INTO hosts VALUES ('192.168.0.123/24');

-- IPv6単一IP
INSERT INTO hosts VALUES ('2001:db8::1');

-- IPv6アドレス + サブネット
INSERT INTO hosts VALUES ('2001:db8::abcd/64');

-- データ取得
SELECT * FROM hosts;

address          |
-----------------+
192.168.0.1      |
192.168.0.123/24 |
2001:db8::1      |
2001:db8::abcd/64|

バリデーション

  • 無効な IP アドレスは登録できません
  • サブネット境界に揃っていなくても格納可能(cidr 型と異なる点)
INSERT INTO hosts VALUES ('192.168.0.999');     -- NG: 無効なIPv4
INSERT INTO hosts VALUES ('2001:db8::zzzz');    -- NG: 無効なIPv6
INSERT INTO hosts VALUES ('192.168.0.1/24');    -- OK
INSERT INTO hosts VALUES ('2001:db8::1234/48'); -- OK

cidrinet の比較

項目 cidr inet
プレフィックス長なし 自動的に /32 or /128 単一 IP として扱う
ホスト部がゼロ 必須 不要
用途 ネットワーク管理・集計 IP アドレス管理全般

演算子

cidr 型・inet 型は、ネットワーク範囲や包含関係を比較できる演算子があります。

演算子 説明 用途
<< / >> 左のネットワークが右に含まれる(>> は逆) IP アドレスがサブネット内か判定
<<=/ =>> 左のネットワークが右に含まれる、または等しい(=>> は逆) 自ネットワークを含めた包含判定
&& 2つのネットワークが重なっている サブネット間のオーバーラップ検出
~ ビット反転 サブネットマスクの反転など
+ IP アドレスに整数を加算 次の IP に移動
- IP アドレスから整数を減算 前の IP に移動
- 2 つの IP アドレスの差を整数で返す IP 間のホスト数計算
SELECT '192.168.1.5'::inet << '192.168.1.0/24'::inet;
-- t

SELECT '192.168.1.0/24'::inet <<= '192.168.1.0/24'::inet;
-- t

SELECT '192.168.0.0/16'::inet >> '192.168.1.5'::inet;
-- t

SELECT '192.168.1.0/24'::inet >>= '192.168.1.0/24'::inet;
-- t

SELECT '192.168.1.0/24'::inet && '192.168.1.128/25'::inet;
-- t

SELECT ~ '255.255.255.0'::inet;
-- 0.0.0.255

SELECT '192.168.0.1'::inet + 5;
-- 192.168.0.6

SELECT '192.168.0.10'::inet - 3;
-- 192.168.0.7

SELECT '192.168.0.10'::inet - '192.168.0.1'::inet;
-- 9

実用例・ユースケース

cidrinet 型は、ネットワーク関連のデータを扱う多様な場面で役立ちます。

  • アクセス制御
    • IP アドレスが特定のネットワークに含まれるか判定する
  • ログ分析
    • ログに記録された IP アドレスをグループ化し、サブネット単位で集計や異常検知を行う
  • バリデーション・整合性確保
    • varchar と比べて、cidr / inet は表現揺れや誤入力が発生しづらく、正確な形式チェックが自動で行われる

アクセス許可リストサンプル

IP アドレスのアクセス制御を行う際に、許可されたネットワークのリスト(ホワイトリスト)を管理し、クライアントの IP アドレスが許可リストに含まれているかを判定する例です。

-- 許可リストテーブル
CREATE TABLE allowed_ips (network cidr);

-- 許可ネットワーク登録
INSERT INTO allowed_ips VALUES
   ('192.168.0.0/24')
  ,('10.0.0.0/8')
  ,('2001:db8::/48');
-- IPアドレスチェック
WITH check_address AS (
  SELECT '192.168.0.123'::inet AS ip
)
SELECT
   ip
  ,EXISTS (
    SELECT 1 FROM allowed_ips WHERE ip << network
  ) AS allowed
FROM
  check_address;

ip           |allowed|
-------------+-------+
192.168.0.123|true   |

便利な関数

IP アドレスやネットワークを操作・解析するための便利な関数がいくつも用意されています。代表的な関数を紹介します。

関数 説明
host() IP アドレス部分のみを取得(プレフィックスなし)
masklen() プレフィックス長(ネットマスクの長さ)を取得
set_masklen() プレフィックス長を指定値に設定
inet_same_family() 2つのIPが同じファミリーか判定(IPv4 か IPv6 か)
broadcast() 指定ネットワークのブロードキャストアドレスを返す(IPv4 のみ)
network() ネットワークアドレスを取得
SELECT host('192.168.1.5/24'::inet);
-- 192.168.1.5

SELECT masklen('192.168.1.5/24'::inet);
-- 24

SELECT set_masklen('192.168.1.5/24'::inet, 16);
-- 192.168.1.5/16

SELECT inet_same_family('192.168.0.1'::inet, '10.0.0.1'::inet);
-- t

SELECT broadcast('192.168.1.0/24'::cidr);
-- 192.168.1.255

SELECT network('192.168.1.5/24'::inet);
-- 192.168.1.0/24

Discussion