aboutsummaryrefslogtreecommitdiff
path: root/2026
diff options
context:
space:
mode:
Diffstat (limited to '2026')
-rw-r--r--2026/2026-02-25-ipasn-net/.gitignore1
-rw-r--r--2026/2026-02-25-ipasn-net/SPEC.md485
-rw-r--r--2026/2026-02-25-ipasn-net/ipasn-net-api.md204
3 files changed, 690 insertions, 0 deletions
diff --git a/2026/2026-02-25-ipasn-net/.gitignore b/2026/2026-02-25-ipasn-net/.gitignore
new file mode 100644
index 0000000..ea58f50
--- /dev/null
+++ b/2026/2026-02-25-ipasn-net/.gitignore
@@ -0,0 +1 @@
+golang/
diff --git a/2026/2026-02-25-ipasn-net/SPEC.md b/2026/2026-02-25-ipasn-net/SPEC.md
new file mode 100644
index 0000000..ee4f0e9
--- /dev/null
+++ b/2026/2026-02-25-ipasn-net/SPEC.md
@@ -0,0 +1,485 @@
+# ipasn.net Client — Implementation Specification
+
+Version 0.1.0 — 2026-02-26
+
+This document specifies the behavior of a client library and companion CLI
+for the `ipasn.net` DNS-based IP enrichment service. It is deliberately
+language-agnostic. Implementations should follow the contracts described
+here and adapt naming conventions to the target language's idioms (e.g.,
+`snake_case` in Go/Rust, `camelCase` in TypeScript, etc.).
+
+---
+
+## 1. Upstream Service
+
+`ipasn.net` is a DNS authoritative server (PowerDNS + custom backend) that
+accepts TXT record queries and returns IP address metadata assembled from
+BGP routing tables, geolocation data, RIR statistics, and RPKI/ROA
+validation state.
+
+Reference: https://blog.apnic.net/2026/02/09/from-the-stupid-dns-tricks-department-ipasn-net/
+
+---
+
+## 2. Canonical Data Model
+
+Every enrichment result is represented as a single structured record. The
+library normalizes all upstream response formats (pipe-delimited, JSON,
+single-attribute) into this canonical shape. Field names below are logical;
+implementations should use idiomatic casing.
+
+```
+IpasnRecord {
+ address : string // queried or resolved IP address
+ address_family : "IPv4" | "IPv6"
+ bgp_status : string // e.g. "ADVERTISED"
+ advertised_prefix: string // CIDR notation
+ origin_asn : integer // autonomous system number (numeric only, no "AS" prefix)
+ org_name : string // organization name, underscores replaced with spaces
+ country_code : string // ISO 3166-1 alpha-2
+ country_name : string // full name, underscores replaced with spaces
+ rir : string // e.g. "arin", "apnic", "ripe", "afrinic", "lacnic"
+ rir_prefix : string // CIDR notation as registered
+ reg_status : string // e.g. "assigned", "allocated"
+ reg_date : string // ISO 8601 date (YYYY-MM-DD)
+ rov_status : string // e.g. "VLD", or empty/null if no ROA
+ roa_prefix : string | null // CIDR notation of the covering ROA
+ roa_max_length : integer | null
+ roa_origin_asn : integer | null
+ roa_trust_anchor : string | null // e.g. "ARIN", "APNIC_RPKI_Root"
+}
+```
+
+### 2.1 Normalization Rules
+
+- `origin_asn`, `roa_origin_asn`, `roa_max_length`: parse to integer. Strip any leading "AS" if present.
+- `org_name`, `country_name`, `roa_trust_anchor`: replace all `_` with space.
+- If RPKI/ROA fields are absent or empty in the upstream response, set the four `roa_*` fields to null and `rov_status` to an empty string or null per the language's convention.
+- If a compact/cymru-compat response is returned, the fields not present in that format (`org_name`, `country_name`, `bgp_status`, `reg_status`, `rov_status`, `roa_*`) are null.
+
+---
+
+## 3. Library API
+
+The library exposes a stateless client that performs DNS TXT lookups and
+returns `IpasnRecord` values. All public functions return structured
+results; raw DNS string handling is internal.
+
+### 3.1 Constructor / Configuration
+
+```
+IpasnClient {
+ dns_server : string // resolver address; default = system resolver
+ timeout : duration // per-query timeout; default = 5 seconds
+ max_retries : integer // retry count on transient DNS failure; default = 1
+}
+```
+
+Implementations that use the system stub resolver by default are fine. An
+explicit resolver override allows callers to bypass local caching or use a
+specific recursive resolver (e.g., `9.9.9.9`).
+
+### 3.2 Core Methods
+
+#### `lookup(target: string, options?: LookupOptions) -> IpasnRecord`
+
+Primary entry point. Accepts an IPv4 address, IPv6 address, or FQDN.
+
+```
+LookupOptions {
+ mode : "full" | "json" | "compact" // default: "json"
+ force : "ipv4" | "ipv6" | null // only meaningful when target is an FQDN; default: null
+}
+```
+
+Behavior:
+
+1. Classify `target` as IPv4, IPv6, or hostname.
+2. Build the DNS query name per the table in section 4.
+3. Perform a TXT record lookup.
+4. Parse the response into `IpasnRecord` using the appropriate parser (section 5).
+5. Return the record or raise/return an error.
+
+When `target` is a hostname and `force` is null, the upstream service
+resolves the name itself (preferring AAAA). When `force` is `"ipv4"`, the
+`a.dns.` prefix is inserted so the upstream resolves to an A record.
+
+#### `lookup_attribute(ip: string, attribute: string) -> string`
+
+Query a single attribute. `ip` must be an IPv4 or IPv6 address (not a
+hostname). `attribute` is inserted as a subdomain label (e.g., `"cc"`,
+`"rpki"`).
+
+Returns the raw TXT string value. The caller is responsible for
+interpretation. This method exists for forward-compatibility with
+undocumented attributes.
+
+Known attributes: `cc`, `rpki`. The attribute namespace is not fully
+documented upstream and may expand.
+
+#### `lookup_batch(targets: []string, options?: LookupOptions) -> []Result<IpasnRecord, Error>`
+
+Batch variant. Performs lookups concurrently (respecting a configurable
+concurrency limit, default 10). Returns results in input order. Each
+element is independently either a record or an error.
+
+```
+BatchOptions extends LookupOptions {
+ concurrency : integer // max parallel DNS queries; default: 10
+}
+```
+
+### 3.3 Error Types
+
+```
+IpasnError
+ ├── DnsError // DNS transport failure (SERVFAIL, timeout, network)
+ ├── NxdomainError // NXDOMAIN — address/name not found upstream
+ ├── ParseError // response received but could not be parsed
+ └── InvalidInputError // target is not a valid IP address or hostname
+```
+
+Implementations should carry the original DNS response code or transport
+error as context.
+
+---
+
+## 4. DNS Query Construction
+
+Given a classified target and mode, build the QNAME as follows.
+
+### 4.1 IP Address Targets
+
+| Mode | IPv4 QNAME | IPv6 QNAME |
+|---------|-------------------------------------|--------------------------------------|
+| full | `{ip}.ipasn.net` | `{ip}.ipasn.net` |
+| json | `{ip}.json.ipasn.net` | `{ip}.json.ipasn.net` |
+| compact | `{ip}.origin.asn.ipasn.net` | `{ip}.origin6.asn.ipasn.net` |
+
+Note: compact mode uses `origin.` for IPv4 and `origin6.` for IPv6.
+
+### 4.2 Hostname Targets
+
+| Force | QNAME |
+|-------|-------------------------------------|
+| null | `{fqdn}.dns.ipasn.net` |
+| ipv4 | `{fqdn}.a.dns.ipasn.net` |
+
+Hostname targets always return full/pipe-delimited detail regardless of `mode`. The library should parse accordingly and ignore the `mode` parameter for hostname targets (or document this behavior).
+
+### 4.3 Attribute Targets
+
+```
+{ip}.{attribute}.ipasn.net
+```
+
+IPv4 and IPv6 only. Not available for hostnames.
+
+### 4.4 Input Validation
+
+Before constructing the QNAME:
+
+- IPv4: validate dotted-quad, 0-255 per octet, 1-4 octets.
+- IPv6: validate per RFC 5952. Accept compressed forms (e.g., `2001:db8::1`).
+- Hostname: validate as a syntactically valid FQDN. Do not resolve locally — the upstream backend handles resolution.
+- Reject inputs containing whitespace, null bytes, or characters outside the DNS label-safe set after classification.
+
+---
+
+## 5. Response Parsing
+
+### 5.1 TXT Record Reassembly
+
+DNS TXT records may be split across multiple character strings (each <= 255
+bytes). Concatenate all strings in order before parsing. Strip surrounding
+double-quote characters if present (artifact of `dig` output format; raw
+DNS libraries typically return bare strings).
+
+### 5.2 JSON Mode Parser
+
+Concatenate TXT strings, then JSON-decode into a map. Map upstream JSON
+keys to `IpasnRecord` fields:
+
+| Upstream Key | Record Field |
+|-------------------|-------------------|
+| `Address` | `address` |
+| `Class` | `address_family` |
+| `BGP` | `bgp_status` |
+| `Advertised_Prefix` | `advertised_prefix` |
+| `Origin_AS` | `origin_asn` |
+| `Org_Name` | `org_name` |
+| `CC` | `country_code` |
+| `CC_Name` | `country_name` |
+| `RIR` | `rir` |
+| `RIR_Prefix` | `rir_prefix` |
+| `Reg_Status` | `reg_status` |
+| `Reg_Date` | `reg_date` |
+| `ROV` | `rov_status` |
+| `ROA_Prefix` | `roa_prefix` |
+| `ROA_Maxlen` | `roa_max_length` |
+| `ROA_AS` | `roa_origin_asn` |
+| `ROA_TAL` | `roa_trust_anchor` |
+
+Apply normalization rules from section 2.1 after mapping.
+
+### 5.3 Full (Pipe-Delimited) Parser
+
+Split the concatenated TXT string on `|`. Fields are positional (17 fields,
+0-indexed):
+
+```
+0: address
+1: address_family
+2: bgp_status
+3: advertised_prefix
+4: origin_asn
+5: org_name
+6: country_code
+7: country_name
+8: rir
+9: rir_prefix
+10: reg_status
+11: reg_date
+12: rov_status
+13: roa_prefix
+14: roa_max_length
+15: roa_origin_asn
+16: roa_trust_anchor
+```
+
+Trim whitespace from each field after splitting. Apply normalization rules.
+
+If fewer than 17 fields are present, set missing fields to null and do not
+error. This provides forward-compatibility if the upstream format changes.
+
+### 5.4 Compact (Cymru-Compatible) Parser
+
+Split on ` | ` (pipe with surrounding spaces). Fields are positional (5
+fields, 0-indexed):
+
+```
+0: advertised_prefix (CIDR)
+1: origin_asn
+2: country_code
+3: rir
+4: reg_date
+```
+
+All other `IpasnRecord` fields are null. Derive `address_family` from the
+prefix (contains `:` = IPv6, otherwise IPv4). Set `address` from the
+queried input.
+
+### 5.5 Attribute Parser
+
+Return the raw TXT string. No structured parsing. The `rpki` attribute
+returns a compound string (e.g., `VLD_216.88.0.0/14-24_3561_ARIN`) which
+the caller may decompose if needed; the library does not parse it further.
+
+---
+
+## 6. CLI Specification
+
+The CLI is a thin wrapper around the library. It reads targets from
+arguments or stdin, performs lookups, and writes JSON to stdout.
+
+### 6.1 Name
+
+`ipasn`
+
+### 6.2 Usage
+
+```
+ipasn [options] <target> [target ...]
+cat ips.txt | ipasn [options] --stdin
+```
+
+### 6.3 Options
+
+| Flag | Short | Default | Description |
+|-----------------|-------|----------|----------------------------------------------|
+| `--mode` | `-m` | `json` | Query mode: `full`, `json`, `compact` |
+| `--force` | `-f` | (none) | Force resolution: `ipv4` or `ipv6` |
+| `--attribute` | `-a` | (none) | Query single attribute (e.g., `cc`, `rpki`) |
+| `--dns-server` | `-d` | system | DNS resolver address |
+| `--timeout` | `-t` | `5s` | Per-query timeout |
+| `--concurrency` | `-c` | `10` | Max parallel queries |
+| `--stdin` | `-` | false | Read targets from stdin (one per line) |
+| `--compact` | | false | Output NDJSON (one object per line) |
+| `--raw` | | false | Output raw TXT response, no parsing |
+| `--quiet` | `-q` | false | Suppress errors to stderr |
+| `--version` | `-V` | | Print version and exit |
+| `--help` | `-h` | | Print help and exit |
+
+### 6.4 Output Formats
+
+#### Single target (default)
+
+Outputs a single JSON object to stdout:
+
+```json
+{
+ "address": "216.88.0.0",
+ "address_family": "IPv4",
+ "bgp_status": "ADVERTISED",
+ "advertised_prefix": "216.88.0.0/14",
+ "origin_asn": 3561,
+ "org_name": "CenturyLink Communications, LLC",
+ "country_code": "US",
+ "country_name": "United States of America",
+ "rir": "arin",
+ "rir_prefix": "216.88.0.0/14",
+ "reg_status": "assigned",
+ "reg_date": "1998-09-25",
+ "rov_status": "VLD",
+ "roa_prefix": "216.88.0.0/14",
+ "roa_max_length": 24,
+ "roa_origin_asn": 3561,
+ "roa_trust_anchor": "ARIN"
+}
+```
+
+#### Multiple targets (default)
+
+Outputs a JSON array of objects:
+
+```json
+[
+ { "address": "216.88.0.0", ... },
+ { "address": "2001:4860::", ... }
+]
+```
+
+#### Multiple targets with `--compact`
+
+Outputs NDJSON (newline-delimited JSON), one object per line:
+
+```
+{"address":"216.88.0.0",...}
+{"address":"2001:4860::",...}
+```
+
+#### `--attribute` mode
+
+Outputs a JSON object with the target as key and the raw attribute value:
+
+```json
+{ "target": "216.88.0.0", "attribute": "cc", "value": "US" }
+```
+
+#### `--raw` mode
+
+Outputs the raw DNS TXT string, one per line, no JSON wrapping:
+
+```
+216.88.0.0|IPv4|ADVERTISED|216.88.0.0/14|3561|...
+```
+
+### 6.5 Error Output
+
+Errors are written to stderr as JSON:
+
+```json
+{ "error": "dns_error", "target": "999.999.999.999", "message": "NXDOMAIN" }
+```
+
+When processing multiple targets, errors for individual targets do not halt
+execution. The corresponding position in the output array contains an error
+object:
+
+```json
+{ "address": null, "error": "NXDOMAIN", "target": "999.999.999.999" }
+```
+
+### 6.6 Exit Codes
+
+| Code | Meaning |
+|------|----------------------------------------------|
+| 0 | All lookups succeeded |
+| 1 | One or more lookups failed (partial results) |
+| 2 | Invalid arguments / usage error |
+| 3 | Fatal error (no DNS connectivity, etc.) |
+
+### 6.7 Stdin Processing
+
+When `--stdin` is specified, the CLI reads lines from stdin. Blank lines
+and lines starting with `#` are skipped. Leading/trailing whitespace is
+trimmed. This allows feeding output from other tools or newline-separated
+IP lists directly.
+
+---
+
+## 7. Testing
+
+### 7.1 Unit Tests
+
+Implementations must include unit tests for:
+
+- Input classification (IPv4 vs IPv6 vs hostname).
+- QNAME construction for every mode/force/attribute combination.
+- Parsing of all three response formats (JSON, pipe-delimited, compact) using static fixture strings.
+- Normalization: underscore replacement, integer coercion, null handling for missing fields.
+- Error cases: malformed responses, empty TXT records, fewer-than-expected fields.
+
+### 7.2 Integration Tests
+
+Integration tests issue real DNS queries against `ipasn.net`. They should
+be gated behind an opt-in flag or environment variable (`IPASN_LIVE_TESTS=1`)
+since they depend on network access and upstream availability.
+
+Suggested live test targets:
+
+| Target | Expected Assertions |
+|--------------------|----------------------------------------------|
+| `216.88.0.0` | origin_asn is integer, country_code = "US" |
+| `2001:4860::` | address_family = "IPv6" |
+| `www.google.com` | address is populated, origin_asn > 0 |
+| `192.0.2.1` | graceful error (TEST-NET, may not be routed) |
+
+### 7.3 CLI Tests
+
+Test the CLI as a subprocess:
+
+- Single IP, verify valid JSON on stdout.
+- Multiple IPs, verify JSON array length matches input count.
+- `--compact` flag, verify NDJSON (one valid JSON object per line).
+- `--stdin` with piped input.
+- `--attribute cc`, verify `value` field present.
+- `--raw`, verify no JSON wrapping.
+- Invalid input, verify exit code 2.
+
+---
+
+## 8. Implementation Notes
+
+### 8.1 DNS Library Selection
+
+Use a DNS library that provides access to raw TXT record data (not just
+the first string). The library must support querying specific nameservers
+and setting timeouts. Avoid shelling out to `dig`.
+
+### 8.2 Concurrency
+
+Batch operations should use a bounded worker pool or semaphore pattern.
+DNS queries are I/O-bound and benefit from concurrency, but unbounded
+parallelism can trigger rate limiting or resolver overload.
+
+### 8.3 Caching
+
+The library should NOT implement its own DNS cache. The system resolver
+or configured recursive resolver already caches based on TTL. Adding
+another cache layer creates staleness bugs and complicates the
+implementation for no measurable benefit.
+
+### 8.4 Rate Limiting
+
+The upstream service has no documented rate limits, but it is a community
+resource. Implementations should default to reasonable concurrency (10) and
+provide a mechanism for callers to throttle further if needed.
+
+### 8.5 Underscore Handling
+
+The upstream service uses underscores as space substitutes in `Org_Name`,
+`CC_Name`, and `ROA_TAL` fields. The library normalizes these to spaces in
+the canonical record. The CLI outputs the normalized (spaces) form. If a
+caller needs the raw underscore form, they can use `lookup_attribute` or
+`--raw`.
diff --git a/2026/2026-02-25-ipasn-net/ipasn-net-api.md b/2026/2026-02-25-ipasn-net/ipasn-net-api.md
new file mode 100644
index 0000000..1a45c6b
--- /dev/null
+++ b/2026/2026-02-25-ipasn-net/ipasn-net-api.md
@@ -0,0 +1,204 @@
+# ipasn.net DNS API Reference
+
+**Source:** Geoff Huston, APNIC Blog, 2026-02-09
+**URL:** https://blog.apnic.net/2026/02/09/from-the-stupid-dns-tricks-department-ipasn-net/
+
+---
+
+## Overview
+
+`ipasn.net` is a DNS-based IP address enrichment service built on PowerDNS with a plug-in backend. Unlike Team Cymru's `origin.asn.cymru.com`, it accepts IP addresses in natural (non-reversed) order and supports IPv6 addresses with standard colon notation. All queries use DNS TXT record lookups.
+
+The backend assembles responses from three data sources: a current BGP routing table snapshot (origin AS and advertised prefix), a geolocation database (country code), and RIR statistics reports (registry, registration date).
+
+---
+
+## Query Protocol
+
+All queries are issued via `dig +short TXT` (or equivalent DNS TXT lookups).
+
+---
+
+## Endpoints
+
+### 1. Cymru-Compatible (Compact)
+
+Returns a pipe-delimited string similar to Team Cymru's format, but with natural IP address order.
+
+**IPv4:**
+
+```
+dig +short TXT <IPv4_ADDRESS>.origin.asn.ipasn.net
+```
+
+**IPv6:**
+
+```
+dig +short TXT <IPv6_ADDRESS>.origin6.asn.ipasn.net
+```
+
+**Response format:** `"<prefix> | <origin_asn> | <cc> | <rir> | <reg_date>"`
+
+**Example (IPv4):**
+
+```
+$ dig +short TXT 216.88.0.0.origin.asn.ipasn.net
+"216.88.0.0/14 | 3561 | US | arin | 1998-09-25"
+```
+
+**Example (IPv6):**
+
+```
+$ dig +short TXT 2401:2000:6660::.origin6.asn.ipasn.net
+"2401:2000::/32 | 4608 | AU | apnic | 2007-06-19"
+```
+
+---
+
+### 2. Full Detail (Pipe-Delimited)
+
+Returns an extended pipe-delimited record with BGP, registration, org name, and RPKI/ROA data.
+
+```
+dig +short TXT <IP_ADDRESS>.ipasn.net
+```
+
+**Response format (fields separated by `|`):**
+
+| Position | Field | Example |
+|----------|-------|---------|
+| 1 | Queried Address | `216.88.0.0` |
+| 2 | Address Family | `IPv4` or `IPv6` |
+| 3 | BGP Status | `ADVERTISED` |
+| 4 | Advertised Prefix | `216.88.0.0/14` |
+| 5 | Origin AS | `3561` |
+| 6 | Org Name | `CenturyLink_Communications,_LLC` |
+| 7 | Country Code | `US` |
+| 8 | Country Name | `United_States_of_America` |
+| 9 | RIR | `arin` |
+| 10 | RIR Prefix | `216.88.0.0/14` |
+| 11 | Registration Status | `assigned` |
+| 12 | Registration Date | `1998-09-25` |
+| 13 | ROV Status | `VLD` |
+| 14 | ROA Prefix | `216.88.0.0/14` |
+| 15 | ROA Max Length | `24` |
+| 16 | ROA Origin AS | `3561` |
+| 17 | ROA Trust Anchor | `ARIN` |
+
+**Example:**
+
+```
+$ dig +short TXT 216.88.0.0.ipasn.net
+"216.88.0.0|IPv4|ADVERTISED|216.88.0.0/14|3561|CenturyLink_Communications,_LLC|US|United_States_of_America|arin|216.88.0.0/14|assigned|1998-09-25|VLD|216.88.0.0/14|24|3561|ARIN"
+```
+
+---
+
+### 3. JSON Output
+
+Same data as endpoint 2, returned as a JSON object.
+
+```
+dig +short TXT <IP_ADDRESS>.json.ipasn.net
+```
+
+**Response fields:**
+
+```json
+{
+ "Address": "216.88.0.0",
+ "Class": "IPv4",
+ "BGP": "ADVERTISED",
+ "Advertised_Prefix": "216.88.0.0/14",
+ "Origin_AS": "3561",
+ "Org_Name": "CenturyLink_Communications,_LLC",
+ "CC": "US",
+ "CC_Name": "United_States_of_America",
+ "RIR": "arin",
+ "RIR_Prefix": "216.88.0.0/14",
+ "Reg_Status": "assigned",
+ "Reg_Date": "1998-09-25",
+ "ROV": "VLD",
+ "ROA_Prefix": "216.88.0.0/14",
+ "ROA_Maxlen": "24",
+ "ROA_AS": "3561",
+ "ROA_TAL": "ARIN"
+}
+```
+
+Note: The DNS TXT response may span multiple strings that need to be concatenated before JSON parsing.
+
+---
+
+### 4. DNS Resolution + Lookup
+
+Resolves a domain name first, then performs the IP enrichment lookup. Returns full detail format.
+
+**Default (prefers AAAA/IPv6):**
+
+```
+dig +short TXT <FQDN>.dns.ipasn.net
+```
+
+**Force IPv4 (A record):**
+
+```
+dig +short TXT <FQDN>.a.dns.ipasn.net
+```
+
+**Examples:**
+
+```
+$ dig +short TXT www.potaroo.net.dns.ipasn.net
+"2401:2000:6660:0:0:0:0:108|IPv6|ADVERTISED|2401:2000::/32|4608|..."
+
+$ dig +short TXT www.potaroo.net.a.dns.ipasn.net
+"203.133.248.108|IPv4|ADVERTISED|203.133.248.0/24|4608|..."
+```
+
+---
+
+### 5. Individual Attribute Queries
+
+Query a single field by inserting the attribute name before `ipasn.net`.
+
+**Country Code:**
+
+```
+dig +short TXT <IP_ADDRESS>.cc.ipasn.net
+```
+
+Response: `"US"`
+
+**RPKI/ROA Status:**
+
+```
+dig +short TXT <IP_ADDRESS>.rpki.ipasn.net
+```
+
+Response: `"VLD_216.88.0.0/14-24_3561_ARIN"`
+
+---
+
+## Query Pattern Summary
+
+| Query Type | DNS Name Pattern |
+|------------|-----------------|
+| Cymru-compat (IPv4) | `<IPv4>.origin.asn.ipasn.net` |
+| Cymru-compat (IPv6) | `<IPv6>.origin6.asn.ipasn.net` |
+| Full detail | `<IP>.ipasn.net` |
+| JSON output | `<IP>.json.ipasn.net` |
+| DNS resolve (default) | `<FQDN>.dns.ipasn.net` |
+| DNS resolve (force A) | `<FQDN>.a.dns.ipasn.net` |
+| Country code only | `<IP>.cc.ipasn.net` |
+| RPKI status only | `<IP>.rpki.ipasn.net` |
+
+---
+
+## Notes
+
+- IPv4 addresses use natural dotted-quad notation (no octet reversal required).
+- IPv6 addresses use standard colon-delimited notation (no nibble expansion or reversal required).
+- Spaces in org names are replaced with underscores.
+- ROV field values include `VLD` (valid ROA exists). Other possible values are not documented in the source material.
+- The service runs on PowerDNS with a custom backend that dynamically generates responses rather than serving from a zone file. \ No newline at end of file