Skip to content

Release notes — 0.19.0

Released 2026-07-13 with DNS Conduit 0.19.0.

Changes merged to main that are not yet tagged. Staging area for the next operator release.

Breaking changes — Lookup spine

Answer production now runs through a single Lookup pipeline phase instead of separate top-level Route, Forward, and Wait for response steps. Pool selection, upstream I/O, and suspend/resume still behave the same; they run inside the forward lookup provider. See Architecture and packet path — Lookup and Reference: lookup.

What changes for operators

Area Before After
Sparse configs (no lookup: block) Route → Forward → Wait for response Lookup with an implicit forward-only profile — same forwarding behavior
Traces (full profile) Top-level route, forward, wait_response phase events Top-level lookup only; routing and upstream wait appear as nested events inside Lookup — Tracing
Built-in metrics — conduit_phase_duration_seconds (full profile) phase label values route, forward, wait_response phase label lookup for answer-production time (no separate top-level route/forward/wait series)
Retry from Response rules Re-entered at Route Re-enters Lookup (full provider chain, subject to cache eligibility) — Retries and transactions

Operator action

  1. If you rely on trace phase names or PromQL on conduit_phase_duration_seconds{phase="forward"}, update dashboards and alerts to use lookup (and nested trace messages where needed). See Tracing and Built-in metrics.
  2. Re-run conduitctl validate --file <config> after upgrade; existing sparse YAML without lookup: should validate unchanged. See Reference: lookup — Implicit default profile.
  3. See Architecture and packet path (updated in this release) for the Lookup-centric packet path.

New features — DNS answer cache

Optional in-memory DNS answer caching is available when you add a caches: catalog and a cache provider before forward in lookup.profiles. Walkthrough: DNS answer cache. Field reference: Caches, Lookup.

Configuration surface

  • caches[] — named cache instances (type: memory) — Reference: caches
  • lookup.profiles.<name>.providers — ordered provider list (cache then forward is typical) — Reference: lookup
  • Per-instance policy on each cache:
  • negative_cache — NXDOMAIN/NODATA; nxdomain_covers_descendants (default true); servfail_ttl_secs (default 10; 0 = do not cache SERVFAIL) — Negative cache
  • truncated_udp — opt-in TC=1 UDP caching (enabled + required ttl_secs when enabled) — Reference: caches — truncated_udp
  • on_hit.response_rules — whether Response rules run after a cache hit (run or skip; see below)
  • rotate_rrset_on_serve — on cache hits, shuffle answer RR order within each RRset before Send (default false; see below)
  • memory.shard_count, memory.eviction (passive default, active opt-in) — Reference: caches — memory
  • max_entries

Example (cache then forward on profile default):

caches:
  - name: global
    type: memory
    max_entries: 100000
    negative_cache:
      enabled: true
      nxdomain_covers_descendants: true
      servfail_ttl_secs: 10
lookup:
  profiles:
    default:
      providers:
        - type: cache
          cache: global
        - type: forward

Configs that omit lookup: continue to use an implicit default profile with a single forward provider — no cache allocation on the hot path. See Reference: lookup — Implicit default profile.

Cache behavior highlights

  • Cache hit — serves the stored wire answer from memory; no upstream forward attempt for that transaction attempt.
  • Cache miss — consults the next provider (typically forward).
  • Parallel identical queries — single upstream fetch via single-flight; waiters resume when the fill completes.
  • Fill — stores the upstream wire answer before Response rules mutate it; authority and additional sections are preserved.
  • TTL on serve — answer TTLs decay by elapsed time since fill; stored slab bytes are not mutated.
  • Question / EDNS echo on serve — each cache hit rewrites the response Question (including mixed-case 0x20 QNAME encoding) and EDNS from the current client query, and sets the response ID — Serve rewriting.
  • Complete answers shared across UDP and TCP — client transport is not a cache key dimension; a UDP fill can satisfy a TCP query (and vice versa). Oversized complete answers are fitted at Send to the UDP client's EDNS payload size on RR boundaries (TC when required data cannot fit). Opt-in truncated_udp TC=1 stubs use a separate key and are UDP-only; a later complete fill for the same dimensions removes any truncated sibling — Cache key dimensions.
  • In-memory only — cache entries are lost on process restart.
  • Reload / apply — lookup profile changes take effect for new queries via the normal snapshot swap; in-flight transactions keep the snapshot they started under. Cache max_entries updates take effect on the live in-memory backend immediately when apply or reload succeeds (no restart; lowering the cap evicts entries). Other cache policy and memory.shard_count require a process restart — see Reference: caches — Reload and apply.

