Skip to main content

Response Processing

These executors adjust TTLs, address selection, interception results, and reverse-lookup data on existing or synthetic responses.

ttl

Purpose

Rewrites response TTL values.

Example Configuration

Full object form:

- tag: ttl_main
type: ttl
args:
# First force TTL to 300
fix: 300
# Then keep a lower bound
min: 60
# And cap the upper bound
max: 600

Configuration Details

fix

  • Type: duration; Required: no
  • Purpose: Force all TTLs to one fixed value.

min

  • Type: duration; Required: no
  • Purpose: Lower bound for TTLs.

max

  • Type: duration; Required: no
  • Purpose: Upper bound for TTLs.

quick setup

- exec: "ttl 300"
- exec: "ttl 60-600"

Behavior

  • Adjusts TTLs on the response path.
  • Can fix, clamp, or normalize TTLs.

Typical Uses

  • Stabilize answer retention
  • Avoid extreme upstream TTL values

ip_selector

Experimental Plugin (Introduced in v1.2.0)

ip_selector was first introduced in v1.2.0 and is currently in an experimental stage. Configuration fields, default values, probing strategies, and cache semantics may be adjusted in future releases based on real-world usage feedback. It is recommended to validate the plugin in non-critical environments first and monitor the release notes for related changes.

Purpose

Response IP selector. It only shapes A/AAAA records already present in response, using cached probe scores and bounded active probing to produce a stable order or a smaller answer set.

ip_selector does not race upstream DNS responses, suppress dual-stack families, or carry its own domain-rule system. Use forward / fallback for upstream racing, prefer_ipv4 / prefer_ipv6 for dual-stack policy, and sequence with matchers/providers for domain-specific routing.

Example Configuration

- tag: ip_select
type: ip_selector
args:
selection_mode: first_success
outbound: remote
probe_methods: ["tcp:443", "tcp:80"]
probe_stagger: 200
probe_timeout: 600
max_wait: 1000
top_n: 1
dnssec_policy: reorder_only
max_parallel_probes: 256
cache:
enabled: true
size: 4096
ttl: 3600
failure_ttl: 60

Recommended sequence placement:

- exec: $ip_select
- exec: $cache_main
- matches: !$has_resp
exec: $forward_main
- exec: accept

This lets cache store the original upstream response while ip_selector applies final response shaping on the return path.

Configuration Details

selection_mode

  • Type: string; Required: no; Default: first_success
  • Values: first_success, best_within_budget, background
  • Purpose:
    • first_success: use the first candidate IP with a successful probe.
    • best_within_budget: choose the lowest-latency IP within max_wait.
    • background: return immediately using the original or cached order while refreshing probe scores in the background.

probe_methods

  • Type: array<string> | string; Required: no; Default: ["tcp:443", "tcp:80"]
  • Values: tcp:<port>, ping, none
  • Purpose: Probe methods used to score answer IPs. ping is best-effort and fails open when unavailable.

outbound

  • Type: string; Required: no; Default: network.outbound.default
  • Purpose: References a network.outbound.profiles entry so TCP probes can reuse the profile proxy.
  • Notes:
    • Only tcp:<port> probes use proxy settings; ping always runs locally.
    • This field only uses the profile proxy. Target IPs are already known and are not resolved again.

socks5

  • Type: string; Required: no; Default: none
  • Purpose: Local SOCKS5 proxy override for TCP probes.
  • Notes:
    • Uses the same format as upstreams[].socks5.
    • When both outbound and socks5 are configured, local socks5 overrides the profile proxy.
    • Only tcp:<port> probes use SOCKS5; ping always runs locally.

probe_stagger

  • Type: integer; Unit: milliseconds; Required: no; Default: 200
  • Purpose: Delay between launching different probe methods.

probe_timeout

  • Type: integer; Unit: milliseconds; Required: no; Default: 600
  • Purpose: Timeout for one probe attempt.

max_wait

  • Type: integer; Unit: milliseconds; Required: no; Default: 1000
  • Purpose: Maximum per-response wait budget for selection.

top_n

  • Type: integer; Required: no; Default: 1
  • Purpose: Keep the first N selected addresses. Use 0 to reorder only without deleting records.

dnssec_policy

  • Type: string; Required: no; Default: reorder_only
  • Values: reorder_only, skip
  • Purpose: When the request carries DO bit or the response contains RRSIGs covering A/AAAA, the default policy only reorders records. skip leaves the response unchanged.

