import ipaddress
import json
import sys
import urllib.request
from datetime import date
AWS = "https://ip-ranges.amazonaws.com/ip-ranges.json"
GCP = "https://www.gstatic.com/ipranges/cloud.json"
CF = "https://www.cloudflare.com/ips-v4"
PROVIDERS = ["Aws", "Gcp", "Cloudflare"]
def _get(url: str) -> bytes:
req = urllib.request.Request(url, headers={"User-Agent": "forensicnomicon-gen/1.0"})
with urllib.request.urlopen(req, timeout=60) as r:
return r.read()
def fetch_cidrs() -> list[tuple[int, int, int]]:
out: list[tuple[int, int, int]] = []
def add(cidr: str, pidx: int) -> None:
net = ipaddress.ip_network(cidr.strip(), strict=False)
if net.version != 4:
return
out.append((int(net.network_address), int(net.broadcast_address), pidx))
aws = json.loads(_get(AWS))
for p in aws.get("prefixes", []):
add(p["ip_prefix"], PROVIDERS.index("Aws"))
gcp = json.loads(_get(GCP))
for p in gcp.get("prefixes", []):
if "ipv4Prefix" in p:
add(p["ipv4Prefix"], PROVIDERS.index("Gcp"))
for line in _get(CF).decode().splitlines():
if line.strip():
add(line, PROVIDERS.index("Cloudflare"))
return out
def merge(intervals: list[tuple[int, int, int]]) -> list[tuple[int, int, int]]:
if not intervals:
return []
intervals.sort(key=lambda t: (t[0], t[2]))
merged: list[list[int]] = [list(intervals[0])]
for s, e, p in intervals[1:]:
cur = merged[-1]
if s <= cur[1] + 1: if e > cur[1]:
cur[1] = e
else:
merged.append([s, e, p])
return [(s, e, p) for s, e, p in merged]
def main() -> None:
merged = merge(fetch_cidrs())
today = date.today().isoformat()
lines = [
"// @generated by tools/gen_cloud_ranges.py from the published AWS / GCP /",
"// Cloudflare IPv4 feeds. DO NOT EDIT BY HAND. Re-run the tool to refresh.",
f"// Snapshot: {today}. Azure omitted (no stable open-data feed). See mod.rs.",
"use super::CloudProvider;",
"",
f"/// Disjoint, ascending IPv4 ranges (inclusive [start, end] as u32) tagged with",
f"/// their cloud provider. {len(merged)} merged intervals; snapshot {today}.",
"#[rustfmt::skip]",
"pub const CLOUD_RANGES: &[(u32, u32, CloudProvider)] = &[",
]
for s, e, p in merged:
lines.append(f" ({s}, {e}, CloudProvider::{PROVIDERS[p]}),")
lines.append("];")
sys.stdout.write("\n".join(lines) + "\n")
if __name__ == "__main__":
main()