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_setip_setgeoipgeositeadguard_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_falseqname ...qtype ...qclass ...client_ip ...resp_ip ...ptr_ip ...cname ...mark ...env ...random ...rate_limiter ...rcode ...has_resphas_wanted_ansstring_exp ...
- executor
forward ...cache ...ttl ...prefer_ipv4prefer_ipv6sleep ...debug_print ...query_summary ...metrics_collector ...black_hole ...drop_respecs_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
sequenceimmediately. - 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, orarbitraryhas already written a response. - Stop later
forwardor side-effect stages once a branch has already made the decision.
- Close out the pipeline after
return
- Ends the current
sequenceimmediately and returns control to the caller. - Does not build a response.
- If the current
sequencewas entered viajump, the caller resumes at the rule afterjump. - If the current
sequenceis 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
rcodeisREFUSED, so plainrejectmeans “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=>SERVFAILreject SERVFAIL/reject servfail=>SERVFAILreject 3=>NXDOMAINreject NXDOMAIN=>NXDOMAIN
rejectonly supports base DNS RCODEs0..15; extended RCODEs require an EDNS OPT and are not generated by this built-in action.reject 0returns a plainNOERRORresponse 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.markswhile preserving existing values. - Supported forms:
mark 1mark 1 2 3mark 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.markscollection with one or more unsigned integer marks; existing values are not preserved. - It accepts the same argument forms as
mark:set_mark 1set_mark 1 2 3set_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
u32range cause sequence initialization to fail. - Continues to the next rule without building a response or terminating the current
sequence. set_markreplaces 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
sequencetag without a leading$. - If the called
sequence:- reaches its tail normally, the current
sequenceresumes at the rule afterjump. - executes
return, the currentsequencealso resumes at the rule afterjump. - executes
accept,reject, or another operation that returnsStop, the currentsequencestops as well.
- reaches its tail normally, the current
goto seq_tag
- Transfers control to another
sequence; conceptually this behaves like a one-way jump. - The parameter must be the target
sequencetag without a leading$. - The current
sequencenever resumes aftergoto:- If the target
sequencereaches its tail, control does not return to the rules aftergoto. - If the target
sequenceexecutesreturn, thatreturnis propagated outward and still does not return to the rules aftergoto. - If the target
sequenceexecutesaccept,reject, or anotherStop, that result propagates outward directly.
- If the target
- 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_jumpends with marks2,3,20: the child and caller share the sameDnsContext, soset_markreplaces the caller's earlier1, and the parent appends3after the child returns.parent_gotoends with marks2,20becauseset_markalso replaces1and execution never returns aftergoto.