Skip to content

Transaction API (txn)

Rhai for rules (Rule Rhai) scripts receive a sandboxed txn object — the per-query policy surface on the current transaction. Methods set pools, tags, drop/retry intent, egress overrides, and read question/response metadata. They do not edit DNS wire bytes.

This page covers txn only. Lookups, metrics, logging, and runtime reads are separate host surfaces — see Host API overview.

For which hook each API allows, see Hooks and phases and Host API overview — How to read method reference pages.


Client and listener

Read-only facts about how the query arrived — client socket, transport, and listener bind label. Use for per-client or per-listener policy without high-cardinality user metrics labels.

Methods: txn.client_addr() · txn.client_ip() · txn.client_port() · txn.client_protocol() · txn.listener()

txn.client_addr()

Request + response hook · no args · returns string

Full client socket address (ip:port).

txn.client_ip()

Request + response hook · no args · returns string

Client IP address only (no port).

txn.client_port()

Request + response hook · no args · returns i64

Client UDP/TCP source port.

txn.client_protocol()

Request + response hook · no args · returns string

Transport the client used: udp or tcp.

txn.listener()

Request + response hook · no args · returns string

Configured listener bind address label (empty when unset).


Egress

When Conduit sends a query to an upstream backend, it binds a local address on your host — that is egress. You declare allowed addresses in forward.sources_v4 / forward.sources_v6 and, optionally, per-pool sources_v4 / sources_v6.

Use Rhai when egress should depend on the query or on what happened upstream:

Goal Request hook Response hook
Same local IP for every forward on this query txn.set_source_v4(addr) or txn.set_source_v6(addr) Not allowed — egress for the current attempt is already fixed
A different local IP only on the next retry txn.set_retry_source_v4(addr) (stash for later) txn.set_retry_source_v4(addr) + txn.request_retry() when upstream failed or timed out

If the script does not set a source, Conduit round-robins among the sources configured for the selected pool.

Allowed addresses: the IP you pass must be listed in forward.sources_* or the pool’s sources_* for that address family. If it is not, Conduit still answers the client — it ignores the override and picks another configured source (same as built-in set_source_v4 in YAML). See Dual-stack forwarding — Choosing an egress source.

Pool and egress are separate: txn.set_pool("premium") does not change egress by itself. Set both when you need a specific pool and a specific bind address. On one rule, list built-in set_pool before set_source_* so Forward checks the address against the pool you intended.

Example — egress from a lookup table (request hook):

let egress = lookup("egress_map", txn.question().qname);
if egress != "" {
    txn.set_source_v4(egress);
}

Example — different egress only on retry (response hook after a slow upstream):

if txn.last_forward_ms() > 800 {
    txn.set_retry_source_v4("10.0.0.9");
    txn.request_retry();
}

Retry-specific sources apply to one retry forward, then Conduit returns to the standing source from the request hook (if any). Full lifecycle: Source selection.

Methods: txn.set_source_v4 · txn.set_source_v6 · txn.set_retry_source_v4 · txn.set_retry_source_v6 · txn.clear_retry_source_v4 · txn.clear_retry_source_v6

txn.set_source_v4

Request hook only · addr: string (IPv4) · no return; script error on response hook or invalid address

Sets the standing local IPv4 bind for every forward attempt on this transaction.


txn.set_source_v6

Request hook only · addr: string (IPv6) · no return; script error on response hook or invalid address

Sets the standing local IPv6 bind for every forward attempt on this transaction.


txn.set_retry_source_v4

Request + response hook · addr: string (IPv4) · one-shot retry egress; pair with request_retry

Stashes a one-shot IPv4 egress override consumed on the next retry forward only.


txn.set_retry_source_v6

Request + response hook · addr: string (IPv6) · one-shot retry egress; pair with request_retry

Stashes a one-shot IPv6 egress override consumed on the next retry forward only.


txn.clear_retry_source_v4

Request + response hook · no args · clears stashed retry IPv4 override

Clears a stashed retry IPv4 egress override without affecting standing overrides.


txn.clear_retry_source_v6

Request + response hook · no args · clears stashed retry IPv6 override

Clears a stashed retry IPv6 egress override without affecting standing overrides.


Timing and clocks

Wall-clock time and forward attempt count on the current transaction. Custom policy counters live on the separate metrics scope object — see User metrics.

