Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Kinetic Resolution Pipeline: From DNS Query to DHT Record

File: kinetic-dns/src/kinetic_records.rs Crate: kinetic-dns | Stage: 10 Reading Time: 40 minutes


1. What Is This?

This file contains the complete .kin resolution pipeline — the code that turns a raw DNS query packet for saif.kin into a real DNS response containing an A record, CNAME, or TXT record.

It is a single asynchronous function resolve_kinetic() that does six distinct things in strict sequence:

  1. Intercepts reserved/public names before touching the network.
  2. Checks the local Moka cache for a previously resolved apex domain.
  3. If the cache misses, hits the kinetic-daemon REST API (/api/resolve/<name>).
  4. Validates the signature of the returned NameRecord.
  5. Performs E2E KID authentication if the domain specifies a KID record.
  6. Filters SSRF-dangerous IPs, converts Kinetic DNS records into hickory wire-format records, and sends the response.

2. Why Kinetic Needs This

The Hickory DNS library will call the KineticDnsHandler::handle_request() method for every incoming DNS packet. That method routes .kin queries here. At this point, the code has a raw domain name string and must produce a valid DNS wire response.

Important

This pipeline cannot trust the data it receives from the daemon. The daemon is fetching data from a decentralized DHT where anyone can potentially inject malicious records. Therefore, every resolved record must be:

  • Cryptographically verified (signature check via verify_signature()).
  • Optionally E2E authenticated (KID check).
  • SSRF-filtered (no A/AAAA records pointing to 127.x.x.x, 10.x.x.x, etc.).

Warning

Without this pipeline, a malicious actor could publish a DNS zone pointing saif.kin to 127.0.0.1 and cause the browser to attack the user’s own machine.


3. How It Works — Step by Step

Step 1: Reserved Name Interception

-> See: kinetic-dns/src/kinetic_records.rs — Lines 29-80

Before any network call, the code checks if the apex domain (the root part without subdomains) is in kinetic_core::types::PUBLIC_NAMES.

PUBLIC_NAMES is a list of names that are permanently reserved in the Kinetic namespace — things like localhost, internal bootstrap names, and other special identifiers that should never be routable.

localhost handling: If someone queries localhost.kin, the code doesn’t reject it. Instead it returns a hardcoded 127.0.0.1 A record (or ::1 AAAA record) directly, without touching the network.

All other PUBLIC_NAMES: They immediately return NXDOMAIN. No cache lookup. No daemon call. No chance of being redirected to attacker-controlled records.

Step 2: Cache Lookup (Moka try_get_with)

-> See: kinetic-dns/src/kinetic_records.rs — Lines 82-125

The code calls cache.try_get_with(apex_domain, async move { ... }) on the Moka cache.

The key insight here is try_get_with: This method is cache-stampede-safe. If 100 simultaneous DNS queries come in for saif.kin at the same time during a cache miss, only ONE of them will actually fire the API request. The other 99 will wait for that single request to complete and then share its result. This prevents the daemon from being flooded.

The cache stores raw response bytes (Vec<u8>), keyed by apex domain string.

  • Cache Hit (positive): Returns Ok(Some(Vec<u8>)) immediately — no network call.
  • Cache Hit (negative / NXDOMAIN): Returns Ok(None) immediately — the previous lookup found no record, and this answer is cached for 30 seconds.
  • Cache Miss: Fires the API request inside the closure.

Step 3: Daemon API Call

-> See: kinetic-dns/src/kinetic_records.rs — Lines 92-125

On a cache miss, the code constructs the URL <api_url>/api/resolve/<apex_domain> and calls it using the reqwest::Client.

While reading the response, it enforces a strict 100KB payload size limit. The response is read in chunks using resp.chunk().await. If the cumulative size exceeds 100KB, the chunk loop exits immediately with Ok(None), treating it as if the domain doesn’t exist. This prevents a malicious node from sending megabyte payloads to exhaust the DNS server’s memory.

If the daemon returns 404 Not Found, the code returns Ok(None) — the NXDOMAIN path. This negative result is then cached for 30 seconds.

If the daemon returns another error, it returns Err(...) which the cache does NOT store, ensuring the error is not permanently cached.

Step 4: Signature Verification

-> See: kinetic-dns/src/kinetic_records.rs — Lines 131-146

The raw bytes from the cache are deserialized into a kinetic_core::types::NameRecord via serde_json::from_slice().

Immediately after deserialization, before ANY other processing:

#![allow(unused)]
fn main() {
domain_record.verify_signature(kinetic_core::constants::NETWORK_ID).is_err()
}

If the signature fails, the code returns ServFail and logs a warning. The record is completely ignored, even if it contains perfectly formed DNS data.

Important

This is the core anti-tampering check. If a malicious DHT node modifies the payload bytes in transit, the signature fails here.

Step 5: Payload Parsing and KID E2E Authentication

-> See: kinetic-dns/src/kinetic_records.rs — Lines 148-224

After the signature check, the payload is parsed into a DnsZone struct via DnsZone::parse_payload(domain_record.payload()).

The code then scans the apex domain’s @ records for any DnsRecord::KID(did) entries.

A KID record means: “This domain only accepts traffic signed by the specified Kinetic Identity Document.”

If a KID record is found:

  1. It constructs the URL <api_url>/api/resolve-kid/<did> and sends it to the daemon.
  2. The daemon returns the KID document, which contains a list of controller_keys.
  3. The code base64-encodes the NameRecord’s public key and checks if it matches any key in the KID’s controller_keys.
  4. If NO match is found, the DNS query is rejected with ServFail.
  5. If a match is found, E2E authentication passes and resolution continues.

Note

