All dispatches/Geolocation

Automating RFC 8805 Geofeed Ingestion: Validating Operator Claims Against RPKI

How network operators publish self-hosted RFC 8805 CSV geofeeds, and how our compiler authenticates RPSL remarks, verifies RPKI ROAs, and filters out spoofed latency claims.

Why RFC 8805 and RFC 9092 transformed ISP geolocation

Historically, when a cloud provider, ISP, or enterprise network operator deployed a new /24 IPv4 subnet or /48 IPv6 prefix in a new city, updating third-party geolocation databases was a manual, error-prone ordeal. Network engineers had to submit web forms or emails to half a dozen commercial GeoIP vendors and wait weeks for each vendor to manually review and publish the change.

To solve this coordination bottleneck, the IETF published two complementary standards:

  1. *RFC 8805 (A Format for Self-Published IP Geolocation Feeds)*: Standardizes a machine-readable, UTF-8 CSV format where network operators map their CIDR prefixes to ISO 3166-1 alpha-2 country codes, ISO 3166-2 subdivision codes, and city names.
  2. *RFC 9092 (Finding and Using Geofeed Data)*: Standardizes how Regional Internet Registry (RIR) database objects (inetnum, inet6num, and NetRange records in RPSL / RDAP) link to a self-published geofeed file via a remarks: #geofeed https://... or structured geofeed: attribute, and how consumers must validate prefix bailiwick.

While RFC 8805 and RFC 9092 are foundational for modern IP geolocation, blindly trusting every geofeed CSV found in WHOIS is a critical security and accuracy mistake. Over the past three years, we have observed everything from expired geofeed domains hijacked by domain drop-catchers to proxy operators deliberately publishing false geofeeds to spoof residential locations.

This article explains the exact syntax of RFC 8805 and RFC 9092, details the three attack vectors against unverified geofeed ingestion, and walks through the four-stage cryptographic and physical verification pipeline built into the ipscope compiler.

Anatomy of an RFC 8805 CSV feed and RFC 9092 RIR binding

An RFC 8805 geofeed is a UTF-8 text file where each non-comment line contains five comma-separated fields:

# ip_prefix,alpha2_country,region,city,postal_code
198.51.100.0/24,US,US-VA,Ashburn,
203.0.113.0/24,DE,DE-HE,Frankfurt am Main,
2001:db8:400::/48,NL,NL-NH,Amsterdam,

Field specification per RFC 8805 Section 2.1

Field PositionNameStandard / FormatNotes
1ip_prefixIPv4 or IPv6 CIDR (a.b.c.d/len or v6::/len)Mandatory. Must be a valid CIDR block within the operator's RIR allocation.
2alpha2_countryISO 3166-1 alpha-2 (US, DE, JP)Recommended. Uppercase 2-letter country code (or empty if unspecified).
3regionISO 3166-2 subdivision code (US-VA, DE-HE, GB-LND)Optional. Must begin with the matching alpha2_country prefix followed by a hyphen.
4cityUTF-8 city name (Frankfurt am Main, Tokyo)Optional. Quoted if the city name contains a comma.
5postal_codePostal / ZIP codeOptional. Explicitly deprecated by RFC 8805 for privacy and accuracy reasons; should be left empty with a trailing comma.

Binding the CSV to your RIR allocation via RFC 9092

A standalone CSV file on a web server has no authority on its own. Under RFC 9092, the geofeed URL must be referenced inside the authoritative RIR database object (RIPE, ARIN, APNIC, LACNIC, or AFRINIC) that covers the IP block:

inetnum:        198.51.100.0 - 198.51.103.255
netname:        EXAMPLE-NET-EU
descr:          Example Cloud Infrastructure GmbH
country:        DE
admin-c:        EC100-RIPE
tech-c:         EC100-RIPE
status:         ASSIGNED PA
geofeed:        https://noc.example.net/geofeed.csv
mnt-by:         EXAMPLE-MNT
source:         RIPE

On RIRs that do not yet expose a dedicated geofeed: attribute on all legacy object types, operators publish the canonical remark line:

remarks:        #geofeed https://noc.example.net/geofeed.csv

Three real-world failure and spoofing vectors in public geofeeds

In our daily crawl of over 19,500 public #geofeed URLs across all five RIRs, roughly 11.4% of raw geofeed rows fail validation. Those failures fall into three categories:

Vector 1: Out-of-bailiwick prefix injection

Suppose Autonomous System AS64500 owns 192.0.2.0/24 and publishes remarks: #geofeed https://as64500.example/feed.csv on its inetnum object. Nothing prevents the author of feed.csv from adding a row for 8.8.8.0/24 or a competitor's subnet (198.51.100.0/24) and claiming it is located in Antarctica.

If a naive parser ingests every CSV referenced anywhere in WHOIS without checking whether each CSV row's ip_prefix is a mathematical subnet of the specific inetnum / inet6num object that referenced that URL, any network operator can overwrite the geolocation of any other network on the internet.