Methods: txn.elapsed_ms() · txn.get_attempt_count() · txn.last_forward_ms() · txn.now_unix() · txn.utc_hour() · txn.utc_weekday()

txn.elapsed_ms

Request + response hook · no args · returns i64 (ms since transaction start)

Returns milliseconds since the transaction started.


txn.last_forward_ms

Request + response hook · no args · returns i64 (latest upstream RTT; 0 on request hook)

Returns upstream RTT in ms for the latest forward attempt (0 when no forward ran, including cache hits).


txn.get_attempt_count

Request + response hook · no args · returns i64 (forward attempt count at hook entry)

Returns how many forward attempts have started when the hook runs.


txn.now_unix()

Request + response hook · no args · returns i64

UTC Unix timestamp (seconds) when the transaction started.

txn.utc_hour()

Request + response hook · no args · returns i64 (0–23 UTC)

Hour-of-day in UTC when the transaction started.

txn.utc_weekday()

Request + response hook · no args · returns i64 (1–7 UTC)

ISO weekday in UTC when the transaction started (1 = Monday, 7 = Sunday).


Introspection

Read-only identifiers for correlating script behavior with tracing, logs, and config reloads.

Methods: txn.txn_id() · txn.config_generation() · txn.rule_name()

txn.txn_id()

Request + response hook · no args · returns i64

Internal transaction id (same value as txn_id in debug logs and conduitctl trace).

txn.config_generation()

Request + response hook · no args · returns i64

Config snapshot generation active when this transaction started.


Answer provenance

Inspect where Lookup got this attempt's wire answer — cache vs forward — and which named cache, pool, or backend produced it. On the request hook, optionally bypass the cache for this query. Distinct from Rhai lookup(table, key) — see Glossary — Lookup vs lookup(table, key).

On the response hook, branch on source then read the matching identity:

if txn.answer_source() == "cache" {
    log.info(`served from cache=${txn.cache_instance()}`);
} else if txn.answer_source() == "forward" {
    log.info(`served from pool=${txn.selected_pool()} backend=${txn.selected_backend_name()}`);
}

txn.selected_pool() is also routing intent on the request hook (see Routing). On a pure cache hit, backend fields are usually empty because forward did not run.

Methods: txn.answer_source() · txn.cache_instance() · txn.selected_backend() · txn.selected_backend_name() · txn.selected_pool() · txn.set_cache_lookup_eligible(bool)

txn.answer_source()

Response hook · no args · returns string

Which Lookup provider produced this attempt's wire answer — cache, forward, or empty before an answer exists.

txn.cache_instance()

Response hook · no args · returns string

Named cache instance on cache hits; empty otherwise.

txn.selected_backend()

Request + response hook · no args · returns string

Upstream backend socket address for the current forward attempt (empty when unset).

txn.selected_backend_name()

Request + response hook · no args · returns string

Upstream backend logical label for the current forward attempt: the configured backend name when set, otherwise the socket address. This is the same name-when-set identity used in metrics, logs, traces, and event-sink backend filters. Empty when no backend is selected.

txn.selected_pool()

Request + response hook · no args · returns string

Pool name selected for the current forward attempt (empty when unset).

txn.set_cache_lookup_eligible

Request hook only · eligible: bool · no return

When false, the cache provider bypasses this query for the current Lookup attempt.


Outcomes

Drop, retry, and response RCODE metadata on the transaction. Soft calls (drop_query, request_retry) set intent that Conduit resolves after the rest of the script (and any later built-in actions on the same rule) finish. Hard calls (drop_query_now, request_retry_now) stop the script immediately — no further Rhai runs on that rule pass. See Outcome at end of rule for precedence (drop beats retry; soft drop blocks hard retry unless clear_drop ran earlier on the rule).

Methods: txn.clear_drop · txn.clear_retry · txn.drop_query · txn.drop_query_now · txn.request_retry · txn.request_retry_now · txn.set_rcode

txn.drop_query

Request + response hook · no args · no return

Sets soft-drop intent — resolved at end of rule; later script lines still run.


txn.drop_query_now

Request + response hook · no args · no return

Hard drop — stops the script immediately; query drops with no DNS reply.


txn.clear_drop

Request + response hook · no args · no return

Clears soft-drop intent set earlier on this rule pass.