This guarantees that even if an attacker has obtained a valid signature for saif.kin (perhaps via a compromised key), the domain’s KID lock will block them unless their key is also listed as a controller in the KID document.

Step 6: Subdomain Mapping and Record Construction

-> See: kinetic-dns/src/kinetic_records.rs — Lines 226-395

After authentication, the code determines which subdomain is being queried:

  • If domain_name == apex_domain, it uses "@" (the apex record).
  • Otherwise, it strips the apex suffix to get the subdomain label (e.g., "www" from "www.saif.kin").
  • If the exact subdomain is missing from the zone, it falls back to "*" (wildcard records).

For each matching DnsRecord in the zone:

A records: SSRF-checked via kinetic_core::net::is_ssrf_safe(IpAddr::V4(*ip)). Private ranges (127.x, 10.x, 192.168.x, 169.254.x) are blocked with a warning. Safe IPs are added as hickory A records with a 60-second TTL.

AAAA records: Same SSRF filter applied. IPv6 private ranges are blocked.

CNAME records: Checked for local domain targets (localhost, *.local). CNAME targets that parse as IP addresses are SSRF-checked. Valid external CNAMEs are added regardless of query type (per DNS RFC — the resolver will follow CNAMEs automatically).

TXT, PeerId, KID, IPFS records: Returned as TXT records. PeerId is formatted as peerid=<id>, KID as kid=<did>, IPFS as ipfs=<cid>. These are useful for discovery but don’t route traffic.

If response records were constructed, they are sent with NOERROR. If no records matched, the code falls through to the final NXDOMAIN return.


4. Key Pieces

resolve_kinetic<R: ResponseHandler>(...) -> ResponseInfo

The sole exported function. It is generic over R — any type implementing ResponseHandler — so it can work with Hickory’s internal response channel. -> See: kinetic-dns/src/kinetic_records.rs — Lines 15-26

cache.try_get_with(key, async move { ... })

The stampede-safe cache fetch. The closure only fires on cache misses and only once per concurrent group of misses. All concurrent waiters share the result. -> See: kinetic-dns/src/kinetic_records.rs — Lines 86-125

SSRF filter: kinetic_core::net::is_ssrf_safe(ip)

Reused from kinetic-core. Returns false for any private, loopback, link-local, or reserved IP range. Called for every A and AAAA record before it is included in the response. -> See: kinetic-dns/src/kinetic_records.rs — Lines 270-296

KID E2E Authentication block

The nested API call to /api/resolve-kid/<did>. Fetches the KID document and cross-checks the record’s public key against the KID’s controller keys. -> See: kinetic-dns/src/kinetic_records.rs — Lines 152-222


5. Cross-Crate Connections

  • kinetic_core::types::PUBLIC_NAMES: The set of reserved names that bypass resolution.
  • kinetic_core::types::NameRecord::verify_signature(): The cryptographic signature verification for all DHT-fetched records.
  • kinetic_core::types::DnsZone::parse_payload(): Parses the opaque payload bytes into a typed zone with typed records.
  • kinetic_core::types::DnsRecord: The enum of supported record types (A, AAAA, CNAME, TXT, KID, PeerId, IPFS).
  • kinetic_core::net::is_ssrf_safe(): The shared SSRF protection filter applied consistently across the daemon proxy and this DNS layer.
  • kinetic_core::constants::NETWORK_ID: Passed into verify_signature() to tie the signature to this specific Kinetic network deployment.

6. Quick Reference

Query ResultResponse CodeCache Duration
Record found, signature valid, SSRF safeNOERROR5 minutes
Domain not registeredNXDOMAIN30 seconds
Domain in PUBLIC_NAMES (non-localhost)NXDOMAINNot cached
Signature invalidServFailNot cached
KID auth failedServFailNot cached
Daemon API errorServFailNot cached
Payload exceeds 100KBNXDOMAIN5 minutes

Resolution order:

  1. Check PUBLIC_NAMES (instant local decision).
  2. Check Moka cache (try_get_with, stampede-safe).
  3. Query daemon /api/resolve/<apex>.
  4. Verify NameRecord signature.
  5. If KID record exists, verify E2E auth via /api/resolve-kid/<did>.
  6. Map subdomain, filter SSRF, construct hickory records.

7. Open Questions

  • CNAME recursion: When the DNS server returns a CNAME, it expects the client OS resolver to follow the CNAME chain. For .kin CNAME chains that point to other .kin domains, this may cause infinite loops or missing records if the OS resolver doesn’t loop back.
  • TXT-only queries: A client querying only for TXT records on a domain that also has A records will receive the TXT plus any matched KID/PeerId/IPFS records. It does not get the A record unless it also makes an A query.
  • Cache invalidation from daemon: The daemon calls KineticDnsHandler::invalidate_cache() after successful writes, but this only works if both the DNS server and the daemon share the same process (or communicate via IPC). In the standard setup they are separate processes, so this may not actually invalidate across process boundaries.

8. Rust Concepts

  • moka::future::Cache::try_get_with(): The async, concurrent, stampede-safe cache fetch. On a cache miss with N concurrent waiters, exactly one fires the async closure and all N waiters receive the same result.
  • serde_json::from_slice(&bytes): Deserializes raw bytes directly into a typed Rust struct. Used here instead of parsing a String first to avoid an extra allocation.
  • Generic <R: ResponseHandler>: Makes the function work with any type that implements Hickory’s ResponseHandler trait, including test mocks, without dynamic dispatch overhead.
  • Arc<anyhow::Error> inside moka: Moka requires error types to be Clone. anyhow::Error is not Clone, so it is wrapped in Arc (which is cheap to clone via reference-counting) to satisfy the trait bound.