
AWS IPアドレス範囲ファイルをDuckDBで読み込んで、いろいろ見てみる
AWSサービスが使用するIPアドレスの範囲はJSON形式(ip-ranges.json)で公開されています。
- オンプレミスのファイアウォール設定で「特定サービスへのアウトバウンド通信」に絞りたい
- ログにあるIPアドレスがどのAWSサービス/リージョンのものか把握したい
といったユースケースで良く使われます。
今回は ip-ranges.json を DuckDB に読み込ませて、いろいろ見てみます。 DuckDBはオンライン分析処理(OLAP)に特化したデータベースシステムです。 CSVやParquetだけでなく、JSONも読み込むことができます。 拡張機能も豊富なため多いため、とても便利です。
DuckDBに取り入れる
DuckDBを起動します。
duckdb ip-ranges.db
# v1.2.2 7c039464e4
# Enter ".help" for usage hints.
# D
以下コマンドで ip-ranges.json を取得できます。
---- デフォルトで自動ロードされるので基本的には実行不要
-- INSTALL httpfs;
-- LOAD httpfs;
SELECT * FROM read_json('https://ip-ranges.amazonaws.com/ip-ranges.json');
-- ┌────────────┬─────────────────────┬──────────────────────┬────────────────────────────────────────────────────────┐
-- │ syncToken │ createDate │ prefixes │ ipv6_prefixes │
-- │ varchar │ varchar │ struct(ip_prefix v… │ struct(ipv6_prefix varchar, region varchar, service … │
-- ├────────────┼─────────────────────┼──────────────────────┼────────────────────────────────────────────────────────┤
-- │ 1744415597 │ 2025-04-11-23-53-17 │ [{'ip_prefix': 3.4… │ [{'ipv6_prefix': 2600:1f69:7400::/40, 'region': mx-c… │
-- └────────────┴─────────────────────┴──────────────────────┴────────────────────────────────────────────────────────┘
今回は prefixes にある IPv4 アドレス範囲情報を見ていきます。 ipv4 テーブルとして保存しておきましょう。
CREATE TABLE ipv4 AS
SELECT unnest(prefixes, recursive := true) FROM read_json('https://ip-ranges.amazonaws.com/ip-ranges.json');
SELECT * FROM ipv4 LIMIT 5;
-- ┌─────────────────┬────────────────┬─────────┬──────────────────────┐
-- │ ip_prefix │ region │ service │ network_border_group │
-- │ varchar │ varchar │ varchar │ varchar │
-- ├─────────────────┼────────────────┼─────────┼──────────────────────┤
-- │ 3.4.12.4/32 │ eu-west-1 │ AMAZON │ eu-west-1 │
-- │ 3.5.140.0/22 │ ap-northeast-2 │ AMAZON │ ap-northeast-2 │
-- │ 15.190.244.0/22 │ ap-east-2 │ AMAZON │ ap-east-2 │
-- │ 15.230.15.29/32 │ eu-central-1 │ AMAZON │ eu-central-1 │
-- │ 15.230.15.76/31 │ eu-central-1 │ AMAZON │ eu-central-1 │
-- └─────────────────┴────────────────┴─────────┴──────────────────────┘
unnest 関数の挙動は以下を見るとイメージできると思います。
SELECT [{'a': 42, 'b': 84}, {'a': 100, 'b': NULL}] as XXX;
-- ┌─────────────────────────────────────────────┐
-- │ XXX │
-- │ struct(a integer, b integer)[] │
-- ├─────────────────────────────────────────────┤
-- │ [{'a': 42, 'b': 84}, {'a': 100, 'b': NULL}] │
-- └─────────────────────────────────────────────┘
SELECT unnest([{'a': 42, 'b': 84}, {'a': 100, 'b': NULL}]) as XXX;
-- ┌──────────────────────────────┐
-- │ XXX │
-- │ struct(a integer, b integer) │
-- ├──────────────────────────────┤
-- │ {'a': 42, 'b': 84} │
-- │ {'a': 100, 'b': NULL} │
-- └──────────────────────────────┘
SELECT unnest([{'a': 42, 'b': 84}, {'a': 100, 'b': NULL}], recursive := true);
-- ┌───────┬───────┐
-- │ a │ b │
-- │ int32 │ int32 │
-- ├───────┼───────┤
-- │ 42 │ 84 │
-- │ 100 │ NULL │
-- └───────┴───────┘
統計いろいろ
サービス名の一覧
サービス名一覧(エントリ数TOP10)を出力します。
SELECT
service,
COUNT(*) AS count
FROM ipv4
GROUP BY service
ORDER BY count DESC
LIMIT 10;
-- ┌───────────────────────┬───────┐
-- │ service │ count │
-- │ varchar │ int64 │
-- ├───────────────────────┼───────┤
-- │ AMAZON │ 4995 │
-- │ EC2 │ 1312 │
-- │ ROUTE53_RESOLVER │ 638 │
-- │ S3 │ 325 │
-- │ API_GATEWAY │ 207 │
-- │ CLOUDFRONT │ 186 │
-- │ GLOBALACCELERATOR │ 108 │
-- │ EBS │ 99 │
-- │ KINESIS_VIDEO_STREAMS │ 91 │
-- │ DYNAMODB │ 87 │
-- ├───────────────────────┴───────┤
-- │ 10 rows 2 columns │
-- └───────────────────────────────┘
AMAZON は「すべての」IPアドレス範囲を含むものです。 他のIPアドレス範囲(EC2,S3など)もここに含まれます。 つまり、個別サービス単位だと EC2 のエントリ数が 最も多いことになりますね。
リージョン名の一覧
リージョン名(とそのエントリ数)一覧を出力します。 日本は 8位でした。
SELECT
region,
COUNT(*) AS count
FROM ipv4
GROUP BY region,
ORDER BY count DESC
LIMIT 20;
-- ┌────────────────┬───────┐
-- │ region │ count │
-- │ varchar │ int64 │
-- ├────────────────┼───────┤
-- │ us-east-1 │ 1169 │
-- │ us-west-2 │ 536 │
-- │ us-west-1 │ 514 │
-- │ us-east-2 │ 501 │
-- │ eu-central-1 │ 497 │
-- │ eu-west-1 │ 449 │
-- │ GLOBAL │ 400 │
-- │ ap-northeast-1 │ 348 │
-- │ eu-west-2 │ 345 │
-- │ ap-southeast-1 │ 344 │
-- ├────────────────┴───────┤
-- │ 10 rows 2 columns │
-- └────────────────────────┘
使われているサブネットマスクの分布
使われているサブネットマスク(CIDR)の分布を可視化してみました。
SELECT
split_part(ip_prefix, '/', 2) as subnet_mask,
count(*) as count,
bar(count(*), 0, 2100, 30)
FROM ipv4
GROUP BY subnet_mask
ORDER BY subnet_mask DESC;
-- ┌─────────────┬───────┬────────────────────────────────┐
-- │ subnet_mask │ count │ bar(count_star(), 0, 2100, 30) │
-- │ varchar │ int64 │ varchar │
-- ├─────────────┼───────┼────────────────────────────────┤
-- │ 32 │ 1139 │ ████████████████▎ │
-- │ 31 │ 490 │ ███████ │
-- │ 30 │ 179 │ ██▌ │
-- │ 29 │ 345 │ ████▉ │
-- │ 28 │ 351 │ █████ │
-- │ 27 │ 301 │ ████▎ │
-- │ 26 │ 509 │ ███████▎ │
-- │ 25 │ 220 │ ███▏ │
-- │ 24 │ 2033 │ █████████████████████████████ │
-- │ 23 │ 551 │ ███████▊ │
-- │ 22 │ 588 │ ████████▍ │
-- │ 21 │ 303 │ ████▎ │
-- │ 20 │ 203 │ ██▉ │
-- │ 19 │ 78 │ █ │
-- │ 18 │ 148 │ ██ │
-- │ 17 │ 101 │ █▍ │
-- │ 16 │ 528 │ ███████▌ │
-- │ 15 │ 316 │ ████▌ │
-- │ 14 │ 113 │ █▌ │
-- │ 13 │ 42 │ ▌ │
-- │ 12 │ 19 │ ▎ │
-- │ 11 │ 4 │ │
-- ├─────────────┴───────┴────────────────────────────────┤
-- │ 22 rows 3 columns │
-- └──────────────────────────────────────────────────────┘
一番大きな /11
は 4エントリ(2サブネット)ありました。
SELECT * FROM ipv4
WHERE split_part(ip_prefix, '/', 2) = '11';
-- ┌───────────────┬───────────┬─────────┬──────────────────────┐
-- │ ip_prefix │ region │ service │ network_border_group │
-- │ varchar │ varchar │ varchar │ varchar │
-- ├───────────────┼───────────┼─────────┼──────────────────────┤
-- │ 44.192.0.0/11 │ us-east-1 │ AMAZON │ us-east-1 │
-- │ 44.224.0.0/11 │ us-west-2 │ AMAZON │ us-west-2 │
-- │ 44.192.0.0/11 │ us-east-1 │ EC2 │ us-east-1 │
-- │ 44.224.0.0/11 │ us-west-2 │ EC2 │ us-west-2 │
-- └───────────────┴───────────┴─────────┴──────────────────────┘
ユースケース: 特定リージョン/サービスのIPアドレス範囲を洗い出す
例として東京リージョンの API Gateway のIPアドレス範囲一覧を洗い出します。
SELECT * FROM ipv4
WHERE
region = 'ap-northeast-1' AND
service = 'API_GATEWAY';
-- ┌──────────────────┬────────────────┬─────────────┬──────────────────────┐
-- │ ip_prefix │ region │ service │ network_border_group │
-- │ varchar │ varchar │ varchar │ varchar │
-- ├──────────────────┼────────────────┼─────────────┼──────────────────────┤
-- │ 18.180.88.0/23 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- │ 3.112.162.0/23 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- │ 3.112.96.160/27 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- │ 35.73.115.128/25 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- │ 35.75.130.0/24 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- │ 35.75.131.0/26 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- │ 35.77.112.0/22 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- │ 35.77.124.0/23 │ ap-northeast-1 │ API_GATEWAY │ ap-northeast-1 │
-- └──────────────────┴────────────────┴─────────────┴──────────────────────┘
常に最新のものを持ってきたい場合は、 以下のようなシェルスクリプトを実行すると良いでしょう。
aws_region='ap-northeast-1'
aws_service='API_GATEWAY'
duckdb_sql=$(cat <<EOF
WITH ipv4 AS (
SELECT unnest(prefixes, recursive := true)
FROM read_json('https://ip-ranges.amazonaws.com/ip-ranges.json')
)
SELECT ip_prefix FROM ipv4
WHERE
region = '${aws_region}' AND
service = '${aws_service}';
EOF
)
duckdb -list -c "${duckdb_sql}"
# ip_prefix
# 18.180.88.0/23
# 3.112.162.0/23
# 3.112.96.160/27
# 35.73.115.128/25
# 35.75.130.0/24
# 35.75.131.0/26
# 35.77.112.0/22
# 35.77.124.0/23
ユースケース: 特定IPアドレスの所属確認
inet 拡張 を使うことで IPv4、IPv6 アドレスに関する値や関数を使えるようになります。
例えば以下SQLでは ip_prefix に対してホスト部分とネットマスク部分を表示しています。
SELECT
ip_prefix,
host(ip_prefix::INET) AS host,
netmask(ip_prefix::INET) AS netmask,
FROM ipv4
LIMIT 5;
-- ┌─────────────────┬──────────────┬────────────────────┐
-- │ ip_prefix │ host │ netmask │
-- │ varchar │ varchar │ inet │
-- ├─────────────────┼──────────────┼────────────────────┤
-- │ 3.4.12.4/32 │ 3.4.12.4 │ 255.255.255.255 │
-- │ 3.5.140.0/22 │ 3.5.140.0 │ 255.255.252.0/22 │
-- │ 15.190.244.0/22 │ 15.190.244.0 │ 255.255.252.0/22 │
-- │ 15.230.15.29/32 │ 15.230.15.29 │ 255.255.255.255 │
-- │ 15.230.15.76/31 │ 15.230.15.76 │ 255.255.255.254/31 │
-- └─────────────────┴──────────────┴────────────────────┘
特定IPアドレスのエントリを調べてみましょう。
SELECT * FROM ipv4
WHERE
'52.94.10.1'::INET <<= ip_prefix::INET;
-- ┌───────────────┬───────────┬──────────┬──────────────────────┐
-- │ ip_prefix │ region │ service │ network_border_group │
-- │ varchar │ varchar │ varchar │ varchar │
-- ├───────────────┼───────────┼──────────┼──────────────────────┤
-- │ 52.94.10.0/24 │ us-west-2 │ AMAZON │ us-west-2 │
-- │ 52.94.10.0/24 │ us-west-2 │ DYNAMODB │ us-west-2 │
-- └───────────────┴───────────┴──────────┴──────────────────────┘
おわりに
AWS IPアドレス範囲ファイルをDuckDBで読み込んで、見てみました。 JSONも扱えるのは便利です。 便利な関数も多く、出力形式も多数サポートされているので、 いろいろなユースケースで活用できると思います。
以上、参考になれば幸いです。