txn.request_retry

Response hook only · no args · no return; no effect on request hook

Soft retry — resolved at end of rule; re-enters Lookup when still set.


txn.request_retry_now

Response hook only · no args · no return; no effect on request hook

Hard retry — stops the script immediately and re-enters Lookup (unless soft drop blocks).


txn.clear_retry

Response hook only · no args · no return; no effect on request hook

Clears soft-retry intent set earlier on this rule pass.


txn.set_rcode

Request + response hook · rcode: Rcode or string · no return

Sets response RCODE metadata on the transaction before Send.


Query and response

Read-only access to the client question and, on the response hook, the upstream outcome metadata Conduit recorded after Wait for response. These APIs expose qname, typed DNS wire enums (RecordType, QueryClass, DnsOpcode, EdnsOptionCode), message ID, and Rcode — not full answer records or wire bytes.

On the request hook, only the question is meaningful — upstream has not answered yet. On the response hook, the question is unchanged and txn.response() / txn.response_rcode() reflect the outcome of the current forward attempt (including timeout or pool exhaustion, which Conduit typically records as SERVFAIL). See Hooks and phases — phase guards.

Methods: txn.question() · txn.response() · txn.response_rcode() · txn.response_truncated() · txn.response_answer_count() · Types: RecordType · Rcode · QueryClass · DnsOpcode · EdnsOptionCode

RecordType

Rule Rhai global · static module · DNS QTYPE enum

Named constants and TYPE{n} aliases for every IANA-assigned RR type (plus Conduit-specific ANAME); use for qtype comparisons and RecordType::from_number(n) for arbitrary wire numbers.


Rcode

Rule Rhai global · static module · DNS RCODE enum

Response codes for txn.response().rcode, txn.response_rcode(), and txn.set_rcode(...) — every IANA-assigned RCODE with a name (SERVFAIL, NXDOMAIN, DSOTYPENI, …) and matching RCODE{n} alias.


QueryClass

Rule Rhai global · static module · DNS QCLASS enum

Query class on txn.question().qclass — all IANA scalar class assignments (IN, CH, HS, NONE, ANY, …), plus CLASS{n} aliases.


DnsOpcode

Rule Rhai global · static module · DNS opcode enum

Message opcode from the query header — txn.question().opcode. IANA-assigned opcodes include QUERY, STATUS, NOTIFY, UPDATE, obsolete IQUERY, and DNS_STATEFUL_OPERATIONS (alias DSO), each with an OPCODE{n} alias.


EdnsOptionCode

Rule Rhai global · static module · EDNS(0) option code enum

Option codes present on the client OPT record — txn.question().edns_options is an array of EdnsOptionCode values (empty when the query has no EDNS). Named constants cover every IANA-assigned EDNS option code, including COOKIE, EDNS_CLIENT_SUBNET (alias CLIENT_SUBNET), PADDING, EXTENDED_DNS_ERROR (alias EDE), REPORT_CHANNEL, and Cisco UMBRELLA_IDENT (alias UMBRELLA), each with a CODE{n} alias.


txn.question()

Request + response hook · no args · returns map

Returns a map of client question fields: qname, qtype, qclass, opcode, edns_options, and DNS message id (typed enums where noted below).


txn.response()

Response hook only · no args · returns map; script error on request hook

Returns upstream outcome metadata for the current forward attempt (rcode, routing path, optional wire-derived counts, question fields).

txn.response_truncated()

Response hook · no args · returns bool

Whether upstream response had TC=1 (requires compile-time wire-meta gating).

txn.response_answer_count()

Response hook · no args · returns i64

Answer section count from the upstream wire answer, or -1 when wire metadata was not parsed.


txn.response_rcode()

Request + response hook · no args · returns Rcode or ()

Returns upstream RCODE on the response hook; () on the request hook when no rcode is available.


Routing

set_pool and set_retry_pool choose which pool Route uses. clear_pool removes a standing pool choice so Route picks the configured default pool (the pool named default, or the first pool in your config). See Pool selection lifecycle.

Methods: txn.clear_pool · txn.clear_retry_pool · txn.set_pool · txn.set_retry_pool

txn.clear_pool

Request + response hook · no args · no return

Clears standing pool choice — next Route uses the configured default pool.

txn.clear_retry_pool