max_parallel_probes

  • Type: integer; Required: no; Default: 256
  • Purpose: Plugin-wide active probe concurrency limit.

cache

  • Type: object; Required: no
  • Fields:
    • enabled: enable probe score cache, default true.
    • size: target cache capacity, default 4096.
    • ttl: successful score retention in seconds, default 3600.
    • failure_ttl: failed score retention in seconds, default 60.

quick setup

- exec: "ip_selector"
- exec: "ip_selector best_within_budget tcp:443,tcp:80,ping"

ip_selector accepts only the OxiDNS-native names listed above and does not provide compatibility aliases.

Metrics

  • ip_selector_probe_total{method,result}
  • ip_selector_probe_latency_count{method}
  • ip_selector_probe_latency_sum_ms{method}
  • ip_selector_selected_total{source="probe|cache|fallback"}
  • ip_selector_cache_entries
  • ip_selector_dropped_probe_total{reason="parallel_limit|inflight"}

prefer_ipv4 / prefer_ipv6

Purpose

Biases dual-stack results toward one address family.

Example Configuration

- tag: probe_v4
type: forward
args:
upstreams:
- addr: "udp://1.1.1.1:53"

- tag: prefer_v4
type: prefer_ipv4
args:
# Optional: execute only for the internal preferred-QTYPE probe
probe_executor: probe_v4
# Cache whether the preferred family exists
cache: true
# Keep the preference cache for one hour
cache_ttl: 3600

Configuration Details

probe_executor

  • Type: string; Required: no; Default: unset
  • Purpose: Specifies the executor tag used only for the internal preferred-QTYPE probe. Use a plain tag without $.
  • May reference forward, sequence, or another executor that can independently produce a DNS response.
  • Inline quick setup is not supported. The dependency graph rejects missing or non-executor references, self-references, and indirect cycles at startup.
  • When omitted, the existing continuation probe mode is preserved, so existing configurations need no changes.

cache

  • Type: boolean; Required: no; Default: true
  • Purpose: Cache preference decisions.

cache_ttl

  • Type: integer; Required: no; Default: 3600
  • Unit: seconds
  • Purpose: Retention for the preference cache.

quick setup

- exec: "prefer_ipv4"
- exec: "prefer_ipv6"
Notes
  • Quick setup uses compatibility mode and the defaults: no probe_executor, cache: true, and cache_ttl: 3600.
  • Use the full plugin configuration to select a dedicated probe executor, disable the cache, or change its TTL.

Behavior

  • Helps make A and AAAA selection more stable when both families exist.
  • Preferred-family queries pass through normally and warm the preference cache when they return preferred-family answers.
  • Non-preferred-family queries are blocked immediately when the cache says the preferred family exists. On a cache miss, the original query continues through the outer downstream chain.
  • With probe_executor, the preferred-family probe runs only the selected executor. Without it, compatibility mode runs the outer downstream chain for the probe as well.
  • A preferred-family answer suppresses the original family. A non-truncated NOERROR without that answer or NXDOMAIN is a definitive miss and uses the original result. No response, execution error, timeout, truncation, or another failure RCODE is unknown, uses the original result, and does not write a negative cache entry.

Subquery and Context Isolation

  • The preferred-family query is an internal availability probe, not a replacement response for the client request. When the probe succeeds, the outer context keeps the original QTYPE and generates a NOERROR/NODATA response for that request.
  • On a cache miss, the original query and preferred-family probe use isolated DnsContext instances. The probe inherits ingress data and marks present before entering the selector, but its request, response, later marks, runtime extensions, execution path, and ExecStep are not merged into the outer context. After suppression, outer marks therefore remain as they were when the selector was entered.
  • If the preferred probe has no answer, fails, or times out and the original result is used, the complete original subquery result, including its marks, becomes the outer result.
  • This isolation keeps a live probe hit consistent with a direct cache hit: neither exposes preferred-probe state to outer post-processing plugins, and marks must not be used to infer internal probe outcomes.
  • Compatibility mode without probe_executor may execute the chain after the selector twice. Do not place executors with logging, counting, learning, scripts, webhooks, or other non-idempotent side effects in that probe path unless duplicate execution is explicitly acceptable.
  • Keep a dedicated probe executor free of side effects where possible. Cancellation can prevent pending work, but it cannot roll back external operations that already completed.
  • The selector cache is still keyed only by domain name. Disable cache when a probe sequence depends on the client, marks, randomness, rate limits, or other request-scoped state.

