Skip to main content

Execution Chains and Control Flow

This page explains how the four plugin categories compose, including sequence rules, Quick Setup, and built-in control-flow semantics.

Responsibilities of the Four Plugin Categories

server

Purpose: Accept DNS requests and send them into an executor entry.

Traits:

  • Does not implement complex policy logic.
  • Usually configures a bind address, TLS parameters, and an entry executor.

executor

Purpose: Perform actions.

Typical actions include:

  • Query upstreams
  • Generate local answers
  • Read and write cache
  • Adjust TTL
  • Handle ECS
  • Run fallback and concurrent races
  • Perform observability and system integrations

matcher

Purpose: Evaluate conditions for use in sequence rules.

Typical match dimensions include:

  • Query name
  • Query type
  • Client IP
  • Response IP
  • Response code
  • Environment variables
  • Sampling outcome
  • Rate-limit state

provider

Purpose: Provide reusable datasets for matchers or other plugins.

Current main provider types:

  • domain_set
  • ip_set
  • geoip
  • geosite
  • adguard_rule

The sequence Orchestration Model

sequence is the policy hub of OxiDNS. Most non-trivial configs use it as the primary entry.

Example:

- tag: seq_main
type: sequence
args:
- matches:
- "$lan_clients"
- "qtype A,28"
exec: "$cache_main"
- matches: "!has_resp"
exec: "$forward_main"
- exec: "accept"

Each rule has two key fields:

  • matches
    • One matcher expression or an array of expressions.
    • When it is an array, every condition must be true for the rule to match.
  • exec
    • The action to execute when the rule matches.

Referencing Plugins and Quick Setup

Reference Existing Plugins

Use $tag to reference a plugin that has already been defined:

- exec: "$forward_main"
- matches:
- "$is_internal"
- "!has_resp"
exec: "$cache_main"

Quick Setup

If a sequence rule uses type + arguments instead of $tag, OxiDNS creates a temporary plugin on the fly.

Example:

- exec: "forward 1.1.1.1 8.8.8.8"
- matches: "qname domain:example.com"
exec: "ttl 300"

Common quick setup forms today:

  • matcher
    • _true
    • _false
    • qname ...
    • qtype ...
    • qclass ...
    • client_ip ...
    • resp_ip ...
    • ptr_ip ...
    • cname ...
    • mark ...
    • env ...
    • random ...
    • rate_limiter ...
    • rcode ...
    • has_resp
    • has_wanted_ans
    • string_exp ...
  • executor
    • forward ...
    • cache ...
    • ttl ...
    • prefer_ipv4
    • prefer_ipv6
    • sleep ...
    • debug_print ...
    • query_summary ...
    • metrics_collector ...
    • black_hole ...
    • drop_resp
    • ecs_handler ...
    • forward_edns0opt ...
    • ipset ...
    • nftset ...
    • upgrade ...
    • download ...
    • reload_provider ...
    • reload

Built-In sequence Control Flow

Besides calling plugins, sequence.args[].exec can also use built-in control flow:

accept

  • Ends the current sequence immediately.
  • This is an explicit early stop, so callers do not continue with later rules.
  • Does not build a response by itself.
  • Typical use:
    • Close out the pipeline after cache, hosts, or arbitrary has already written a response.
    • Stop later forward or side-effect stages once a branch has already made the decision.

return

  • Ends the current sequence immediately and returns control to the caller.
  • Does not build a response.
  • If the current sequence was entered via jump, the caller resumes at the rule after jump.
  • If the current sequence is the top-level entry, this acts like an early exit from the current rule chain.

reject [rcode]

  • Builds a DNS response from the current request immediately and ends the current sequence.
  • The default rcode is REFUSED, so plain reject means “reject this request”.
  • A decimal numeric code or English RCODE name can be provided explicitly; English names are case-insensitive. Common mappings and meanings are listed in the DNS Code Reference, for example:
    • reject 2 => SERVFAIL
    • reject SERVFAIL / reject servfail => SERVFAIL
    • reject 3 => NXDOMAIN
    • reject NXDOMAIN => NXDOMAIN
  • reject only supports base DNS RCODEs 0..15; extended RCODEs require an EDNS OPT and are not generated by this built-in action.
  • reject 0 returns a plain NOERROR response and does not add an SOA automatically.
  • Callers do not continue with later rules.
  • A typical use is returning a specific error code directly, for example:
- matches: "qtype HTTPS"
exec: "reject NXDOMAIN"

mark ...

  • Appends one or more unsigned integer marks to DnsContext.marks while preserving existing values.
  • Supported forms:
    • mark 1
    • mark 1 2 3
    • mark 1,2,3
  • Continues to the next rule in the current sequence.
  • Does not build a response and does not terminate the current sequence.

set_mark ...

  • Replaces the complete DnsContext.marks collection with one or more unsigned integer marks; existing values are not preserved.
  • It accepts the same argument forms as mark:
    • set_mark 1
    • set_mark 1 2 3
    • set_mark 1,2,3
  • Duplicate values are deduplicated. Marks form a set, so configuration order has no runtime meaning.
  • At least one value is required. Missing arguments, negative values, non-numeric values, and values outside the u32 range cause sequence initialization to fail.
  • Continues to the next rule without building a response or terminating the current sequence.
  • set_mark replaces the entire collection; it does not distinguish classification marks from supplemental marks. Values that must be retained need to be included explicitly.

For example:

- exec: "mark 1,4"
- exec: "set_mark 2,3"
- exec: "mark 5"

The final marks are 2,3,5: set_mark removes the existing 1,4, then the following mark appends 5.

jump seq_tag

  • Calls another sequence; conceptually this behaves like a subroutine call.
  • The parameter must be the target sequence tag without a leading $.
  • If the called sequence:
    • reaches its tail normally, the current sequence resumes at the rule after jump.
    • executes return, the current sequence also resumes at the rule after jump.
    • executes accept, reject, or another operation that returns Stop, the current sequence stops as well.

goto seq_tag

  • Transfers control to another sequence; conceptually this behaves like a one-way jump.
  • The parameter must be the target sequence tag without a leading $.
  • The current sequence never resumes after goto:
    • If the target sequence reaches its tail, control does not return to the rules after goto.
    • If the target sequence executes return, that return is propagated outward and still does not return to the rules after goto.
    • If the target sequence executes accept, reject, or another Stop, that result propagates outward directly.
  • This is useful when ownership of the request should be handed off permanently to another policy branch.

Example:

- matches: "$rate_ok"
exec: "mark 100"
- matches: "!$rate_ok"
exec: "reject 2"

Example showing the difference between jump and goto:

- tag: child_seq
type: sequence
args:
- exec: "set_mark 2,20"
- exec: "return"

- tag: parent_jump
type: sequence
args:
- exec: "mark 1"
- exec: "jump child_seq"
- exec: "mark 3"

- tag: parent_goto
type: sequence
args:
- exec: "mark 1"
- exec: "goto child_seq"
- exec: "mark 3"
  • parent_jump ends with marks 2,3,20: the child and caller share the same DnsContext, so set_mark replaces the caller's earlier 1, and the parent appends 3 after the child returns.
  • parent_goto ends with marks 2,20 because set_mark also replaces 1 and execution never returns after goto.