IP-Range-Listen generieren
Nehmen wir an, Du hast irgendeinen openwrt Router und möchtest IP-Sets verwenden, um ganze Adress-Ranges in Deiner Firewall zu blockieren. Dann ist die dringendste Frage erstmal: wo bekomme ich zB die Adressen her, die Google verwendet, oder Facebook?
IP-Adressen (IPv4 und IPv6) werden von zentralen Registrierungsstellen vergeben. Das Gute ist, dass man diese abrufen kann. Gehen wir das eben schnell durch.
Es gibt folgende Registrierungsstellen, die im Internet die IP Adressen vergeben und dauerhaft zuordnen. Das sind die AfriNIC, ARIN, APNIC, LACNIC, RIPE NCC.

Zum Beispiel RIPE bietet eine Schnittstelle an, die wir mit Scripten anvisieren können, um uns zB die Infos, die wir benötigen, automatisiert zu holen. Wir wollen im ersten Schritt die ASN Nummern von Unternehmen haben, die wir gerne komplett blocken wollen. Das sind die Nummern, unter denen die Firmen registriert sind und denen ganze IP-Adress-Ranges zugewiesen werden.
ASN-lookup bash-script
#!/usr/bin/env bash
# asn_lookup.sh — Find all ASNs registered to one or more company/org names.
#
# Sources (all tried, results merged & deduplicated):
# 1. bgp.tools CSV — bgp.tools/asns.csv (global, all RIRs, grepped locally)
# 2. ARIN Whois-RWS — whois.arin.net REST API (ARIN region: US/CA/...)
# 3. RIPE NCC RDAP — rdap.db.ripe.net (Europe / Middle East / Central Asia)
# 4. Hurricane Electric— bgp.he.net (HTML scrape) (global cross-reference)
#
# No API keys required. Dependencies: curl, jq
#
# Usage:
# ./asn_lookup.sh -c "Cloudflare"
# ./asn_lookup.sh -c "Alphabet" "Meta" -o companies --format txt
# ./asn_lookup.sh -f companies.txt -o results --format csv
# ./asn_lookup.sh -c "Amazon" --show-source -o amazon
set -euo pipefail
# ── Defaults ──────────────────────────────────────────────────────────────────
COMPANIES=()
COMPANY_FILE=""
OUTPUT=""
FORMAT="txt"
QUIET=false
NO_COLOR=false
SHOW_SOURCE=false
NO_DEDUP=false
TIMEOUT=10 # per-request curl timeout in seconds
UA="Mozilla/5.0 asn-lookup-sh/2.0"
CACHE_DIR="/tmp/.asn_lookup_$$"
BGP_TOOLS_CSV="$CACHE_DIR/asns.csv"
RESULTS_FILE="$CACHE_DIR/results.txt"
mkdir -p "$CACHE_DIR"
trap 'rm -rf "$CACHE_DIR"' EXIT
# ── Colours ───────────────────────────────────────────────────────────────────
setup_colors() {
if [[ "$NO_COLOR" == true ]] || [[ ! -t 2 ]]; then
BOLD=""; RESET=""; GREEN=""; YELLOW=""; RED=""; CYAN=""; DIM=""; BLUE=""
else
BOLD=$'\033[1m'; RESET=$'\033[0m'; GREEN=$'\033[32m'
YELLOW=$'\033[33m'; RED=$'\033[31m'; CYAN=$'\033[36m'
DIM=$'\033[2m'; BLUE=$'\033[34m'
fi
}
log_info() { [[ "$QUIET" == false ]] && printf ' %s\n' "${DIM}${1}${RESET}" >&2; }
log_ok() { [[ "$QUIET" == false ]] && printf ' %s\n' "${GREEN}✔ ${1}${RESET}" >&2; }
log_warn() { printf ' %s\n' "${YELLOW}⚠ ${1}${RESET}" >&2; }
log_source() { [[ "$QUIET" == false ]] && printf ' %s\n' "${BLUE}↳ ${1}${RESET}" >&2; }
log_header() { [[ "$QUIET" == false ]] && printf '%s\n' "${BOLD}${CYAN}${1}${RESET}" >&2; }
die() { printf '%s\n' "${RED:-}Error: ${1}${RESET:-}" >&2; exit 1; }
# ── Deps ──────────────────────────────────────────────────────────────────────
check_deps() {
local missing=()
command -v curl &>/dev/null || missing+=("curl")
command -v jq &>/dev/null || missing+=("jq")
[[ ${#missing[@]} -eq 0 ]] && return
die "missing tools: ${missing[*]}
Install: sudo apt install ${missing[*]} | brew install ${missing[*]}"
}
# ── Usage ─────────────────────────────────────────────────────────────────────
usage() {
cat <<EOF
${BOLD}asn_lookup.sh${RESET} — find all ASNs registered to one or more company/org names
${BOLD}USAGE${RESET}
asn_lookup.sh -c "Company" ["Company2"…] [OPTIONS]
asn_lookup.sh -f FILE [OPTIONS]
${BOLD}INPUT${RESET}
-c, --company NAME [NAME…] Company/org names to search
-f, --file FILE Text file, one company name per line
${BOLD}OUTPUT${RESET}
-o, --output FILENAME Output filename (no extension); omit = stdout
--format txt|csv txt = one ASN per line (default)
csv = Company,ASN,Source
${BOLD}OPTIONS${RESET}
--show-source Annotate results with the source that found them
--no-dedup Keep duplicates across sources
--timeout N Curl timeout per request in seconds (default: 10)
--no-color Disable colours
-q, --quiet Suppress progress output
-h, --help Show this help
${BOLD}SOURCES${RESET}
1. bgp.tools CSV — global BGP table, all 5 RIRs (downloaded once, grepped locally)
2. ARIN Whois-RWS — US/Canada/Caribbean org registry
3. RIPE NCC RDAP — Europe/Middle East/Central Asia
4. Hurricane Electric bgp.he.net — global cross-reference
${BOLD}EXAMPLES${RESET}
asn_lookup.sh -c "Cloudflare"
asn_lookup.sh -c "Alphabet" "Meta" -o results --format csv
asn_lookup.sh -f companies.txt --show-source -o out
asn_lookup.sh -c "Amazon" -q -o amazon
EOF
exit 0
}
# ── Argument parsing ──────────────────────────────────────────────────────────
parse_args() {
[[ $# -eq 0 ]] && usage
while [[ $# -gt 0 ]]; do
case "$1" in
-c|--company)
shift
while [[ $# -gt 0 && ! "$1" =~ ^- ]]; do COMPANIES+=("$1"); shift; done ;;
-f|--file) COMPANY_FILE="$2"; shift 2 ;;
-o|--output) OUTPUT="$2"; shift 2 ;;
--format) FORMAT="$2"; shift 2 ;;
--timeout) TIMEOUT="$2"; shift 2 ;;
--show-source) SHOW_SOURCE=true; shift ;;
--no-dedup) NO_DEDUP=true; shift ;;
--no-color) NO_COLOR=true; shift ;;
-q|--quiet) QUIET=true; shift ;;
-h|--help) usage ;;
*) die "unknown option: $1 (--help for usage)" ;;
esac
done
}
load_companies() {
if [[ -n "$COMPANY_FILE" ]]; then
[[ -f "$COMPANY_FILE" ]] || die "file not found: $COMPANY_FILE"
while IFS= read -r line || [[ -n "$line" ]]; do
line="${line#"${line%%[![:space:]]*}"}"; line="${line%"${line##*[![:space:]]}"}"
[[ -z "$line" || "$line" =~ ^# ]] && continue
COMPANIES+=("$line")
done < "$COMPANY_FILE"
fi
[[ ${#COMPANIES[@]} -gt 0 ]] || die "no company names provided"
}
# ── Helpers ───────────────────────────────────────────────────────────────────
norm_asn() { echo "$1" | tr '[:lower:]' '[:upper:]' | sed 's/^AS//' | tr -d '[:space:]'; }
is_valid_asn() { [[ "$1" =~ ^[0-9]+$ ]] && [[ "$1" -gt 0 ]] && [[ "$1" -lt 4294967296 ]]; }
add_result() {
# add_result "company" "asn_raw" "source"
local company="$1" source="$3"
local asn; asn=$(norm_asn "$2")
is_valid_asn "$asn" || return 0
printf '%s|%s|%s\n' "$company" "$asn" "$source" >> "$RESULTS_FILE"
}
# Wrapper: curl with timeout, suppress errors, return 1 on failure
safe_curl() {
curl -fsSL --max-time "$TIMEOUT" --connect-timeout 5 -A "$UA" "$@" 2>/dev/null
}
# ═══════════════════════════════════════════════════════════════════════════════
# SOURCE 1 — bgp.tools/asns.csv
# Downloaded once per run. Format: AS15169,"Google LLC",Eyeball
# We grep case-insensitively. The name column is the registered BGP holder name.
# ═══════════════════════════════════════════════════════════════════════════════
fetch_bgp_tools() {
[[ -f "$BGP_TOOLS_CSV" ]] && return 0
log_info " downloading bgp.tools ASN list (one-time, ~3 MB)…"
if safe_curl "https://bgp.tools/asns.csv" -o "$BGP_TOOLS_CSV"; then
local count; count=$(wc -l < "$BGP_TOOLS_CSV")
log_ok " bgp.tools: ${count} entries cached"
return 0
fi
rm -f "$BGP_TOOLS_CSV"
return 1
}
source_bgptools() {
local company="$1"
fetch_bgp_tools || { log_warn "bgp.tools: download failed — skipping"; return 1; }
local found=0
# Each line: AS12345,"Some Org Name",Class
# grep -i for the company name in the whole line, then extract the AS number
while IFS= read -r line; do
[[ -z "$line" ]] && continue
local raw_asn; raw_asn=$(echo "$line" | cut -d',' -f1)
local asn; asn=$(norm_asn "$raw_asn")
if is_valid_asn "$asn"; then
add_result "$company" "$asn" "bgp.tools"
(( found++ )) || true
fi
done < <(grep -i -- "$company" "$BGP_TOOLS_CSV" 2>/dev/null || true)
echo "$found"
}
# ═══════════════════════════════════════════════════════════════════════════════
# SOURCE 2 — ARIN Whois-RWS
# Step 1: search orgs by name wildcard → get org handles
# Step 2: for each handle, fetch /asns to get the ASN list
# ═══════════════════════════════════════════════════════════════════════════════
source_arin() {
local company="$1"
local found=0
# jq's @uri encodes the string for use in a URL
local encoded; encoded=$(printf '%s' "$company" | jq -sRr @uri)
local search_url="https://whois.arin.net/rest/orgs;name=*${encoded}*.json"
local orgs_json
orgs_json=$(safe_curl -H "Accept: application/json" "$search_url") || return 1
# The orgRef can be a single object or an array — normalise to a stream
local handles
handles=$(printf '%s' "$orgs_json" | jq -r '
( .orgs.orgRef // empty )
| if type == "array" then .[] else . end
| .["@handle"]
' 2>/dev/null) || return 1
[[ -z "$handles" ]] && return 0
while IFS= read -r handle; do
[[ -z "$handle" ]] && continue
local asns_json
asns_json=$(safe_curl -H "Accept: application/json" \
"https://whois.arin.net/rest/org/${handle}/asns.json") || continue
# asnRef can be single object or array
while IFS= read -r ref_url; do
[[ -z "$ref_url" ]] && continue
local asn; asn=$(basename "$ref_url" | sed 's/^AS//')
if is_valid_asn "$asn"; then
add_result "$company" "$asn" "ARIN"
(( found++ )) || true
fi
done < <(printf '%s' "$asns_json" | jq -r '
( .asns.asnRef // empty )
| if type == "array" then .[] else . end
| .["$"]
' 2>/dev/null || true)
done <<< "$handles"
echo "$found"
}
# ═══════════════════════════════════════════════════════════════════════════════
# SOURCE 3 — RIPE NCC full-text search (RDAP)
# Searches aut-num objects whose org/descr fields contain the company name.
# Returns up to 100 results per query.
# ═══════════════════════════════════════════════════════════════════════════════
source_ripe() {
local company="$1"
local found=0
local encoded; encoded=$(printf '%s' "$company" | jq -sRr @uri)
# RIPE full-text search for aut-num objects
local url="https://rest.db.ripe.net/search.json?query-string=${encoded}&type-filter=aut-num&flags=no-referenced&flags=no-irt"
local json
json=$(safe_curl -H "Accept: application/json" "$url") || return 1
while IFS= read -r asn_raw; do
[[ -z "$asn_raw" ]] && continue
local asn; asn=$(norm_asn "$asn_raw")
if is_valid_asn "$asn"; then
add_result "$company" "$asn" "RIPE"
(( found++ )) || true
fi
done < <(printf '%s' "$json" | jq -r '
.objects.object[]?
| select(.type == "aut-num")
| .["primary-key"].attribute[]?
| select(.name == "aut-num")
| .value
' 2>/dev/null || true)
echo "$found"
}
# ═══════════════════════════════════════════════════════════════════════════════
# SOURCE 4 — Hurricane Electric bgp.he.net
# Plain HTML search — extracts ASNs from search results table.
# ═══════════════════════════════════════════════════════════════════════════════
source_he() {
local company="$1"
local found=0
local encoded; encoded=$(printf '%s' "$company" | jq -sRr @uri)
local url="https://bgp.he.net/search?search%5Bsearch%5D=${encoded}&commit=Search"
local html
html=$(safe_curl "$url") || return 1
# Extract ASNs from href="/AS12345" patterns in the results table
while IFS= read -r asn; do
[[ -z "$asn" ]] && continue
if is_valid_asn "$asn"; then
add_result "$company" "$asn" "HE"
(( found++ )) || true
fi
done < <(echo "$html" | grep -oE 'href="/AS[0-9]+"' | grep -oE '[0-9]+' || true)
echo "$found"
}
# ── Per-company orchestrator ──────────────────────────────────────────────────
lookup_company() {
local company="$1"
log_header ""
log_header " ▸ ${company}"
local b; b=$(wc -l < "$RESULTS_FILE" 2>/dev/null || echo 0)
# ── Source 1: bgp.tools
log_info "[1/4] bgp.tools CSV…"
local n1=0
n1=$(source_bgptools "$company" 2>/dev/null || echo 0)
log_source "bgp.tools: ${n1} hit(s)"
# ── Source 2: ARIN
log_info "[2/4] ARIN Whois-RWS…"
local n2=0
n2=$(source_arin "$company" 2>/dev/null || echo 0)
log_source "ARIN: ${n2} hit(s)"
# ── Source 3: RIPE NCC
log_info "[3/4] RIPE NCC RDAP…"
local n3=0
n3=$(source_ripe "$company" 2>/dev/null || echo 0)
log_source "RIPE: ${n3} hit(s)"
# ── Source 4: Hurricane Electric
log_info "[4/4] Hurricane Electric bgp.he.net…"
local n4=0
n4=$(source_he "$company" 2>/dev/null || echo 0)
log_source "HE: ${n4} hit(s)"
local after; after=$(wc -l < "$RESULTS_FILE" 2>/dev/null || echo 0)
local raw=$(( after - b ))
log_ok "${company}: ${raw} raw result(s) from all sources"
}
# ── Dedup & output ────────────────────────────────────────────────────────────
dedup() {
# Keep first occurrence of each Company|ASN pair
awk -F'|' '!seen[$1"|"$2]++' "$RESULTS_FILE"
}
write_output() {
local data="$1"
if [[ "$FORMAT" == "csv" ]]; then
echo "Company,ASN,Source"
while IFS='|' read -r company asn source; do
[[ -z "$asn" ]] && continue
printf '"%s",AS%s,%s\n' "$company" "$asn" "$source"
done <<< "$data"
else
if [[ "$SHOW_SOURCE" == true ]]; then
while IFS='|' read -r company asn source; do
[[ -z "$asn" ]] && continue
printf 'AS%-10s # %-24s [%s]\n' "$asn" "$company" "$source"
done <<< "$data"
else
while IFS='|' read -r _ asn _; do
[[ -z "$asn" ]] && continue
printf 'AS%s\n' "$asn"
done <<< "$data"
fi
fi
}
# ── Main ──────────────────────────────────────────────────────────────────────
main() {
parse_args "$@"
setup_colors
check_deps
load_companies
[[ "$FORMAT" != "txt" && "$FORMAT" != "csv" ]] && die "invalid format '$FORMAT'"
touch "$RESULTS_FILE"
log_header ""
log_header "asn_lookup — searching ${#COMPANIES[@]} name(s) across 4 sources"
for company in "${COMPANIES[@]}"; do
lookup_company "$company"
done
local final_data
if [[ "$NO_DEDUP" == true ]]; then
final_data=$(cat "$RESULTS_FILE")
else
final_data=$(dedup)
fi
# Count non-empty lines
local total=0
[[ -n "$final_data" ]] && total=$(printf '%s\n' "$final_data" | grep -c . || true)
log_header ""
log_ok "Done — ${total} unique ASN(s) total"
log_header ""
if [[ -n "$OUTPUT" ]]; then
local fname="$OUTPUT"
[[ "$fname" != *".${FORMAT}" ]] && fname="${fname}.${FORMAT}"
write_output "$final_data" > "$fname"
[[ "$QUIET" == false ]] && \
printf ' %s\n\n' "${BOLD}Saved → ${fname} (${total} entries)${RESET}" >&2
else
write_output "$final_data"
fi
}
main "$@"
Wir lassen das Tool für uns arbeiten, indem wir es auf Kommandzeile benutzen. Das heißt, wir speichern uns den Quelltext ab, zB unter asn-lookup.sh, machen das Programm ausführbar und öffnen dann das Kommandozeilenprogramm. Wir navigieren dann in den Ordner, in dem Du das Programm abgespeichert hast und können dann damit arbeiten, ohne lange Adress-Pfade anzugeben.
Im Kopf des Quelltextes siehst Du schon, wie man es benutzen kann, bzw. findest dort Hinweise auf die möglichen Optionen.
./asn_lookup.sh -c „C1“ „C2“ -o DATEINAME –format txt
Das Script prüft nun bei den Registrierungswebseiten die Unternehmensnamen C1 und C2. Die Liste kann aber theoretisch so lang sein, wie Du willst. Möchtest Du alle Companies zusammen lookuppen, dann passt Du Dir das einfach an, wie gewünscht. Aber amP macht das keinen Sinn. Fangen wir mit einem einfachen und wohlverdienten Beispiel an: Google.
./asn_lookup.sh -c „Google“ -o google –format txt
Wenn Du nun das Script ausführst, erstell es in dem Ordner in dem das Script liegt, eine Datei die da heißt google.txt. In diesem befinden sich alle ASN Nummern, die zu dem Unternehmensnamen gefunden wurden.
Mit dieser Liste können wir nun die IP-Ranges abfragen, die wir für unsere IP-Sets brauchen, damit unser Router künftig alle diese Adressen blockiert.
Wir befinden uns immer noch im gleichen Ordner und speichern uns das nachfolgende Script als asn-ranges.sh ab.
ASN-Ranges bash-script
#!/usr/bin/env bash
# asn_ranges.sh — Collect all IPv4/IPv6 prefixes announced by one or more ASNs.
#
# Queries ALL available sources in parallel per ASN, then merges + deduplicates:
# 1. RIPE NCC stat API (no key required)
# 2. bgp.tools (no key required; replaces defunct BGPView)
# 3. RADB / IRR whois (no key; needs: whois)
# 4. RouteViews API (no key required; optional: --rv-key for higher rate limits)
# 5. Cloudflare Radar API (requires free token: --cf-token)
#
# Dependencies: curl, jq
# Optional: whois (for RADB/IRR source)
#
# Usage examples:
# ./asn_ranges.sh -a 13335 15169
# ./asn_ranges.sh -a AS13335 AS15169 -o cloudflare-google --format csv
# ./asn_ranges.sh -f asns.txt -o output --format txt --ipv4-only
# ./asn_ranges.sh -a 13335 --ipv6-only -o ranges --format csv
# ./asn_ranges.sh -a 13335 15169 --delay 0.5 --no-color -q
# ./asn_ranges.sh -a 13335 --cf-token TOKEN -o out
# ./asn_ranges.sh -a 13335 --rv-key KEY -o out
#
# Output file naming (when -o is given):
# <name>-ipv4.<ext> and/or <name>-ipv6.<ext>
set -euo pipefail
# ── Defaults ──────────────────────────────────────────────────────────────────
FORMAT="txt"
OUTPUT=""
DELAY="0.25"
WANT_V4=true
WANT_V6=true
QUIET=false
NO_COLOR=false
ASN_LIST=()
ASN_FILE=""
CF_TOKEN="" # Cloudflare Radar API token (optional)
RV_KEY="" # RouteViews API key (optional, relaxes rate limits)
UA="asn-ranges-sh/3.0"
# API URL templates
RIPE_URL="https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS{ASN}&sourceapp=asn-ranges-sh"
BGPTOOLS_URL="https://bgp.tools/table.jsonl"
RV_URL="https://api.routeviews.org/asn/{ASN}"
CF_URL="https://api.cloudflare.com/client/v4/radar/bgp/routes/pfx2as?asn={ASN}&format=json"
# ── Colours ───────────────────────────────────────────────────────────────────
setup_colors() {
if [[ "$NO_COLOR" == true ]] || [[ ! -t 2 ]]; then
BOLD=""; RESET=""; GREEN=""; YELLOW=""; RED=""; CYAN=""; DIM=""; BLUE=""
else
BOLD=$'\033[1m'; RESET=$'\033[0m'; GREEN=$'\033[32m'
YELLOW=$'\033[33m'; RED=$'\033[31m'; CYAN=$'\033[36m'
DIM=$'\033[2m'; BLUE=$'\033[34m'
fi
}
log_info() { [[ "$QUIET" == false ]] && printf '%s\n' " ${DIM}${1}${RESET}" >&2 || true; }
log_ok() { [[ "$QUIET" == false ]] && printf '%s\n' " ${GREEN}✔ ${1}${RESET}" >&2 || true; }
log_warn() { printf '%s\n' " ${YELLOW}⚠ ${1}${RESET}" >&2; }
log_err() { printf '%s\n' " ${RED}✘ ${1}${RESET}" >&2; }
log_header() { [[ "$QUIET" == false ]] && printf '%s\n' "${BOLD}${CYAN}${1}${RESET}" >&2 || true; }
log_plain() { [[ "$QUIET" == false ]] && printf '%s\n' " ${1}" >&2 || true; }
log_src() { [[ "$QUIET" == false ]] && printf '%s\n' " ${BLUE}↳ ${1}${RESET}" >&2 || true; }
die() { printf '%s\n' "${RED}Error: ${1}${RESET}" >&2; exit 1; }
# ── Dependency check ──────────────────────────────────────────────────────────
check_deps() {
local missing=()
command -v curl &>/dev/null || missing+=("curl")
command -v jq &>/dev/null || missing+=("jq")
if [[ ${#missing[@]} -gt 0 ]]; then
die "missing required tools: ${missing[*]}
Install with:
Debian/Ubuntu : sudo apt install ${missing[*]}
macOS : brew install ${missing[*]}
Arch : sudo pacman -S ${missing[*]}"
fi
if ! command -v whois &>/dev/null; then
log_warn "whois not found — RADB/IRR source will be skipped"
log_warn " Install: sudo apt install whois OR brew install whois"
fi
}
# ── Usage ─────────────────────────────────────────────────────────────────────
usage() {
cat <<EOF
${BOLD}asn_ranges.sh${RESET} — fetch all IPv4/IPv6 prefixes for one or more ASNs
Queries all available data sources and merges + deduplicates results.
${BOLD}USAGE${RESET}
asn_ranges.sh -a ASN [ASN …] [OPTIONS]
asn_ranges.sh -f FILE [OPTIONS]
${BOLD}INPUT (one of -a or -f required)${RESET}
-a, --asn ASN [ASN …] One or more ASNs (e.g. 13335 or AS15169)
-f, --file FILE Text file: one ASN per line (or comma-separated)
${BOLD}OUTPUT${RESET}
-o, --output FILENAME Base output filename (no extension).
Writes <FILENAME>-ipv4.<ext> and <FILENAME>-ipv6.<ext>
Omit to print to stdout.
--format txt|csv txt = one prefix per line (default)
csv = two-column ASN,Prefix
${BOLD}ADDRESS FAMILIES${RESET}
--ipv4-only Only fetch/output IPv4 prefixes
--ipv6-only Only fetch/output IPv6 prefixes
${BOLD}DATA SOURCES${RESET}
Sources queried automatically (no key required):
• RIPE NCC stat API
• BGPView API
• RADB / IRR (whois must be installed)
• RouteViews (1 req/s guest rate limit without a key)
Optional authenticated sources:
--cf-token TOKEN Cloudflare Radar API bearer token
(free at https://dash.cloudflare.com/profile/api-tokens)
--rv-key KEY RouteViews API key
(free via PeeringDB at https://api.routeviews.org/member-area/)
--skip-source NAME Skip a specific source (ripe|bgptools|radb|routeviews|cloudflare)
Can be repeated.
${BOLD}MISC${RESET}
--delay SECONDS Pause between ASN iterations (default: 0.25)
--no-color Disable coloured output
-q, --quiet Suppress progress; errors still shown
-h, --help Show this help
${BOLD}EXAMPLES${RESET}
asn_ranges.sh -a 13335 15169
asn_ranges.sh -a AS13335 -o cloudflare --format csv
→ cloudflare-ipv4.csv + cloudflare-ipv6.csv
asn_ranges.sh -f asns.txt -o output --ipv4-only
→ output-ipv4.txt
asn_ranges.sh -a 13335 --cf-token \$CF_TOKEN --rv-key \$RV_KEY -o out
asn_ranges.sh -a 13335 --skip-source bgpview --skip-source radb -o out
EOF
exit 0
}
# ── Argument parsing ──────────────────────────────────────────────────────────
SKIP_SOURCES=()
parse_args() {
[[ $# -eq 0 ]] && usage
while [[ $# -gt 0 ]]; do
case "$1" in
-a|--asn)
shift
while [[ $# -gt 0 && ! "$1" =~ ^- ]]; do
IFS=',' read -ra parts <<< "$1"
ASN_LIST+=("${parts[@]}")
shift
done
;;
-f|--file) ASN_FILE="$2"; shift 2 ;;
-o|--output) OUTPUT="$2"; shift 2 ;;
--format) FORMAT="$2"; shift 2 ;;
--delay) DELAY="$2"; shift 2 ;;
--ipv4-only) WANT_V6=false; shift ;;
--ipv6-only) WANT_V4=false; shift ;;
--no-color) NO_COLOR=true; shift ;;
-q|--quiet) QUIET=true; shift ;;
--cf-token) CF_TOKEN="$2"; shift 2 ;;
--rv-key) RV_KEY="$2"; shift 2 ;;
--skip-source) SKIP_SOURCES+=("$2"); shift 2 ;;
-h|--help) usage ;;
*) die "unknown option: $1 — run with --help for usage" ;;
esac
done
}
source_enabled() {
local name="$1"
for s in "${SKIP_SOURCES[@]:-}"; do
[[ "$s" == "$name" ]] && return 1
done
return 0
}
# ── ASN helpers ───────────────────────────────────────────────────────────────
normalize_asn() {
echo "$1" | tr '[:lower:]' '[:upper:]' | sed 's/^[[:space:]]*AS//' | tr -d '[:space:]'
}
is_valid_asn() { [[ "$1" =~ ^[0-9]+$ ]]; }
load_asns() {
local -n _out=$1
local raw=()
if [[ -n "$ASN_FILE" ]]; then
[[ -f "$ASN_FILE" ]] || die "file not found: $ASN_FILE"
while IFS= read -r line || [[ -n "$line" ]]; do
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
IFS=',' read -ra parts <<< "$line"
raw+=("${parts[@]}")
done < "$ASN_FILE"
else
raw=("${ASN_LIST[@]}")
fi
local -a seen=()
for raw_asn in "${raw[@]}"; do
local n
n=$(normalize_asn "$raw_asn")
[[ -z "$n" ]] && continue
is_valid_asn "$n" || { log_warn "skipping invalid ASN: '$raw_asn'"; continue; }
local dup=false
for s in "${seen[@]:-}"; do [[ "$s" == "$n" ]] && dup=true && break; done
if [[ "$dup" == false ]]; then
_out+=("$n")
seen+=("$n")
fi
done
}
# ── Source fetchers ───────────────────────────────────────────────────────────
# Each fetcher prints "IPv4|<prefix>" or "IPv6|<prefix>" lines to stdout.
# Returns 0 on success, 1 on failure.
fetch_ripe() {
local asn="$1"
local url="${RIPE_URL/\{ASN\}/$asn}"
local http_code json
http_code=$(curl -o /tmp/_asn_ripe.json -w "%{http_code}" -fsSL \
--max-time 20 -A "$UA" "$url" 2>/dev/null) || true
[[ "$http_code" != "200" ]] && return 1
json=$(cat /tmp/_asn_ripe.json)
local status
status=$(printf '%s' "$json" | jq -r '.status // "error"')
[[ "$status" != "ok" ]] && return 1
if [[ "$WANT_V4" == true ]]; then
printf '%s' "$json" | jq -r '
.data.prefixes[]?.prefix // empty
| select(test(":") | not)
| "IPv4|\(.)"
'
fi
if [[ "$WANT_V6" == true ]]; then
printf '%s' "$json" | jq -r '
.data.prefixes[]?.prefix // empty
| select(test(":"))
| "IPv6|\(.)"
'
fi
}
fetch_bgptools() {
local asn="$1"
local http_code
# bgp.tools/table.jsonl: one JSON object per line, e.g.
# {"CIDR":"1.1.1.0/24","ASN":13335,"Hits":509}
# We stream it and filter for the target ASN in jq to avoid loading the full file.
http_code=$(curl -o /tmp/_asn_bgptools.jsonl -w "%{http_code}" -fsSL \
--max-time 60 -A "$UA" "$BGPTOOLS_URL" 2>/dev/null) || true
[[ "$http_code" != "200" ]] && return 1
[[ ! -s /tmp/_asn_bgptools.jsonl ]] && return 1
if [[ "$WANT_V4" == true ]]; then
jq -r --argjson asn "$asn" '
select(.ASN == $asn)
| .CIDR
| select(test(":") | not)
| "IPv4|\(.)"
' /tmp/_asn_bgptools.jsonl 2>/dev/null || true
fi
if [[ "$WANT_V6" == true ]]; then
jq -r --argjson asn "$asn" '
select(.ASN == $asn)
| .CIDR
| select(test(":"))
| "IPv6|\(.)"
' /tmp/_asn_bgptools.jsonl 2>/dev/null || true
fi
}
fetch_radb() {
local asn="$1"
command -v whois &>/dev/null || return 1
if [[ "$WANT_V4" == true ]]; then
# !gasNNN — get all IPv4 routes for an origin ASN
whois -h whois.radb.net "!gas${asn}" 2>/dev/null \
| tr ' ' '\n' \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$' \
| sed 's/^/IPv4|/' \
|| true
fi
if [[ "$WANT_V6" == true ]]; then
# !6asNNN — get all IPv6 routes for an origin ASN
whois -h whois.radb.net "!6as${asn}" 2>/dev/null \
| tr ' ' '\n' \
| grep -E '^[0-9a-fA-F:]+/[0-9]+$' \
| sed 's/^/IPv6|/' \
|| true
fi
}
fetch_routeviews() {
local asn="$1"
local url="${RV_URL/\{ASN\}/$asn}"
local http_code json
local -a curl_args=(-fsSL --max-time 30 -A "$UA")
[[ -n "$RV_KEY" ]] && curl_args+=(-H "Api-Key: ${RV_KEY}")
http_code=$(curl -o /tmp/_asn_rv.json -w "%{http_code}" \
"${curl_args[@]}" "$url" 2>/dev/null) || true
[[ "$http_code" != "200" ]] && return 1
json=$(cat /tmp/_asn_rv.json)
# RouteViews /asn/{ASN} returns .data.prefixes[] with each prefix as a string
local error
error=$(printf '%s' "$json" | jq -r '.error // empty')
[[ -n "$error" ]] && return 1
if [[ "$WANT_V4" == true ]]; then
printf '%s' "$json" | jq -r '
.data.prefixes[]? // empty
| select(test(":") | not)
| "IPv4|\(.)"
' 2>/dev/null || true
fi
if [[ "$WANT_V6" == true ]]; then
printf '%s' "$json" | jq -r '
.data.prefixes[]? // empty
| select(test(":"))
| "IPv6|\(.)"
' 2>/dev/null || true
fi
}
fetch_cloudflare() {
local asn="$1"
[[ -z "$CF_TOKEN" ]] && return 1
local url="${CF_URL/\{ASN\}/$asn}"
local http_code json
http_code=$(curl -o /tmp/_asn_cf.json -w "%{http_code}" -fsSL \
--max-time 20 -A "$UA" \
-H "Authorization: Bearer ${CF_TOKEN}" \
"$url" 2>/dev/null) || true
[[ "$http_code" != "200" ]] && return 1
json=$(cat /tmp/_asn_cf.json)
local success
success=$(printf '%s' "$json" | jq -r '.success // false')
[[ "$success" != "true" ]] && return 1
# Cloudflare pfx2as returns .result.prefix_origins[].prefix
if [[ "$WANT_V4" == true ]]; then
printf '%s' "$json" | jq -r '
.result.prefix_origins[]?.prefix // empty
| select(test(":") | not)
| "IPv4|\(.)"
'
fi
if [[ "$WANT_V6" == true ]]; then
printf '%s' "$json" | jq -r '
.result.prefix_origins[]?.prefix // empty
| select(test(":"))
| "IPv6|\(.)"
'
fi
}
# ── Multi-source fetch + merge ────────────────────────────────────────────────
# Calls all enabled sources, merges output, deduplicates.
# Prints "SOURCE|VERSION|PREFIX" lines so the summary can show per-source counts.
fetch_all_sources() {
local asn="$1"
local combined_v4="" combined_v6=""
# Helper: count tagged lines safely (never triggers set -e)
count_lines() { printf '%s\n' "$1" | grep -c "$2" 2>/dev/null || echo 0; }
# Helper: accumulate prefixes from a source's output into combined_v4 / combined_v6
accumulate() {
local src_out="$1" label="$2"
local n4 n6
n4=$(count_lines "$src_out" "^IPv4|")
n6=$(count_lines "$src_out" "^IPv6|")
log_src "${label}: ${n4} IPv4 ${n6} IPv6"
[[ -n "$src_out" ]] || return 0
local raw4 raw6
raw4=$(printf '%s\n' "$src_out" | grep "^IPv4|" | cut -d'|' -f2 2>/dev/null || true)
raw6=$(printf '%s\n' "$src_out" | grep "^IPv6|" | cut -d'|' -f2 2>/dev/null || true)
# Only append if there is actual content — avoids stray newlines that
# confuse the dedup regex later
[[ -n "${raw4//[$'\n\r ']/}" ]] && combined_v4+=$'\n'"$raw4"
[[ -n "${raw6//[$'\n\r ']/}" ]] && combined_v6+=$'\n'"$raw6"
}
# --- RIPE ---
if source_enabled "ripe"; then
local ripe_out=""
if ripe_out=$(fetch_ripe "$asn" 2>/dev/null); then
accumulate "$ripe_out" "RIPE NCC "
else
log_src "RIPE NCC : ${RED}failed${RESET}"
fi
fi
# --- bgp.tools ---
if source_enabled "bgptools"; then
local bgptools_out=""
if bgptools_out=$(fetch_bgptools "$asn" 2>/dev/null); then
accumulate "$bgptools_out" "bgp.tools "
else
log_src "bgp.tools : ${RED}failed${RESET}"
fi
fi
# --- RADB/IRR ---
if source_enabled "radb"; then
local radb_out=""
if radb_out=$(fetch_radb "$asn" 2>/dev/null); then
accumulate "$radb_out" "RADB/IRR "
else
log_src "RADB/IRR : ${YELLOW}failed / skipped${RESET}"
fi
fi
# --- RouteViews ---
if source_enabled "routeviews"; then
local rv_out=""
if rv_out=$(fetch_routeviews "$asn" 2>/dev/null); then
accumulate "$rv_out" "RouteViews "
else
log_src "RouteViews : ${RED}failed${RESET}"
fi
fi
# --- Cloudflare Radar ---
if source_enabled "cloudflare"; then
if [[ -n "$CF_TOKEN" ]]; then
local cf_out=""
if cf_out=$(fetch_cloudflare "$asn" 2>/dev/null); then
accumulate "$cf_out" "CF Radar "
else
log_src "CF Radar : ${RED}failed${RESET}"
fi
else
log_src "CF Radar : ${DIM}skipped (no --cf-token)${RESET}"
fi
fi
# Deduplicate and emit as "IPv4|prefix" / "IPv6|prefix" lines
# These tagged lines are what gets stored in RECORDS_DATA and read by write_output_family
if [[ "$WANT_V4" == true && -n "$combined_v4" ]]; then
printf '%s\n' "$combined_v4" \
| grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/[0-9]+$' \
| sort -u \
| sed 's/^/IPv4|/'
fi
if [[ "$WANT_V6" == true && -n "$combined_v6" ]]; then
printf '%s\n' "$combined_v6" \
| grep -E '^[0-9a-fA-F:]+:[0-9a-fA-F:]+/[0-9]+$' \
| sort -u \
| sed 's/^/IPv6|/'
fi
}
# ── Output helpers ────────────────────────────────────────────────────────────
write_output_family() {
local family="$1"
if [[ "$FORMAT" == "csv" ]]; then
echo "ASN,Prefix"
for idx in "${!RECORDS_ASN[@]}"; do
local asn="${RECORDS_ASN[$idx]}"
while IFS='|' read -r ver prefix; do
[[ "$ver" == "$family" && -n "$prefix" ]] && echo "AS${asn},${prefix}" || true
done <<< "${RECORDS_DATA[$idx]}"
done
else
for idx in "${!RECORDS_DATA[@]}"; do
while IFS='|' read -r ver prefix; do
[[ "$ver" == "$family" && -n "$prefix" ]] && echo "$prefix" || true
done <<< "${RECORDS_DATA[$idx]}"
done
fi
}
write_output_stdout() {
if [[ "$FORMAT" == "csv" ]]; then
echo "ASN,Prefix"
for idx in "${!RECORDS_ASN[@]}"; do
local asn="${RECORDS_ASN[$idx]}"
while IFS='|' read -r _ prefix; do
[[ -n "$prefix" ]] && echo "AS${asn},${prefix}" || true
done <<< "${RECORDS_DATA[$idx]}"
done
else
for idx in "${!RECORDS_DATA[@]}"; do
while IFS='|' read -r _ prefix; do
[[ -n "$prefix" ]] && echo "$prefix" || true
done <<< "${RECORDS_DATA[$idx]}"
done
fi
}
# ── Main ──────────────────────────────────────────────────────────────────────
main() {
parse_args "$@"
setup_colors
check_deps
[[ -z "$ASN_FILE" && ${#ASN_LIST[@]} -eq 0 ]] && die "provide ASNs via -a or -f (--help for usage)"
[[ -n "$ASN_FILE" && ${#ASN_LIST[@]} -gt 0 ]] && die "-a and -f are mutually exclusive"
[[ "$WANT_V4" == false && "$WANT_V6" == false ]] && die "--ipv4-only and --ipv6-only cannot be combined"
[[ "$FORMAT" != "txt" && "$FORMAT" != "csv" ]] && die "invalid format '$FORMAT' — use txt or csv"
local -a asns=()
load_asns asns
[[ ${#asns[@]} -eq 0 ]] && die "no valid ASNs found in input"
# Show which sources are active
log_header ""
log_header "asn_ranges — fetching prefixes for ${#asns[@]} ASN(s)"
log_header ""
if [[ "$QUIET" == false ]]; then
local src_list="RIPE NCC, bgp.tools, RADB/IRR, RouteViews"
[[ -n "$CF_TOKEN" ]] && src_list+=", Cloudflare Radar"
printf '%s\n' " ${DIM}Sources : ${src_list}${RESET}" >&2
[[ -z "$CF_TOKEN" ]] && printf '%s\n' " ${DIM}Tip : add --cf-token TOKEN to include Cloudflare Radar${RESET}" >&2
[[ -z "$RV_KEY" ]] && printf '%s\n' " ${DIM}Tip : add --rv-key KEY for higher RouteViews rate limits${RESET}" >&2
printf '\n' >&2
fi
RECORDS_ASN=()
RECORDS_DATA=()
local -a failed=()
local total=0 total_v4=0 total_v6=0
for i in "${!asns[@]}"; do
local asn="${asns[$i]}"
local idx=$(( i + 1 ))
log_info "[${idx}/${#asns[@]}] AS${asn} — querying all sources …"
local lines=""
if lines=$(fetch_all_sources "$asn"); then
local v4_n v6_n
v4_n=$(printf '%s\n' "$lines" | grep -c "^IPv4|" 2>/dev/null || echo 0)
v6_n=$(printf '%s\n' "$lines" | grep -c "^IPv6|" 2>/dev/null || echo 0)
if [[ $(( v4_n + v6_n )) -eq 0 ]]; then
log_err "AS${asn}: no prefixes found across all sources"
failed+=("$asn")
else
RECORDS_ASN+=("$asn")
RECORDS_DATA+=("$lines")
log_ok "AS${asn}: ${v4_n} IPv4 ${v6_n} IPv6 (merged + deduplicated)"
(( total += v4_n + v6_n )) || true
(( total_v4 += v4_n )) || true
(( total_v6 += v6_n )) || true
fi
else
log_err "AS${asn}: all sources failed"
failed+=("$asn")
fi
[[ $idx -lt ${#asns[@]} ]] && sleep "$DELAY"
[[ "$QUIET" == false ]] && printf '\n' >&2
done
[[ $total -eq 0 ]] && die "no prefixes retrieved from any source"
# Summary
log_header "────────────────────────────────────────"
log_plain "${BOLD}Total (deduplicated)${RESET}"
[[ "$QUIET" == false ]] && printf '%s\n' " ${GREEN}IPv4 : ${total_v4}${RESET}" >&2
[[ "$QUIET" == false ]] && printf '%s\n' " ${YELLOW}IPv6 : ${total_v6}${RESET}" >&2
[[ ${#failed[@]} -gt 0 ]] && log_err "No data : $(printf 'AS%s ' "${failed[@]}")"
log_header "────────────────────────────────────────"
log_plain ""
if [[ -n "$OUTPUT" ]]; then
local ext="$FORMAT"
local base="${OUTPUT%.*}"
if [[ "$WANT_V4" == true ]]; then
local fname_v4="${base}-ipv4.${ext}"
write_output_family "IPv4" > "$fname_v4"
log_plain "${BOLD}Saved → ${fname_v4} (${total_v4} prefixes)${RESET}"
fi
if [[ "$WANT_V6" == true ]]; then
local fname_v6="${base}-ipv6.${ext}"
write_output_family "IPv6" > "$fname_v6"
log_plain "${BOLD}Saved → ${fname_v6} (${total_v6} prefixes)${RESET}"
fi
log_plain ""
else
write_output_stdout
fi
rm -f /tmp/_asn_ripe.json /tmp/_asn_bgptools.jsonl /tmp/_asn_rv.json /tmp/_asn_cf.json
}
main "$@"
Wir machen das Script noch ausführbar und können dann anhand unserer vorher angefertigten Textdatei, die alle ASN Nummern enthält, eine Abfrage der IP-Ranges machen.
./asn_ranges.sh -f TEXTDATEIMITASNS -o DATEINAMEN –format txt
Also: ./asn_ranges.sh -f google.txt -o google –format txt
Das wird uns zwei Textfiles erzeugen, einmal für IPv4-Adressen und einmal für IPv6-Adressen. Das hat den Hintergrund, dass Openwrt, bzw. dessen Firewall die Adressen getrennt benötigt. Das wird etwas weiter unten klar, warum das so ist.
Nun haben wir zwei Textdateien, die wir, so wie sie sind, in unserer openwrt Firewall verwenden können. Im folgenden benutzen wir die grafische Oberfläche LuCI.
Erstellen eines IP-Sets
Wenn ihr im LuCI Adminpanel seid, navigiert ihr zum Firewall-Setup.

Dann sogleich zu den IP-Sets.

Hier habt ihr die Möglichkeit neue IP-Sets anzulegen. Klickt auf „add“ und erstellt ein neues IP-Set. Wie erwähnt müssen wir das zwei Mal machen, einmal für die IPv4- und dann für die IPv6-Adressranges. Zuerst erstellen wir das Set für IPv4.

Unter Include File könnt ihr die eben erstellte Textdatei mit den IPv4 Adressen hochladen (das Hochladen wird hier nicht in einem Bild dargestellt, weil das in den verschiedenen Versionen von LuCi auch anders aussieht. Ihr werdet aber damit klarkommen die Datei hochzuladen. Hier seht ihr im letzten Bild, dass die Datei bereits hochgeladen ist. Wichtig ist, dass wir zuerst ein IPv4 Set erstellen wollen.)

Immer schön Saven und dann wiederholen wir das für das IPv6 Set.

Damit hätten wir im Router nun die Adressranges hinterlegt. Jetzt müssen wir noch eine Firewall-Regel daraus machen und gehen in den nächsten Reiter „Traffic-Rules“. Das kann und wird leider in manchen LuCi Versionen auch anders aussehen.

Dort erstellt ihr eine neue Regel. Auch zuerst wieder die Regel für IPv4.

Dann wechselt ihr auf den Reiter Advanced Settings, um dort das IPv4 Set anzugeben, welches wir eben gerade erstellt haben.

Hier lasst ihr alle anderen Einstellungen so, und klickt auf „Use IP-Set“. Dort wählt ihr dann das IPv4-Set aus. Wichtig ist hier auch, dass das übereinstimmt mit der Option Restrict to adress family. Ansonsten bekommt ihr Fehlermeldungen des Todes und die Regel wird nicht greifen.
Das gleiche wiederholen wir mit dem IPv6-Set. Vergesst nicht, danach alles zu speichern und einmal die Firewall neu zustarten wenn ihr mit ssh eingeloggt seid. fw4 restart

Und wenn ihr alles korrekt umgesetzt habt, und ihr euch über euren Router mit dem Netz verbindet, solltet ihr beim Aufruf von google.de die folgende Seite sehen:

Mission Accomplished! Congrats! <3
Nachteile so klar wie Kloßbrühe
Bei Google ist mir jeder Block tatsächlich willkommen. Youtube wird ebenso geblockt, wie googletagmanager.com. Doch blocke ich zB die ASNs von Microslop, dann blockt es mir auch zB duckduckgo.com weg. Das ist ärgerlich, denn ich mochte ddg eigentlich immer und die Suchmaschine war meine bisher favorisierte. Ich könnte die ASN IP Ranges von ddg jetzt aus der Liste händisch entfernen, aber ganz im Ernst: darauf habe ich gar keine Lust.
Wer eine so deutliche Kollaboration mit Microsoft eingeht, dass die IPs der Heimat-URL auf deren Hostnamen aufgelöst werden (oder andersrum), dann ist mir dieser Block tatsächlich auch willkommen, weil dann leider ddg unbenutzbar geworden ist. Es gibt andere Suchmaschinen, die auch zu irgendwelchen Ergebnissen führen. Außerdem ist das auch immer das Risiko, wenn man nomadig durch das Internet zieht. Ein Dienst geht, ein anderer kommt. Es ist ok. Aber für Dich vielleicht nicht.
Daher ist es wichtig sich klar zu machen, dass das Blocken ganzer IP Ranges von missbräuchlichen Companies als Holzhammermethode tatsächlich eine bedeutende Klarheit schafft und man sich damit sehr festlegt.
Doch genau das ist mir zB auch persönlich wichtig. Denn es wird sich nichts ändern, wenn wir Dienste weiterhin nutzen, die unsere Privatsphäre und den Datenschutz so krass missachten. Da wo wir sagen: tja kann man nichts machen und einen Dienst weiterhin nutzen, kann man genauso gut auch sagen, tja, kann man nichts machen und sich eine Alternative suchen. Ein ASN-weiter Block schafft die Grundlage für ein gesundes tja, kann man halt nichts machen.
In diesem Sinne, viel Spaß beim Arschtritte verteilen! Kann man halt nichts machen! <3