Request + response hook · no args · clears retry_pool stash only

Clears the retry_pool stash from set_retry_pool without clearing soft-retry intent.


txn.set_pool

Request + response hook · name: string (pool) · no return

Sets selected_pool for Route — first forward and later attempts when retry_pool is absent.


txn.set_retry_pool

Request + response hook · name: string (pool) · stashes pool for next retry Route

Stashes a pool name consumed once on the next retry Route; does not trigger retry alone.


Sampling

Deterministic sampling and cadence gates for scripts — mirror YAML selectors on Sampling and cadence. Percentage methods (sample_percent*) use a 0..100 scale with optional key salt; cadence methods (every_nth_*) match every Nth query on this worker or process-wide. Use to gate expensive logic, set audit tags, or combine with declarative rules.

YAML selector parity

YAML selector / field Rule Rhai equivalent
sample_percent (no salt) txn.sample_percent(percent)
sample_percent + key txn.sample_percent(percent, key)
sample_percent + key_from: qname txn.sample_percent_for_qname(percent) or txn.sample_percent(percent, txn.question().qname)
sample_percent + key_from: rule_name txn.sample_percent_for_rule(percent) or txn.sample_percent(percent, txn.rule_name())
every_nth_worker txn.every_nth_worker(n)
every_nth_global txn.every_nth_global(n)

key_from: sink_name applies to event sink filters only — not exposed on Rule Rhai. Prefer a YAML selector on the rule when you only need coarse gating without running script logic on every match.

Methods: txn.sample_percent(percent) · txn.sample_percent(percent, key) · txn.sample_percent_for_qname(percent) · txn.sample_percent_for_rule(percent) · txn.every_nth_worker(n) · txn.every_nth_global(n) · txn.rule_name()

txn.sample_percent

Request + response hook · percent: float · optional key: string · returns bool

Returns whether this transaction falls in the ~percent% sample; optional key selects an independent bucket.


txn.sample_percent_for_qname

Request + response hook · percent: float · returns bool

~percent% sample with per-qname salt — matches YAML key_from: qname.


txn.sample_percent_for_rule

Request + response hook · percent: float · returns bool

~percent% sample salted with this rule's configured name — matches YAML key_from: rule_name.


txn.every_nth_worker

Request + response hook · n: integer · returns bool

true when this worker's transaction id is divisible by n — matches YAML every_nth_worker.


txn.every_nth_global

Request + response hook · n: integer · returns bool

true when the process-wide query index is divisible by n — matches YAML every_nth_global.


txn.rule_name

Request + response hook · no args · returns string

Returns the configured name of the rule whose rhai action is running this script.


Tags

Tags are small key/value labels you attach to a transaction for the rest of its life — including across retries and on the response hook. They do not change routing by themselves; they let later policy, event export, and other scripts branch on how the query was classified.

Goal Typical hook API
Classify the query before upstream Request txn.set_tag("tier", "vip") or txn.set_tag("audit", true)
Act on classification + upstream outcome Response txn.has_tag("suspicious") then metrics, retry, or drop
Gate dnstap / event sinks Request (set tag) Sink tag_required in config — see Event export — Filters
Remove a label Either txn.clear_tag("temporary")

Tags set on the request hook stay on the transaction when the response hook runs (the request hook does not run again on retry). Pair request set_tag with response has_tag — see Hooks and phases — Pairing scripts.

Example — request classify, response act:

// request hook
if txn.question().qname == "login.suspicious.example." {
    txn.set_tag("suspicious", true);
}

// response hook
if txn.has_tag("suspicious") && txn.last_forward_ms() > 500 {
    metrics.inc("slow_login", 1);
}

Boolean tags use true / false. String tags store text (txn.set_tag("tier", "vip")). YAML built-in set_tag on the same rule runs before Rhai when listed above the script — the script can read those tags with has_tag and add more.

Methods: txn.clear_tag(key) · txn.has_tag(key) · txn.set_tag(key, value)

txn.clear_tag

Request + response hook · key: string · no return

Removes a tag key from the transaction tag map.


txn.has_tag

Request + response hook · key: string · returns bool

Returns whether a tag key is present on the transaction.


txn.set_tag

Request + response hook · key: string, value: bool or string · no return

Sets a bool or string tag on the transaction for rules, metrics, and export.