Typical Uses

  • Prefer the family that works better on a given network
  • Reduce dual-stack instability
Notes
  • Preference is not a substitute for fixing broken transport paths.
  • Prefer a dedicated forward or sequence through probe_executor so the probe does not repeat the outer downstream chain. Omitting it retains continuation compatibility.

black_hole

Purpose

Generates local interception responses for matched DNS queries, covering every qtype.

Example Configuration

- tag: sinkhole
type: black_hole
args:
mode: custom
ips:
# Returned for A queries
- "0.0.0.0"
# Returned for AAAA queries
- "::"
short_circuit: true

- tag: block_nxdomain
type: black_hole
args:
mode: nxdomain
short_circuit: true

Configuration Details

mode

  • Type: string; Required: no; Default: nxdomain when ips is empty, custom when ips is configured
  • Values:
    • nxdomain: returns NXDOMAIN and adds an SOA authority record for negative caching.
    • nodata: returns an empty NOERROR response and adds an SOA authority record for negative caching.
    • null: returns 0.0.0.0 for A, :: for AAAA, and NODATA for other qtypes.
    • custom: returns matching address-family values from ips for A / AAAA; missing families and other qtypes return NODATA.
    • refused: returns an empty REFUSED response.
  • Compatibility: legacy ips + short_circuit config does not need mode; it is treated as custom.

ips

  • Type: array; Required: no; Default: empty array
  • Purpose: Local synthetic addresses used by custom mode.
  • Operational impact:
    • IPv4 addresses are used only for A responses.
    • IPv6 addresses are used only for AAAA responses.
    • ips is allowed only for implicit or explicit custom mode.

short_circuit

  • Type: bool; Required: no; Default: false
  • Purpose: Stops the remaining executor chain after an interception response is generated.

quick setup

- exec: "black_hole"
- exec: "black_hole nxdomain short_circuit=true"
- exec: "black_hole nodata"
- exec: "black_hole null"
- exec: "black_hole custom 0.0.0.0 :: short_circuit=true"
# legacy form, equivalent to custom
- exec: "black_hole 0.0.0.0 ::"

Behavior

  • Plain black_hole returns NXDOMAIN.
  • nxdomain, nodata, and refused cover every qtype.
  • null and custom return addresses for A/AAAA and NODATA for other qtypes.
  • Requests without a question pass through unchanged.
  • By default it keeps running the remaining chain after a match; enable short_circuit to stop immediately.

Metrics

Exported through the global GET /api/metrics endpoint:

  • blackhole_block_total

Typical Uses

  • Blocking ads, trackers, and malicious domains
  • Returning NXDOMAIN / NODATA / REFUSED by policy
  • Returning null or custom placeholder addresses

drop_resp

Purpose

Drops the current response.

Example Configuration

- tag: clear_response
type: drop_resp
# No standalone args; execution simply clears the current response

Configuration Details

No standalone configuration fields.

quick setup

- exec: "drop_resp"

Behavior

  • Clears the existing response from context so later rules can continue.

Typical Uses

  • Discard unwanted intermediate results
  • Force a later branch to rebuild the answer

reverse_lookup

Purpose

Maintains a reverse IP-to-name cache and optionally handles PTR requests.

Example Configuration

- tag: reverse_lookup_main
type: reverse_lookup
args:
# Reverse-cache capacity
size: 65535
# Retention time for IP -> name mappings
ttl: 7200
# Answer PTR directly from the learned cache
handle_ptr: true

Configuration Details

size

  • Type: integer; Required: no
  • Purpose: Reverse cache capacity.

handle_ptr

  • Type: boolean; Required: no; Default: false
  • Purpose: Answer PTR requests from the reverse cache.

ttl

  • Type: duration; Required: no
  • Purpose: Reverse cache retention TTL.

Behavior

  • Learns from successful responses.
  • Can expose cached domain names for IP lookups and PTR handling.

Plugin API

  • GET /plugins/<tag>?ip=<ip_addr>

Typical Uses

  • Debugging resolved destinations
  • Supporting PTR-like introspection for learned answers
Notes
  • This is an auxiliary index, not a replacement for authoritative PTR data.