Vector 2: Expired domain hijacking (Dangling #geofeed Remarks)

Smaller ISPs, hosting companies, and startups occasionally rebrand, merge, or shut down without cleaning up old remarks: #geofeed https://old-brand.example/geofeed.csv entries in RIPE or ARIN. When old-brand.example expires at the domain registrar, malicious actors register the expired domain for $10, stand up an HTTPS server with a valid Let's Encrypt certificate, and serve a manipulated geofeed.csv for the still-allocated IP space.

Vector 3: Intentional geo-spoofing for streaming and ad-fraud arbitrage

Residential proxy aggregators, sneaker-bot proxy providers, and bullet-proof hosting operators routinely lease cheap /24 datacenter blocks in low-cost colocation facilities (for example, in Frankfurt, Bucharest, or Warsaw) and publish RFC 8805 geofeeds claiming the subnets are physically located in Dallas, Los Angeles, or London. Because they control both the inetnum object (via lease assignment) and the HTTPS domain hosting the CSV, standard RFC 9092 bailiwick checks pass completely. Only independent physical network telemetry can catch the lie.

Inside the ipscope 4-Gate Geofeed Verification Pipeline

Before a single row from an RFC 8805 geofeed is allowed to influence an ipscope dataset snapshot, our compiler passes the feed through four strict verification gates:

RIR RDAP/RPSL Snapshot (ARIN, RIPE, APNIC, LACNIC, AFRINIC)
   |
   v
[ Gate 1: Strict RFC 9092 Prefix Bailiwick & Longest-Prefix Hierarchy ]
   |
   v
[ Gate 2: RPKI ROA Cryptographic Origin & ASN Ownership Alignment ]
   |
   v
[ Gate 3: TLS 1.3 / Domain WHOIS Age & Content-Delta Quarantine ]
   |
   v
[ Gate 4: Multi-Vantage Speed-of-Light RTT Physical Verification ]
   |
   +---> RTT Compatible? --(Yes)--> Sets declared_country_code AND physical_country_code
   |
   +---> RTT Violated?   --(No)---> Preserves declared_country_code, overrides physical_country_code,
                                    and sets is_virtual_location = true

Gate 1: Strict RFC 9092 bailiwick enforcement

When our crawler extracts https://noc.example.net/geofeed.csv from an RIR allocation object covering parent prefix P, the parser constructs a strict radix filter scoped exclusively to P. Every row in geofeed.csv with prefix C_i must satisfy two mathematical invariants:

C_i is a subnet of P   AND   prefix_len(C_i) >= prefix_len(P)

Any row where C_i falls outside the parent inetnum or inet6num block P is discarded immediately and increments the feed's security violation counter. Furthermore, if a more-specific sub-allocation object P_sub exists inside P in the RIR database and defines its own #geofeed URL, the child object's geofeed takes precedence for P_sub per RFC 9092 Section 5.

Gate 2: RPKI Route Origin Authorization (ROA) cross-checking

To defend against stale RIR remarks and unauthorized sub-allocation edits, the compiler cross-references every candidate prefix C_i against the global BGP routing table and the Resource Public Key Infrastructure (RPKI) validator cache:

  • Does the prefix have an active BGP announcement?
  • Is the originating ASN backed by a cryptographically valid RPKI ROA (RPKI Valid) signed by the resource holder's RIR certificate?
  • If the geofeed domain belongs to an entity that has no relationship to the active RPKI-validated originating ASN (for example, a dangling remark from a previous lessee), the geofeed is demoted to low confidence.

Gate 3: Domain lifecycle and mass-mutation anomaly quarantine

Before trusting a geofeed HTTPS endpoint, our fetcher inspects:

  1. Transport security: Must resolve over HTTPS (TLS 1.2 or TLS 1.3) with a valid public WebPKI certificate chain and strict redirect bailiwick (cross-domain redirects from the #geofeed URL to third-party domains are flagged).
  2. Domain registration continuity: If the domain hosting the #geofeed URL was recently re-registered after an expiration drop or changed registrar ownership after the RIR last-modified date of the inetnum object, the feed is automatically quarantined for security review.
  3. Syntax and ISO 3166-2 dictionary validation: Every country code is validated against ISO 3166-1 alpha-2, and every subdivision code (US-VA, DE-HE) is validated against the official ISO 3166-2 subdivision registry so typos like US-VIRGINIA or UK-LON (instead of GB-LND) are normalized or rejected cleanly.

Gate 4: Speed-of-light RTT physical verification

This is where ipscope's dual-country architecture (declared_country_code vs physical_country_code) shines.

Even when an operator legitimately owns a prefix, passes RFC 9092 bailiwick, and holds a valid RPKI ROA, their self-published geofeed only represents a declared claim. Our compiler tests every geofeed row against active minimum-RTT measurements from our global probe fleet:

Geofeed Claim (alpha2_country, city)Measured min_rtt from Nearest Local ProbeCompiler Output
DE, DE-HE, Frankfurt am Main1.4 ms from Frankfurt (fra-01)declared_country_code: "DE", physical_country_code: "DE", is_virtual_location: false
US, US-NY, New York1.6 ms from Frankfurt (fra-01), 82 ms from Newark (ewr-01)declared_country_code: "US", physical_country_code: "DE", is_virtual_location: true

Because we preserve the operator's claim in declared_country_code while setting physical_country_code (and country_code) to the physically verified router location, geofeed spoofing can never trick your edge routing or licensing logic.

Writing a local RFC 8805 validator in Python

If your network engineering team publishes an RFC 8805 geofeed for your own ASN prefixes, we recommend validating the CSV syntax and prefix bailiwick in CI/CD before deploying it to your web server. Here is a zero-dependency Python 3 script using the standard library ipaddress and csv modules:

import csv
import ipaddress
import re
import sys
from pathlib import Path

ISO_COUNTRY_RE = re.compile(r"^[A-Z]{2}$")
ISO_REGION_RE = re.compile(r"^[A-Z]{2}-[A-Z0-9]{1,3}$")

def validate_geofeed(csv_path: Path, allowed_supernets: list[str]) -> int:
    supernets = [ipaddress.ip_network(cidr, strict=True) for cidr in allowed_supernets]
    errors = 0

    with csv_path.open("r", encoding="utf-8") as fh:
        for line_no, raw_line in enumerate(fh, start=1):
            stripped = raw_line.strip()
            if not stripped or stripped.startswith("#"):
                continue

            row = next(csv.reader([stripped]))
            if len(row) != 5:
                print(f"Line {line_no}: expected 5 CSV columns, got {len(row)}", file=sys.stderr)
                errors += 1
                continue

            prefix_str, country, region, city, postal = [col.strip() for col in row]

            try:
                net = ipaddress.ip_network(prefix_str, strict=True)
            except ValueError as exc:
                print(f"Line {line_no}: invalid CIDR '{prefix_str}': {exc}", file=sys.stderr)
                errors += 1
                continue

            if not any(net.version == sup.version and net.subnet_of(sup) for sup in supernets):
                print(f"Line {line_no}: prefix {net} is out of bailiwick", file=sys.stderr)
                errors += 1

            if country and not ISO_COUNTRY_RE.match(country):
                print(f"Line {line_no}: invalid ISO 3166-1 alpha-2 country '{country}'", file=sys.stderr)
                errors += 1

            if region:
                if not ISO_REGION_RE.match(region) or not region.startswith(f"{country}-"):
                    print(
                        f"Line {line_no}: region '{region}' must match '{country}-XXX' ISO 3166-2 format",
                        file=sys.stderr,
                    )
                    errors += 1

            if postal:
                print(f"Line {line_no}: warning: postal_code ('{postal}') is deprecated by RFC 8805", file=sys.stderr)

    return errors

if __name__ == "__main__":
    err_count = validate_geofeed(
        Path("geofeed.csv"),
        allowed_supernets=["198.51.100.0/22", "2001:db8:400::/40"],
    )
    sys.exit(1 if err_count > 0 else 0)

How network operators can trigger an immediate geofeed sync on ipscope

Our compiler automatically crawls all RIR #geofeed remarks daily. However, if you have just re-homed a subnet or published a new RFC 8805 feed and want to trigger an immediate validation and re-probe cycle, you can submit your prefix or geofeed URL programmatically via POST /v1/feedback:

curl -sS -X POST "https://api.ipscope.io/v1/feedback" \
  -H "Authorization: Bearer ${IPSCOPE_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "prefix_or_ip": "198.51.100.0/24",
    "role": "network_owner",
    "issue_type": "wrong_city",
    "requested_correction": {
      "country_code": "DE",
      "subdivision": "Hesse",
      "city": "Frankfurt am Main"
    },
    "evidence_url": "https://noc.example.net/geofeed.csv"
  }'

When a network_owner correction is submitted with an evidence_url, our verification worker immediately runs Gates 1 through 4 against the target prefix and queues verified updates for the next dataset snapshot release (06:00 UTC).

Frequently asked questions (FAQ)

Should I include postal codes in my RFC 8805 geofeed CSV?

No. RFC 8805 Section 2.1.1.5 explicitly deprecates the 5th column (postal_code) because mapping an entire /24 or /48 IP block to a single street postal code creates false precision and privacy concerns. Always include the trailing fourth comma so the row has 5 columns, leaving the 5th column empty (for example, 198.51.100.0/24,DE,DE-HE,Frankfurt am Main,).

How do I format the region column in an RFC 8805 geofeed?

The region column (3rd column) must use the full ISO 3166-2 code, which includes the two-letter country prefix, a hyphen, and the one-to-three-character subdivision code (for example, US-VA for Virginia, DE-HE for Hesse, CA-ON for Ontario, or NL-NH for North Holland). Writing VA instead of US-VA violates RFC 8805 Section 2.1.1.3.

What MIME Content-Type header should my web server return for geofeed.csv?

Serve your geofeed over HTTPS (TLS 1.2+) with Content-Type: text/csv; charset=utf-8 (or text/plain; charset=utf-8), and enable ETag and Last-Modified response headers so automated crawlers can use conditional If-None-Match requests efficiently.

Start with 30K free lookups a month.

No card, no trial clock. Upgrade when your traffic does, or download the database when you would rather keep it in-house.