Hit/miss path detail: DNS answer cache — Hit and miss path.

on_hit.response_rules — run vs skip

When the cache serves a hit, Conduit already has a complete wire answer. on_hit.response_rules controls whether the pipeline still runs Response rules on that hit — the same built-in response rules and response-hook Rhai that run after an upstream forward — or sends the cached answer straight to the client. See DNS answer cache — on_hit.response_rules and Reference: caches — on_hit.

Value Cache hit path
run (default when on_hit is omitted) Run Response rules on the cached answer, then Send — same policy hooks as a forward-produced answer
skip Skip Response rules; go straight to Send (lower latency when response rules only matter on cache misses)

If you use response-hook metrics.inc only for upstream outcomes, cache hits will not increment those counters when skip is set. Use default run, built-in metrics (below), or record metrics on the request hook. See User metrics — Cache hits and on_hit skip.

rotate_rrset_on_serve

When true, each cache hit returns the same RRs as stored but may reorder records within each answer RRset (for example multiple A or AAAA records for one name) using a random cyclic offset per RRset. That spreads client load across peers when upstream returned several addresses in a fixed order. Default false — served answer RR order matches the stored wire aside from serve rewriting (query ID, question/EDNS echo, TTL decay); the cache slab is never mutated. See DNS answer cache — rotate_rrset_on_serve.


New features — Observability and Rhai

Built-in metrics

New and extended series (Prometheus scrape and OTLP push with equivalent semantics). Catalog: Built-in metrics — Lookup and cache.

Series Profile Purpose
conduit_queries_dropped_total minimal + full Policy silent drops (reason: request_rules / response_rules)
conduit_lookup_provider_outcomes_total minimal + full Terminal lookup provider outcomes (profile, provider, outcome)
conduit_cache_lookups_total minimal + full Cache read path (cache, profile, result: hit / miss / bypass)
conduit_responses_total minimal + full New label answer_source: cache or forward
conduit_responses_truncated_total minimal + full Same answer_source label
conduit_cache_fills_total, conduit_cache_singleflight_coalesced_total full only Stores and single-flight waiters resolved
conduit_lookup_duration_seconds, conduit_cache_lookup_duration_seconds, conduit_response_duration_seconds full only Latency split by provider / cache / answer source

Forward series (conduit_forward_attempts_total, conduit_forward_duration_seconds) increment only when the forward provider actually attempts upstream I/O — not on cache hit short-circuit.

Example PromQL (cache vs forward response volume):

sum(rate(conduit_responses_total[5m])) by (listener, answer_source)

Tracing and event export

  • Lookup phase entry and per-provider nested events; cache hit without forward route events — Tracing.
  • Event-sink and built-in rule selectors answer_source (cache | forward) and cache_instance (exact cache name) — Event export — Filters.
  • Dnstap extra_fields may include answer_source and cache_instance — Event export — Extra metadata.

Rhai (response and request hooks)

API Hook Notes
txn.set_cache_lookup_eligible(bool) Request Default true; set false to bypass cache for this query — Cache eligibility
txn.answer_source() Response Returns cache, forward, or empty string before an answer exists
txn.cache_instance() Response Cache instance name on cache hits; empty otherwise

txn.last_forward_ms() returns 0 when no forward attempt ran (for example a cache hit). It measures upstream forward RTT only — use txn.answer_source() or built-in metrics to distinguish cache from forward.

lookup(table, key) is unchanged — it reads data_sources policy tables, not the Lookup pipeline phase. See Data sources and lookups. Phase placement for request/response hooks: Hooks and phases.


Upgrade notes

Documentation (this release)

Canonical operator pages for Lookup, cache, metrics, and Rhai in this release:

Observability — OTLP metrics fidelity

OTLP metrics push now maps Prometheus metric families directly (same source as scrape) instead of re-parsing scrape text. Histogram sum, count, and bucket counts match scrape; HELP text is exported as the OTLP description; units are derived from name suffixes (_seconds → s, _bytes → By, and similar). Metric names are unchanged (still Prometheus-style conduit_*). See Metrics.


All changes in this release (automated pull request list).