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
- If you rely on trace phase names or PromQL on
conduit_phase_duration_seconds{phase="forward"}, update dashboards and alerts to uselookup(and nested trace messages where needed). See Tracing and Built-in metrics. - Re-run
conduitctl validate --file <config>after upgrade; existing sparse YAML withoutlookup:should validate unchanged. See Reference: lookup — Implicit default profile. - 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: cacheslookup.profiles.<name>.providers— ordered provider list (cachethenforwardis 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 cachetruncated_udp— opt-in TC=1 UDP caching (enabled+ requiredttl_secswhen enabled) — Reference: caches —truncated_udpon_hit.response_rules— whether Response rules run after a cache hit (runorskip; 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(passivedefault,activeopt-in) — Reference: caches —memorymax_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_udpTC=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 —
lookupprofile changes take effect for new queries via the normal snapshot swap; in-flight transactions keep the snapshot they started under. Cachemax_entriesupdates take effect on the live in-memory backend immediately when apply or reload succeeds (no restart; lowering the cap evicts entries). Other cache policy andmemory.shard_countrequire 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) andcache_instance(exact cache name) — Event export — Filters. - Dnstap
extra_fieldsmay includeanswer_sourceandcache_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
- Sparse / minimal configs — omitting
lookup:keeps forward-only behavior; no YAML change required unless you want caching. See Reference: lookup — Implicit default profile. - Enable caching — add
caches:and a cache provider entry beforeforwardinlookup.profiles.default(or your active profile). See DNS answer cache and Minimal cache-enabled config. - Custom response-hook metrics — review cache-hit behavior with default
on_hit.response_rules: run; setskiponly if response rules are intentionally miss-only. See User metrics — Cache hits and on_hit skip. - Dashboards — update trace and
conduit_phase_duration_secondsqueries that assumed top-levelroute/forward/wait_responsephases. See Tracing and Built-in metrics. - Validation — invalid cache references, invalid
on_hit.response_rules, and enabledtruncated_udpwithoutttl_secsfail atconduitctl validatetime; failed reload/apply retains the last-good snapshot. See Reference: caches — Validation and Reference: lookup — Validation.
Documentation (this release)
Canonical operator pages for Lookup, cache, metrics, and Rhai in this release:
- Architecture and packet path — Lookup
- DNS answer cache (guide)
- Reference: lookup and Reference: caches
- Built-in metrics — Lookup and cache
- Tracing and Event export
- Retries and transactions
- Transaction API (cache eligibility,
answer_source,cache_instance)
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).