Executor Plugins
Executors are the core action layer in ForgeDNS. They can read or write requests, set responses, query upstreams, cache results, perform fallback logic, emit logs, or trigger system integrations.
When reading this chapter, keep two questions in mind:
- Does this plugin act only in the forward stage, or can it also rewrite results on the return path?
- Is it part of the main resolution path, or an observability and side-effect plugin?
sequence
Purpose
Orchestrates matchers and executors into a pipeline. This is the most common entry executor.
Example Configuration
- tag: seq_main
type: sequence
args:
# Try cache first
- exec: "$cache_main"
# Stop immediately on cache hit
- matches: "has_resp"
exec: "accept"
# Arrays of matches are AND-ed together
# Examples prefer quick-setup matcher expressions directly
- matches:
- "client_ip $lan_ip_set"
- "qname $local_domains"
exec: "$hosts_main"
# Rules may also execute unconditionally
- exec: "$metrics_main"
# Only forward when no response exists yet
- matches: "!has_resp"
exec: "$forward_main"
# Normalize TTL after a response is available
- matches: "has_resp"
exec: "$ttl_main"
Configuration Details
args
- Type:
array; Required: yes; Default: none - Purpose: Defines the rule chain.
- Runtime impact:
- Rules execute in order.
- Initialization fails when the array is empty.
args[].matches
- Type:
stringorarray - Required: no
- Purpose: Match condition for the current rule.
- Runtime impact:
- Multiple conditions are combined with logical AND.
- Omitted means the rule has no precondition.
args[].exec
- Type:
string; Required: no; Default: none - Purpose: Action to run when the rule matches.
- Supports:
- plugin references
- quick setup expressions
- built-in control flow
Behavior
- Rules run sequentially.
- A rule with multiple
matchesrequires all of them to be true. - Other
sequenceinstances can be called withjumporgoto.
Built-In Control Flow
Besides plugin calls, sequence.args[].exec can also use built-in control flow:
accept
- Ends the current
sequenceimmediately. - This is an explicit early stop, so outer callers do not continue with later rules.
- Does not build a response by itself; it is usually used after an earlier stage has already produced one.
return
- Ends the current
sequenceimmediately and gives control back to the caller. - Does not build a response.
- If the current
sequencewas entered byjump, the caller continues with the next rule.
reject [rcode]
- Builds a response immediately and ends the current
sequence. - The default
rcodeisREFUSED. - Only decimal numeric rcodes are accepted, for example
reject 2orreject 3. - Stops later rules from running.
mark ...
- Inserts one or more integer marks, then continues to the next rule in the current
sequence. - Supports
mark 1,mark 1 2 3, andmark 1,2,3.
jump seq_tag
- Calls another
sequence; conceptually this is a subroutine call. - The parameter must be the target
sequencetag without$. - If the target
sequencereaches its tail or executesreturn, the currentsequenceresumes with the next rule. - If the target
sequenceexecutesaccept,reject, or anotherStop, the currentsequencestops too.
goto seq_tag
- Transfers control one-way to another
sequence. - The parameter must be the target
sequencetag without$. - Once
gotoruns, the currentsequencenever resumes at later rules. - If the target
sequenceexecutesreturn, thatreturnis propagated outward.
Typical Uses
- One readable top-level entry.
- Split cache, local answers, forwarding, and integrations into understandable policy layers.
- Build complex branches with marks and matchers.
Notes
- Referenced plugins must already exist.
- A
sequenceneeds at least one rule.
forward
Purpose
Sends DNS queries to upstreams.
Example Configuration
- tag: forward_main
type: forward
args:
# Effective fan-out in multi-upstream mode
concurrent: 3
upstreams:
# Simplest UDP upstream
- tag: "cf_udp"
addr: "udp://1.1.1.1:53"
timeout: 3s
# Domain-based DoH upstream showing bootstrap, pooling, HTTP/3,
# and Linux socket options
- tag: "doh_main"
addr: "https://resolver.example/dns-query"
bootstrap: "8.8.8.8:53"
bootstrap_version: 4
port: 443
idle_timeout: 30
max_conns: 256
timeout: 5s
enable_pipeline: false
enable_http3: true
so_mark: 100
bind_to_device: "eth0"
# DoT upstream showing dial_addr, SOCKS5, TLS verification, and pipelining
- tag: "dot_backup"
addr: "tls://dns.example:853"
dial_addr: "203.0.113.53"
socks5: "user:pass@127.0.0.1:1080"
idle_timeout: 60
max_conns: 128
insecure_skip_verify: false
timeout: 4s
enable_pipeline: true
Configuration Details
concurrent
- Type:
integer; Required: no; Default:1 - Runtime range is clamped to
1..=3. - Purpose: Number of concurrent upstream fan-out requests.
upstreams
- Type:
array; Required: yes; Default: none - Purpose: Defines one or more upstream targets.
- Runtime impact:
- One upstream means normal forwarding.
- More than one enables racing behavior.
short_circuit
- Type:
boolean; Required: no; Default:false - Purpose: Stop the executor chain after a successful upstream response.
- Notes:
- When disabled,
forwardstill populatesresponse, but later executors can continue processing it. - When enabled, a successful upstream result immediately ends the remaining executor chain.
- When disabled,
upstreams[].addr
- Type:
string; Required: yes - Purpose: Upstream address, protocol, and target.
- Supports:
udp://tcp://tcp+pipeline://tls://tls+pipeline://quic:///doq://https:///doh://h3://
- Notes:
- No scheme means UDP.
- DoH addresses should include the full request path.
upstreams[].tag
- Type:
string; Required: no - Purpose: Per-upstream log label.
upstreams[].dial_addr
- Type:
ip; Required: no - Purpose: Actual connection IP while preserving the hostname from
addrfor SNI, Host, and certificate validation.
upstreams[].port
- Type:
integer; Required: no - Purpose: Override the protocol default port.
upstreams[].bootstrap
- Type:
string; Required: no - Purpose: Bootstrap resolver for domain-based upstreams.
upstreams[].bootstrap_version
- Type:
integer; Required: no - Allowed values:
4,6 - Purpose: Force bootstrap resolution toward IPv4 or IPv6.
upstreams[].socks5
- Type:
string; Required: no - Purpose: SOCKS5 proxy for upstream connections.
- Supports:
host:portusername:password@host:port
upstreams[].idle_timeout
- Type:
integer; Required: no - Unit: seconds
- Purpose: Idle pooled connection lifetime.
upstreams[].max_conns
- Type:
integer; Required: no - Purpose: Maximum pooled connections.
upstreams[].insecure_skip_verify
- Type:
boolean; Required: no; Default:false - Purpose: Skip TLS certificate validation.
upstreams[].timeout
- Type:
duration; Required: no; Default:5s - Purpose: Per-upstream query timeout.
upstreams[].enable_pipeline
- Type:
boolean; Required: no - Purpose: Enable pipelining for TCP or DoT.
upstreams[].enable_http3
- Type:
boolean; Required: no; Default:false - Purpose: Use HTTP/3 for DoH.
upstreams[].so_mark
- Type:
integer; Required: no - Purpose: Linux
SO_MARK.
upstreams[].bind_to_device
- Type:
string; Required: no - Purpose: Linux
SO_BINDTODEVICE.
quick setup
- exec: "forward 1.1.1.1"
- exec: "forward 1.1.1.1 8.8.8.8"
- exec: "forward 1.1.1.1 short_circuit=true"
Quick setup supports the trailing flag forms short_circuit, short_circuit=true, and short_circuit=false.
Use the full plugin form for bootstrap, proxy, HTTP/3, pool settings, and other advanced options.
Behavior
- Single-upstream mode queries the configured upstream directly.
- Multi-upstream mode races queries from a randomized starting point and keeps the first successful answer.
- When combined with
prefer_ipv4orprefer_ipv6, it can run preferred-family probes. - With
short_circuitenabled, a successful upstream response stops the remaining executor chain immediately.
Typical Uses
- Standard forwarding
- Multi-upstream resilience
- Mixed-protocol upstream groups
Notes
- More upstreams are not automatically better. Keep upstream groups semantically clear.
cache
Purpose
Provides TTL-aware response caching with negative cache support and persistence.
Example Configuration
- tag: cache_main
type: cache
args:
# Maximum number of cached entries
size: 8192
# Stop the chain immediately when cache returns a response
short_circuit: true
# Serve stale responses briefly after original TTL expiry and refresh lazily
lazy_cache_ttl: 120
# Cache NXDOMAIN / NODATA responses too
cache_negative: true
# Upper bound for negative-cache TTL
max_negative_ttl: 300
# Fallback TTL when a negative response has no SOA
negative_ttl_without_soa: 60
# Upper bound for positive TTL
max_positive_ttl: 600
# Exclude ECS from the cache key for a better hit ratio
ecs_in_key: false
# Persist cache contents to disk
dump_file: "./dns_cache.dump"
# Periodic dump interval in seconds
dump_interval: 600
Configuration Details
size
- Type:
integer; Required: no; Default: implementation default - Purpose: Cache capacity.
lazy_cache_ttl
- Type:
duration; Required: no - Purpose: Enable lazy cache for successful positive responses.
- Behavior:
- The original response TTL still defines the fresh-hit window.
lazy_cache_ttldefines the stale reply TTL and keeps entries briefly available after freshness expires.- Stale hits trigger an asynchronous background refresh.
- This setting does not shorten the original fresh TTL.
dump_file
- Type:
string; Required: no - Purpose: Persistence dump file path.
dump_interval
- Type:
duration; Required: no - Purpose: Periodic dump interval.
short_circuit
- Type:
boolean; Required: no; Default:false - Purpose: Stop the chain when the cache produces a response.
- Notes:
- When set to
false, later executors still run even if cache has already populatedresponse. - If you want to skip later
forwardstages on cache hits, handle it explicitly insequence, for example withhas_resporaccept.
- When set to
cache_negative
- Type:
boolean; Required: no; Default:false - Purpose: Cache negative responses.
max_negative_ttl
- Type:
duration; Required: no - Purpose: Cap negative-cache TTL.
negative_ttl_without_soa
- Type:
duration; Required: no - Purpose: Fallback TTL for negative answers without SOA.
max_positive_ttl
- Type:
duration; Required: no - Purpose: Cap positive-cache TTL.
ecs_in_key
- Type:
boolean; Required: no - Purpose: Include ECS information in the cache key.
quick setup
- exec: "cache"
- exec: "cache short_circuit=true"
- With no arguments, quick setup uses the default cache configuration.
- It currently supports the trailing flag forms
short_circuit,short_circuit=true, andshort_circuit=false. - Use the full plugin form for other advanced settings.
Behavior
- Reads from cache on the forward path and writes responses on the return path.
- Respects DNS TTL semantics instead of using a fixed timeout.
- Can persist cache contents through dump and load operations.
Plugin API
GET /plugins/<cache_tag>/flushGET /plugins/<cache_tag>/dumpPOST /plugins/<cache_tag>/load_dump
Typical Uses
- Lower upstream latency
- Protect upstreams from repeated identical traffic
- Preserve warm cache state across restarts
Notes
- Decide carefully whether ECS should be part of the cache key. It improves correctness for ECS-aware policies but reduces hit ratio.
fallback
Purpose
Runs a primary executor first and falls back to a secondary executor when the primary is too slow or fails.
Example Configuration
- tag: fallback_main
type: fallback
args:
# Preferred path
primary: "forward_fast"
# Backup path
secondary: "forward_stable"
# Let the backup take over after 200 ms
threshold: 200
# Keep the backup running in parallel for lower tail latency
always_standby: true
Configuration Details
primary
- Type:
string; Required: yes - Purpose: Primary executor tag.
secondary
- Type:
string; Required: yes - Purpose: Secondary executor tag.
threshold
- Type:
integer; Required: no - Unit: milliseconds
- Purpose: Delay before the secondary is allowed to take over.
always_standby
- Type:
boolean; Required: no; Default:false - Purpose: Keep the secondary in standby for all requests rather than only after the threshold condition.
short_circuit
- Type:
boolean; Required: no; Default:false - Purpose: Stop the executor chain after fallback selects the winning response.
Behavior
- Provides controlled degradation instead of unconditional double-querying.
- Useful when one path is usually faster but another path is more complete or stable.
- With
short_circuitenabled, the winning branch writes its response and then immediately stops the remaining executor chain.
Typical Uses
- Low-latency primary plus stable backup
- Tail-latency protection
Notes
- A too-aggressive threshold can turn the secondary into a routine dependency.
hosts
Purpose
Returns local static answers using host-style entries.
Example Configuration
- tag: hosts_main
type: hosts
args:
entries:
# Unprefixed rules default to full:
- "router.local 192.168.1.1"
# Exact-name rule
- "full:gateway.local 192.168.1.2"
# Suffix rule returning both IPv4 and IPv6
- "domain:svc.local 10.0.0.10 fd00::10"
# Keyword rule
- "keyword:nas 192.168.1.20"
# Regex rule
- "regexp:^api[0-9]+\\.corp\\.local$ 10.10.0.5"
files:
# Merge more hosts rules from files
- "/etc/forgedns/hosts.txt"
short_circuit: true
Configuration Details
entries
- Type:
array; Required: no; Default: empty array - Purpose: Defines inline hosts rules.
- Rule format:
<domain_rule> <ip1> <ip2> ...
files
- Type:
array; Required: no; Default: empty array - Purpose: Specifies the list of external hosts rule files.
short_circuit
- Type:
bool; Required: no; Default:false - Purpose: Stops the remaining executor chain after a local answer is generated.
Rule format:
<domain_rule> <ip1> <ip2> ...
Behavior
- Handles only
INclassA/AAAArequests with exactly one question. - Unprefixed rules default to
full:to match mosdnshosts. - Rule-family priority is fixed as
full -> domain -> regexp -> keyword. domain:uses the longest matching suffix.- Repeated patterns use last-write-wins semantics in load order: inline
entriesfirst, then each configured file line by line. - Positive local answers return same-family addresses with a fixed TTL of
10. - If the domain matches but the requested address family is missing, the plugin returns
NoError + empty answer + fake SOAinstead of passing through. - Non-matching queries pass through to subsequent execution.
- By default it keeps running the remaining chain after a local response; enable
short_circuitto stop immediately for both positive and empty local replies.
Typical Uses
- Local service discovery
- Small fixed overrides
arbitrary
Purpose
Injects arbitrary DNS records from zone-style rule strings.
Example Configuration
- tag: arbitrary_main
type: arbitrary
args:
rules:
# TXT record
- "example.com. 60 IN TXT \"hello world\""
# MX record
- "mail.example.com. 300 IN MX 10 mx1.example.com."
# A / AAAA / CNAME / PTR records are also supported
- "www.example.com. 120 IN A 192.0.2.10"
- "www.example.com. 120 IN AAAA 2001:db8::10"
- "alias.example.com. 120 IN CNAME www.example.com."
- "10.2.0.192.in-addr.arpa. 300 IN PTR host.example.com."
files:
# Load more static records from files
- "/etc/forgedns/zone.txt"
short_circuit: false
Configuration Details
rules
- Type:
array; Required: no - Purpose: Inline record rules.
- Syntax:
- Each list item is parsed as an independent zone snippet.
- Supports
$ORIGIN,$TTL,$INCLUDE,$GENERATE, owner inheritance, TTL units, comments, quoted strings, and multiline()syntax. - Common record types are parsed directly, including
A,AAAA,CNAME,NS,PTR,DNAME,ANAME,MD,MF,MB,MG,MR,NSAPPTR,MX,RT,AFSDB,RP,MINFO,HINFO,TXT,SPF,AVC,RESINFO,SOA,SRV,NAPTR, andCAA. - Other record types can be loaded through RFC3597 generic syntax:
TYPE#### \# <len> <hex>. - Defaults TTL to
3600when omitted.
files
- Type:
array; Required: no - Purpose: External rule files.
- Syntax: Uses the same zone parser as
rules.
short_circuit
- Type:
bool; Required: no; Default:false - Purpose: Stop the remaining executor chain after setting a synthetic response.
- Notes: By default
arbitraryonly sets the response and lets the chain continue.
Behavior
- Produces fully synthetic answers.
- Matches exactly on
qname + qtype + qclass. - When a request carries multiple questions, all matched records are accumulated into one response.
- By default the executor only sets the response and keeps the remaining chain running.
- When
short_circuitis enabled it returnsStopafter a match. - Quick setup syntax is intentionally not supported.
- Useful when
hostsis too limited.
Typical Uses
- TXT test records
- Local authority-style data
Notes
- Keep rule files readable. Arbitrary records become hard to audit faster than
hostsentries. - This is still a static answer generator, not a full authoritative server with transfer or dynamic update support.
- The parser is broader than the zone parser used by mosdns
arbitrary, but matching remains an exact static lookup.
redirect
Purpose
Rewrites matching names toward different target names or answer destinations.
Example Configuration
- tag: redirect_main
type: redirect
args:
rules:
# Exact-name redirect
- "full:old.example.com new.example.net"
# Suffix redirect
- "domain:legacy.example.com modern.example.net"
# Keyword redirect
- "keyword:staging staging-gateway.example.net"
files:
# Merge more redirect rules from files
- "/etc/forgedns/redirect.txt"
Configuration Details
rules
- Type:
array; Required: no; Default: empty array - Purpose: Defines inline redirect rules.
- Rule format:
<domain_rule> <target_name>
files
- Type:
array; Required: no; Default: empty array - Purpose: Specifies the list of external redirect rule files.
Rule format:
<domain_rule> <target_name>
Behavior
- Forward phase:
- Rewrites the request QUESTION NAME.
- Return phase:
- Restores the target name in the response question back to the original name.
- Appends a
CNAME original -> targetrecord in the answers.
Typical Uses
- Point a unified entry domain to another set of records.
- Perform alias redirection for specific domains without changing client configuration.
Notes
- It is better suited for simple queries such as
A/AAAA/TXT. - Full semantic transparency is not guaranteed for complex records and some extension scenarios.
ecs_handler
Purpose
Controls EDNS Client Subnet forwarding or injection.
Example Configuration
- tag: ecs_main
type: ecs_handler
args:
# Strip client-supplied ECS first
forward: false
# Add ECS when the request has none
send: true
# IPv4 ECS prefix length
mask4: 24
# IPv6 ECS prefix length
mask6: 48
- tag: ecs_preset
type: ecs_handler
args:
# Preset ECS source
preset: "203.0.113.10"
# Fixed-source mode usually keeps these switches explicit
forward: false
send: true
mask4: 24
mask6: 48
Configuration Details
forward
- Type:
boolean; Required: no - Purpose: Preserve ECS from the client side.
send
- Type:
boolean; Required: no - Purpose: Send ECS to upstreams.
preset
- Type:
string; Required: no - Purpose: Use a preset ECS source.
mask4
- Type:
integer; Required: no - Purpose: IPv4 ECS mask.
mask6
- Type:
integer; Required: no - Purpose: IPv6 ECS mask.
quick setup
- exec: "ecs_handler 203.0.113.10/24"
Behavior
- Can preserve, synthesize, or normalize ECS before forwarding.
- Interacts with cache correctness if ECS is also part of the cache key.
Typical Uses
- Geo-sensitive upstream policies
- Client-network-aware answers
Notes
- Keep ECS handling and cache-key policy aligned.
forward_edns0opt
Purpose
Forwards selected EDNS0 options to upstreams.
Example Configuration
- tag: edns_forward
type: forward_edns0opt
args:
# Preserve only the selected EDNS0 option codes
codes: [10, 12]
Configuration Details
codes
- Type:
array; Required: yes - Purpose: EDNS0 option codes to preserve and forward.
quick setup
- exec: "forward_edns0opt 10,12"
Behavior
- Keeps only selected EDNS0 options instead of blindly forwarding everything.
Typical Uses
- Preserve specific client-side EDNS signaling needed by upstreams.
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
prefer_ipv4 / prefer_ipv6
Purpose
Biases dual-stack results toward one address family.
Example Configuration
- tag: prefer_v4
type: prefer_ipv4
args:
# Cache whether the preferred family exists
cache: true
# Keep the preference cache for one hour
cache_ttl: 3600
Configuration Details
cache
- Type:
boolean; Required: no; Default:false - Purpose: Cache preference decisions.
cache_ttl
- Type:
duration; Required: no - Purpose: Retention for the preference cache.
Behavior
- Helps make A and AAAA selection more stable when both families exist.
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.
black_hole
Purpose
Returns sinkhole IPs directly.
Example Configuration
- tag: sinkhole
type: black_hole
args:
ips:
# Returned for A queries
- "0.0.0.0"
# Returned for AAAA queries
- "::"
short_circuit: true
Configuration Details
ips
- Type:
array; Required: yes - Purpose: Sinkhole addresses to return.
short_circuit
- Type:
bool; Required: no; Default:false - Purpose: Stops the remaining executor chain after a local answer is generated.
quick setup
- exec: "black_hole 0.0.0.0 ::"
- exec: "black_hole 0.0.0.0 :: short_circuit=true"
Behavior
- Generates immediate answers that point to sinkhole addresses.
- By default it keeps running the remaining chain after a match; enable
short_circuitto stop immediately.
Typical Uses
- Blocking domains
- Safe redirection away from real destinations
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.
query_summary
Purpose
Records concise query summaries.
Example Configuration
- tag: summary_main
type: query_summary
args:
# Extra title so multiple summary points are easy to distinguish
msg: "main pipeline"
Configuration Details
msg
- Type:
string; Required: no - Purpose: Extra summary label.
quick setup
- exec: "query_summary main"
Behavior
- Emits compact logs or summaries for operator visibility.
Typical Uses
- Light observability on the main path
- Distinguish different branches
query_recorder
Purpose
Persists the entry request, the post-next response, and sequence execution-path events into a recorder-owned SQLite database, then exposes history, aggregate stats, and an SSE stream.
Example Configuration
- tag: query_recorder_main
type: query_recorder
args:
# SQLite path for this recorder. Different recorders should use different paths.
path: "./data/query-recorder-main.sqlite"
# Hot-path enqueue buffer size
queue_size: 8192
# Batch size per SQLite flush
batch_size: 256
# Background flush interval in milliseconds
flush_interval_ms: 200
# Number of recent records kept in memory for SSE tail replay
memory_tail: 1024
# Retention window in days; minimum 1
retention_days: 7
# Cleanup interval in hours; minimum 1
cleanup_interval_hours: 1
Configuration Details
path
- Type:
string; Required: yes - Purpose: SQLite path for this recorder.
queue_size
- Type:
integer; Required: no; Default:8192 - Purpose: Bounded queue size between the request path and the writer thread.
batch_size
- Type:
integer; Required: no; Default:256 - Purpose: Number of records flushed per SQLite batch.
flush_interval_ms
- Type:
integer; Required: no; Default:200 - Purpose: Maximum batch flush interval in milliseconds.
memory_tail
- Type:
integer; Required: no; Default:1024 - Purpose: Size of the in-memory tail used by
stream?tail=n.
retention_days
- Type:
integer; Required: no; Default:7; Minimum:1 - Purpose: Record retention window. Expired rows are deleted by the cleanup task.
cleanup_interval_hours
- Type:
integer; Required: no; Default:1; Minimum:1 - Purpose: Cleanup task cadence.
Behavior
- This is a pure executor observer and does not change server finalization logic.
- It captures a structured snapshot of the entry request, enables
DnsContext.execution_path, runsnext, and commits immediately afternextreturns. - Successful runs store the current response. Failed runs store
errorand an empty response shape. - Request and response payloads are not stored as wire blobs. Question, RR, and EDNS fields are extracted into JSON text columns.
- Each recorder uses exactly two tables:
qr_<safe_tag>_<fnv64hex>_v1_recordsqr_<safe_tag>_<fnv64hex>_v1_steps
recordscontains only the fixed schema fields for structured snapshots.stepsstoressequencepath events for path analysis and hit-rate reporting.- Every recorder owns its own bounded queue, SQLite connection, writer thread, tail buffer, and SSE broadcaster.
- v1 assumes different recorders use different
pathvalues. There is no cross-recorder writer sharing or path coordination.
Data Shape
questions_jsonis always a question array, for example:
[
{ "name": "www.example.com.", "qtype": "A", "qclass": "IN" }
]
answers_json,authorities_json,additionals_json, andsignature_jsonare RR arrays, for example:
[
{
"name": "www.example.com.",
"class": "IN",
"ttl": 300,
"rr_type": "A",
"payload_kind": "A",
"payload_text": "192.0.2.1",
"payload": { "ip": "192.0.2.1" }
}
]
req_edns_jsonandresp_edns_jsonare EDNS objects orNULL.- The
v1suffix in the table name is the schema version. Future upgrades add new versioned tables rather than altering the existing ones in place.
API
GET /plugins/<tag>/records- Returns record rows ordered by
created_at_msdescending. - Query parameters:
cursor=<created_at_ms>:<id>limit=<n>, default100, max500since_ms=<unix_ms>until_ms=<unix_ms>
- Returns record rows ordered by
GET /plugins/<tag>/records/<id>- Returns one full record plus
steps.
- Returns one full record plus
GET /plugins/<tag>/stats/overview- Returns totals, error count, dropped count, and average latency.
- Supports
since_msanduntil_ms.
GET /plugins/<tag>/stats/plugins- Returns hit stats grouped by
matcher / executor / builtin. - Supports
since_ms,until_ms, andkind=matcher|executor|builtin|all.
- Returns hit stats grouped by
GET /plugins/<tag>/stream- Streams newly written records over SSE.
- Supports
tail=<n>to replay the in-memory tail.
Typical Uses
- Persistent audit and troubleshooting trails
sequencepath analysis and plugin hit-rate reporting- Real-time query log feeds for dashboards or control planes
Notes
- Place the recorder close to the entry point if you want the full main-path trace.
- If an earlier branch short-circuits before the recorder, that request will not be recorded.
- If
nextfails and the server later emits a fallback response, the database still reflects the plugin's point of view:errorplus an empty response. - If the management API is disabled, the recorder still writes SQLite data but does not expose query or SSE routes.
metrics_collector
Purpose
Collects Prometheus metrics for query handling.
Example Configuration
- tag: metrics_main
type: metrics_collector
args:
# Collector label exported through /metrics
name: "main"
Configuration Details
name
- Type:
string; Required: no - Purpose: Metrics label namespace.
quick setup
- exec: "metrics_collector main"
Behavior
- Exposes query counters, inflight counts, and latency metrics through the management API.
API
GET /metrics
Typical Uses
- Prometheus integration
- Observe multiple policy entry points separately
debug_print
Purpose
Prints a debug message.
Example Configuration
- tag: debug_main
type: debug_print
args:
# Log title; defaults to "debug print" when omitted
msg: "before forward"
Configuration Details
msg
- Type:
string; Required: yes - Purpose: Message content.
quick setup
- exec: "debug_print cache branch"
Typical Uses
- Temporary debugging
- Reading sequence branches during development
sleep
Purpose
Sleeps for a bounded duration inside the chain.
Example Configuration
- tag: sleep_100ms
type: sleep
args:
# Add 100 ms of async delay
duration: 100
Configuration Details
duration
- Type:
duration; Required: yes - Purpose: Sleep duration.
quick setup
- exec: "sleep 100"
Typical Uses
- Testing
- Timing experiments
http_request
Purpose
Sends callback requests to external http/https services. It can trigger before the current DNS flow enters downstream executors or after downstream execution completes, which makes it suitable for webhooks, audit pipelines, alerts, and external integrations.
Example Configuration
- tag: webhook_notify_after
type: http_request
args:
method: POST
url: "https://hooks.example.com/dns"
phase: after
async: true
timeout: 5s
headers:
X-Client-IP: "${client_ip}"
X-Qname: "${qname}"
query_params:
source: "forgedns"
qname: "${qname}"
json:
qname: "${qname}"
client_ip: "${client_ip}"
rcode: "${rcode_name}"
resp_ip: "${resp_ip}"
Config Fields
args.method
- Type:
string; Required: yes - Purpose: Selects the HTTP method such as
GET,POST,PUT,PATCH, orDELETE.
args.url
- Type:
string; Required: yes - Purpose: The target URL.
- Notes: Supports
${key}placeholder interpolation. The rendered URL must use eitherhttporhttps.
args.phase
- Type:
string; Required: no; Default:after - Allowed values:
before,after - Purpose: Controls whether the request is sent before or after downstream executors run.
args.async
- Type:
boolean; Required: no; Default:true - Purpose: Chooses bounded background dispatch or inline synchronous dispatch.
args.timeout
- Type:
string; Required: no; Default:5s - Purpose: Caps the total time budget for one HTTP call.
- Supported units:
ms,s,m,h,d
args.error_mode
- Type:
string; Required: no; Default:continue - Allowed values:
continue: only log the failure and keep runningstop: returnStopon failurefail: return an executor error immediately
args.headers
- Type:
map<string,string>; Required: no; Default: empty - Purpose: Adds HTTP request headers.
- Notes: Header values support
${key}placeholder interpolation.
args.query_params
- Type:
map<string,string>; Required: no; Default: empty - Purpose: Appends additional query parameters to the rendered URL.
- Notes: Values support
${key}placeholder interpolation and are combined with any query already present inargs.url.
args.body
- Type:
string; Required: no - Purpose: Sends a raw string body.
- Notes: Supports
${key}placeholder interpolation and can be paired withargs.content_type.
args.json
- Type:
object | array; Required: no - Purpose: Sends a JSON body.
- Notes: Automatically sets
Content-Type: application/json. Every string leaf supports${key}interpolation while non-string values are preserved as-is.
args.form
- Type:
map<string,string>; Required: no - Purpose: Sends an
application/x-www-form-urlencodedbody. - Notes: Values support
${key}interpolation and the plugin automatically sets the matchingContent-Type.
args.content_type
- Type:
string; Required: no - Purpose: Sets
Content-Typefor rawargs.body. - Notes: This helper can only be used with
args.body, not withargs.jsonorargs.form.
args.socks5
- Type:
string; Required: no - Purpose: Routes requests through a SOCKS5 proxy.
- Notes: Uses the same format as
upstream[].socks5, includinghost:port,username:password@host:port, and bracketed IPv6.
args.insecure_skip_verify
- Type:
boolean; Required: no; Default:false - Purpose: Skips HTTPS certificate validation.
args.max_redirects
- Type:
integer; Required: no; Default:5 - Purpose: Limits how many redirects are followed.
args.queue_size
- Type:
integer; Required: no; Default:256 - Purpose: Sets the bounded queue capacity used by async mode.
Available Placeholders
- Same as
script:qname,qtype,qtype_name,qclass,qclass_name - Source fields:
client_ip,client_port,server_name,url_path - Runtime fields:
marks,has_resp - Response fields:
rcode,rcode_name,resp_ip - Cron metadata:
cron_plugin_tag,cron_job_name,cron_trigger_kind,cron_scheduled_at_unix_ms
Behavior
- With
phase: before, the HTTP request is dispatched first and the downstream executor chain runs afterward. - With
phase: after, the downstream executor chain runs first and the HTTP request is dispatched against the resulting context. async: trueuses a bounded background queue. Queue insertion failures are handled according toerror_mode.async: falsewaits for the HTTP call on the current request path.- Only terminal
2xxresponses are treated as success.3xxresponses are followed up tomax_redirects. - The plugin drains and discards the HTTP response body so connections remain reusable, but it does not write that body back into
DnsContext. - If
Content-Typeis already set explicitly inargs.headers, the plugin does not overwrite it.
Notes
args.body,args.json, andargs.formare mutually exclusive.- This is a side-effect executor. In v1 it cannot rewrite DNS requests, responses, marks, or attrs based on the HTTP result.
- v1 does not support multipart uploads or quick setup syntax.
- If you need both trigger moments, configure two separate
http_requestplugin instances.
script
Purpose
Runs an explicitly configured external command and injects a stable subset of the current DnsContext into command arguments or environment variables.
Example
- tag: script_notify
type: script
args:
command: "bash"
args:
- "/opt/forgedns/notify.sh"
- "${qname}"
- "${client_ip}"
env:
FDNS_QNAME: "${qname}"
FDNS_CLIENT_IP: "${client_ip}"
FDNS_MARKS: "${marks}"
timeout: "5s"
error_mode: continue
max_output_bytes: 4096
Config Fields
args.command
- Type:
string; Required: yes - Purpose: Command path or program name to execute.
- Notes: This field is never templated.
args.args
- Type:
array<string>; Required: no; Default: empty - Purpose: Positional command arguments.
- Notes: Each item supports
${key}interpolation.
args.env
- Type:
map<string,string>; Required: no; Default: empty - Purpose: Extra child-process environment variables.
- Notes: Values support
${key}interpolation and overlay the inherited process environment.
args.cwd
- Type:
string; Required: no; Default: none - Purpose: Working directory for the child process.
args.timeout
- Type:
string; Required: no; Default:5s - Purpose: Maximum execution time for one script run.
- Supported units:
ms,s,m,h,d
args.error_mode
- Type:
string; Required: no; Default:continue - Allowed values:
continue: log failure or timeout, then returnNextstop: log failure or timeout, then returnStopfail: return an executor error immediately
args.max_output_bytes
- Type:
usize; Required: no; Default:4096 - Purpose: Maximum captured stdout/stderr length before truncation.
Available Placeholders
- Request fields:
qname,qtype,qtype_name,qclass,qclass_name - Source fields:
client_ip,client_port,server_name,url_path - Runtime fields:
marks,has_resp - Response fields:
rcode,rcode_name,resp_ip - Cron metadata:
cron_plugin_tag,cron_job_name,cron_trigger_kind,cron_scheduled_at_unix_ms
Behavior
- The plugin does not mutate DNS requests or responses.
- It runs only the explicit configured command and does not wrap it with
sh -c,cmd /c, or similar shell shortcuts. - Arguments and environment variables are rendered from the current
DnsContexton each execution. - On timeout the child process is terminated, then
error_modedecides how the sequence continues.
Notes
- v1 does not support quick setup syntax.
commandmust not be empty.- Only the documented built-in placeholders are accepted; unknown placeholders fail plugin initialization.
- This is a side-effect executor. It does not support writing attrs, marks, or DNS responses back through stdout.
ipset
Purpose
Writes response IPs into Linux ipset through the embedded Rust netlink backend, without requiring the runtime ipset command.
Example Configuration
- tag: ipset_main
type: ipset
args:
# ipset used for A answers
set_name4: "forgedns_v4"
# ipset used for AAAA answers
set_name6: "forgedns_v6"
# Aggregate IPv4 writes to /24 prefixes
mask4: 24
# Aggregate IPv6 writes to /64 prefixes
mask6: 64
Configuration Details
set_name4
- Type:
string; Required: no; Default: none - Purpose: Specifies the ipset name used to write IPv4 addresses.
set_name6
- Type:
string; Required: no; Default: none - Purpose: Specifies the ipset name used to write IPv6 addresses.
mask4
- Type:
integer; Required: no; Default:24 - Purpose: Specifies the prefix length used when writing IPv4 addresses into ipset.
mask6
- Type:
integer; Required: no; Default:32 - Purpose: Specifies the prefix length used when writing IPv6 addresses into ipset.
quick setup
- exec: "ipset forgedns_v4,4,24 forgedns_v6,6,64"
Format:
<set_name>,<family>,<mask>
Here, family is 4 or 6.
Behavior
- Extracts unique A/AAAA addresses from the answer section.
- Writes them into the corresponding set according to the address family.
- Delivers them to the background writer through a non-blocking queue.
Typical Uses
- Policy routing
- Firewall integration
Notes
- On non-Linux platforms it degrades to a no-op.
- When the queue is full, the side effect is dropped and does not block the DNS hot path.
nftset
Purpose
Writes response IPs into nftables sets through the embedded Rust netlink backend, without requiring the runtime nft command.
Example Configuration
Structured form:
- tag: nftset_main
type: nftset
args:
ipv4:
# IPv4 target uses the ip family
table_family: "ip"
table_name: "mangle"
set_name: "dns_v4"
mask: 24
ipv6:
# IPv6 target uses the ip6 family
table_family: "ip6"
table_name: "mangle"
set_name: "dns_v6"
mask: 64
Compatibility form:
- tag: nftset_legacy
type: nftset
args:
# Compatibility fields, useful when migrating old configs
table_family4: "ip"
table_name4: "mangle"
set_name4: "dns_v4"
mask4: 24
table_family6: "ip6"
table_name6: "mangle"
set_name6: "dns_v6"
mask6: 64
Configuration Details
ipv4
- Type:
object; Required: no; Default: none - Purpose: Defines the target IPv4 nftables set.
- Child fields:
table_familytable_nameset_namemask
ipv6
- Type:
object; Required: no; Default: none - Purpose: Defines the target IPv6 nftables set.
- Child fields:
table_familytable_nameset_namemask
table_family4 / table_family6
- Type:
string; Required: no; Default: none - Purpose: In the compatibility form, defines the nftables table family for IPv4 / IPv6 respectively.
table_name4 / table_name6
- Type:
string; Required: no; Default: none - Purpose: In the compatibility form, defines the nftables table name for IPv4 / IPv6 respectively.
set_name4 / set_name6
- Type:
string; Required: no; Default: none - Purpose: In the compatibility form, defines the set name for IPv4 / IPv6 respectively.
mask4 / mask6
- Type:
integer; Required: no; Default: implementation-defined - Purpose: In the compatibility form, defines the prefix length for IPv4 / IPv6 respectively.
quick setup
- exec: "nftset ip,mangle,dns_v4,ipv4_addr,24 ip6,mangle,dns_v6,ipv6_addr,64"
Format:
<family>,<table>,<set>,<type>,<mask>
Behavior
- Extracts A/AAAA addresses.
- Writes nftables interval elements according to the prefix.
- Also uses the background writer so that the hot path remains non-blocking.
Typical Uses
- nftables-driven routing or firewall policies
Notes
- On non-Linux platforms it degrades to a no-op.
ros_address_list
Purpose
Writes response IPs into MikroTik RouterOS address lists, with dynamic entries, persistent entries, startup-time file loading, and shutdown cleanup.
Example Configuration
- tag: ros_address_list_main
type: ros_address_list
args:
# RouterOS API endpoint
address: "172.16.1.1:8728"
# API username
username: "api-user"
# API password
password: "secret"
# Use asynchronous writes to avoid blocking the DNS hot path
async: true
# Address list used for A records
address_list4: "forgedns_ipv4"
# Address list used for AAAA records
address_list6: "forgedns_ipv6"
# Prefix for comments on ForgeDNS-managed entries
comment_prefix: "forgedns"
# Lower bound for dynamic-entry TTL
min_ttl: 60
# Upper bound for dynamic-entry TTL
max_ttl: 3600
# Force dynamic entries to 300 seconds; use 0 to omit RouterOS timeout
fixed_ttl: 300
# Remove owned entries when the plugin shuts down
cleanup_on_shutdown: true
persistent:
ips:
# Persistent single IP
- "1.1.1.1"
# Persistent IPv4 CIDR
- "100.64.1.0/24"
# Persistent IPv6 CIDR
- "2001:db8::/64"
files:
# Load more persistent items from files
- "/etc/forgedns/persistent_ips.txt"
Configuration Details
address
- Type:
string; Required: yes - Purpose: RouterOS API endpoint.
username
- Type:
string; Required: yes - Purpose: RouterOS username.
password
- Type:
string; Required: yes - Purpose: RouterOS password.
async
- Type:
bool; Required: no; Default:true - Purpose: Controls whether address writes use asynchronous mode. When enabled, the DNS response path only submits tasks, and a background manager completes the RouterOS interaction.
address_list4
- Type:
string; Required: no - Purpose: IPv4 address-list name.
address_list6
- Type:
string; Required: no - Purpose: IPv6 address-list name.
comment_prefix
- Type:
string; Required: no - Purpose: Prefix for generated RouterOS comments.
persistent
- Type:
object; Required: no; Default: none - Purpose: Defines the static address set that should be kept for the long term. This part does not depend on DNS responses to trigger. After plugin startup it can be synchronized to RouterOS directly and then kept consistent by the reconcile loop.
persistent.ips
- Type:
array<string>; Required: no; Default: empty - Purpose: Declares persistent IPs or CIDR ranges inline.
persistent.files
- Type:
array<string>; Required: no; Default: empty - Purpose: Loads the persistent address set from external files at plugin startup.
- Notes: These files are read once during initialization. To apply later file changes, reload the plugin or the application.
min_ttl
- Type:
u64; Required: no; Default:60 - Purpose: Defines the minimum TTL allowed for dynamic address entries.
max_ttl
- Type:
u64; Required: no; Default:3600 - Purpose: Defines the maximum TTL allowed for dynamic address entries.
fixed_ttl
- Type:
u64; Required: no; Default: none - Purpose: Specifies one fixed TTL for all dynamically written entries. If it is set to
0, dynamic entries will not set a RouterOStimeout.
cleanup_on_shutdown
- Type:
bool; Required: no; Default:true - Purpose: Controls whether entries managed by the plugin are removed when the plugin exits.
Behavior
- The plugin itself does not modify DNS responses.
- It only passes through during the forward phase.
- During the return phase:
- Extracts A/AAAA records from
NOERRORresponses. - Deduplicates them and keeps the largest TTL.
- Submits them to the background manager according to async or sync mode.
- Extracts A/AAAA records from
- The manager is responsible for:
- Initial connectivity verification
- Dynamic entry refresh
- Persistent entry consistency maintenance
- Cleanup on shutdown
Typical Uses
- DNS-driven policy routing on RouterOS
- Maintaining dynamic destination groups from DNS answers
Notes
- At least one of
address_list4oraddress_list6is required. comment_prefixand the plugintagmust not contain;or=.- Synchronous mode does not change the DNS response itself. Even if the RouterOS write fails, the DNS result is still preserved.
upgrade
Purpose
Runs the ForgeDNS upgrade flow from the executor pipeline. It is suitable for maintenance tasks triggered by cron, sequence, or another executor.
Example Configuration
- tag: upgrade_auto
type: upgrade
args:
repository: SvenShi/forgedns
asset: auto
cache_dir: ./upgrade/cache
backup_dir: ./upgrade/backups
restart: service
force: false
cleanup: true
timeout: 30s
socks5: 127.0.0.1:1080
insecure_skip_verify: false
Options
force- Boolean. Default:
false. - Continue downloading, verifying, and replacing even when the selected release is not newer than the current version.
- Boolean. Default:
cleanup- Boolean. Default:
true. - Cleans
cache_dirandbackup_dirafter a successful upgrade.
- Boolean. Default:
repository- GitHub repository. Default:
SvenShi/forgedns.
- GitHub repository. Default:
asset- Release asset name.
autoselects the current platform archive.
- Release asset name.
cache_dir/backup_dir- Download cache and pre-replacement backup directories.
restart- Supported values are
noneandservice. When set toservice, the application exits with a non-zero status code after the binary is successfully replaced, allowing systemd to restart it automatically. Therefore, the corresponding service must setRestarttoalwaysoron-failure.
- Supported values are
timeout,socks5,insecure_skip_verify- Same meaning as the CLI
upgradeflags.
- Same meaning as the CLI
Behavior
- The executor always returns
ExecStep::Next. - The plugin only runs the
applyaction. It does not providecheckordownloadmodes. - By default it updates only when a newer version is available.
force: trueforces the update. - By default it cleans cache and backup files after a successful upgrade. Set
cleanup: falseto keep rollback files. - The upgrade downloads the archive and verifies SHA256 with the GitHub release asset
digestfield. - On Unix it unpacks
.tar.gz, backs up the current binary, and replaces it. Windows currently does not support plugin upgrades.
quick setup
- exec: upgrade
- exec: upgrade force
- exec: upgrade force=false
- Empty arguments run apply with the default configuration.
- Only
forceandforce=true|falseare supported. - Other settings use defaults. Use full
argsconfiguration to override the repository, directories, restart mode, or proxy. modeis not supported; the plugin always applies upgrades.
download
Purpose
Downloads one or more http/https files into a local directory and overwrites the target files only after the new content is fully written.
Example Configuration
- tag: rules_download
type: download
args:
timeout: 30s
socks5: "127.0.0.1:1080"
downloads:
- url: "https://example.com/geosite.dat"
dir: "/etc/forgedns"
- url: "https://example.com/geoip.dat"
dir: "/etc/forgedns"
filename: "geoip.dat"
Quick Setup
- exec: "download https://example.com/rules.txt /etc/forgedns"
Behavior
downloadsrun sequentially in declaration order.- A failed item only emits a warning log and does not stop later items.
- Missing target directories are created automatically.
- Files are written to a temporary path first and then moved into place.
- When
socks5is set, all download connections are routed through that SOCKS5 proxy using the same format asupstream[].socks5. - By default, ForgeDNS checks target files during startup and downloads any missing ones before other plugins initialize. A bootstrap failure aborts startup.
- Set
startup_if_missing: falseto disable that bootstrap behavior.
Notes
- Only
httpandhttpsare supported. socks5acceptshost:portandusername:password@host:port; bracket IPv6 addresses such as"[::1]:1080"are supported too.startup_if_missingonly fills missing files; it does not overwrite existing targets on every startup.- When used inside a normal
sequence, the download time is paid directly by that request. - Overwriting a local file does not apply automatically. If you only need file-backed providers to pick up the new data, prefer chaining
reload_provider; ifconfig.yaml, dependency topology, or the plugin list changed too, usereload.
Recommended Pairing
- tag: rules_refresh
type: sequence
args:
- exec: "$rules_download"
- exec: "$reload_rules"
- tag: rules_download
type: download
args:
downloads:
- url: "https://example.com/geosite.dat"
dir: "/etc/forgedns"
- tag: provider_geosite
type: geosite
args:
file: "/etc/forgedns/geosite.dat"
- tag: reload_rules
type: reload_provider
args:
- "$provider_geosite"
Subscription Refresh Example
This example fits the common flow of “remote subscription -> scheduled download -> targeted provider refresh”:
plugins:
# 1. Run the subscription refresh flow periodically
- tag: subscription_cron
type: cron
args:
timezone: "Asia/Shanghai"
jobs:
- name: refresh_rule_subscriptions
interval: 6h
executors:
- "$subscription_refresh"
# 2. Chain download and targeted provider reload with a sequence
- tag: subscription_refresh
type: sequence
args:
- exec: "$subscription_download"
- exec: "$reload_rule_providers"
# 3. Download remote subscription files
- tag: subscription_download
type: download
args:
timeout: 60s
startup_if_missing: true
downloads:
- url: "https://example.com/geosite.dat"
dir: "/etc/forgedns/rules"
filename: "geosite.dat"
- url: "https://example.com/geoip.dat"
dir: "/etc/forgedns/rules"
filename: "geoip.dat"
# 4. Reload only the affected providers after download completes
- tag: reload_rule_providers
type: reload_provider
args:
- "$provider_geosite"
- "$provider_geoip"
# 5. These providers re-read the local files after reload
- tag: provider_geosite
type: geosite
args:
file: "/etc/forgedns/rules/geosite.dat"
- tag: provider_geoip
type: geoip
args:
file: "/etc/forgedns/rules/geoip.dat"
Notes:
downloadwrites the subscription content to local files.reload_providerrefreshes only the affected provider snapshots without rebuilding unrelated plugins.startup_if_missing: trueis useful for first-time deployment when files may not exist yet.- If the subscription source requires a proxy, set a SOCKS5 proxy on
subscription_download.args.socks5. - If you do not want startup to overwrite existing files, keep the default behavior and only bootstrap missing files.
- If the update also changes
config.yaml, provider topology, or the plugin list, use a fullreloadinstead.
Full Reload Still Fits Config Changes
- tag: config_refresh
type: sequence
args:
- exec: "$subscription_download"
- exec: "$reload_all"
- tag: reload_all
type: reload
reload_provider
Purpose
Reloads one or more providers in place by tag, rebuilding their internal snapshots with the same startup configuration without triggering a full application reload.
Example Configuration
- tag: reload_rule_providers
type: reload_provider
args:
- "$geosite_cn"
- "$geoip_cn"
Quick Setup
- exec: "reload_provider $geosite_cn"
Behavior
- Providers are reloaded sequentially in the order declared in
args. - The semantics are the same as calling
POST /plugins/<provider_tag>/reloadfor each referenced provider. - Once every provider reload succeeds, the executor returns
Next. - Only provider-local data is refreshed; tags, dependencies, and other plugin configuration are unchanged.
Typical Uses
- Refreshing only the affected
domain_set,ip_set,geosite,geoip, oradguard_ruleproviders afterdownload. - Reducing the blast radius and cost of a full application reload in background maintenance flows.
Notes
argsonly accepts provider references such as"$geoip_cn"; inline rules and file references are rejected.- If the update changes
config.yaml, provider topology, the plugin list, or other non-provider structures, you still needreload. - Running this on a live request path may trigger file reads and recompilation, so it is usually a better fit for background
cronor maintenancesequenceflows.
reload
Purpose
Triggers the same application-level full reload as the management API POST /reload, reloading the active configuration and rebuilding all plugins.
Example Configuration
- tag: reload_all
type: reload
Quick Setup
- exec: "reload"
Behavior
- Execution submits a reload request to the application control layer.
- The semantics are the same as the management API
POST /reload. - Once the reload request is accepted, the executor returns
Next. - This is a full application reload. Reloading selected plugin tags is not supported.
Typical Uses
- Pairing with
downloadin acronjob so refreshed rule files take effect immediately. - Triggering a full configuration reload from a dedicated background
sequence.
Notes
- It must run inside a normal ForgeDNS process with application control context attached.
- Execution fails when another reload is already
pendingorin_progress. - Using it in a live request
sequencetriggers a full application reload and is usually not appropriate for latency-sensitive request paths.
cron
Purpose
Schedules a list of executors in the background. It does not participate in the live DNS request path and starts running only after plugin initialization.
Example Configuration
- tag: cron_jobs
type: cron
args:
timezone: "Asia/Shanghai"
jobs:
- name: refresh_sets
interval: 5m
executors:
- "$seq_refresh"
- "debug_print cron refresh"
- name: nightly_cleanup
schedule: "15 3 * * *"
executors:
- "sleep 2s"
- "$seq_cleanup"
Configuration Details
args.jobs
- Type:
array; Required: yes - Purpose: Defines one or more background jobs.
- Runtime impact:
- The array cannot be empty.
- Each job maintains its own trigger state and overlap protection.
args.timezone
- Type:
string; Required: no - Default: system local time zone
- Purpose: Overrides the time zone used by all
schedulejobs in thiscronplugin. - Notes:
- Only affects
schedule. - When omitted, ForgeDNS uses the system local time zone and falls back to
UTCif unavailable. - Use IANA names such as
Asia/Shanghai,UTC, orAmerica/Los_Angeles.
- Only affects
args.jobs[].name
- Type:
string; Required: yes - Purpose: Job name used in logs and runtime metadata.
- Runtime impact:
- Must be unique within the same
cronplugin.
- Must be unique within the same
args.jobs[].schedule
- Type:
string; Required: exactly one ofscheduleorinterval - Purpose: Schedule a job with a standard 5-field cron expression.
- Notes:
- Only
minute hour day month day-of-weekis supported. - Second-level cron expressions are not supported.
- Next runs are computed in
args.timezoneor the system local time zone.
- Only
args.jobs[].interval
- Type:
string; Required: exactly one ofscheduleorinterval - Purpose: Schedule a job with a fixed interval.
- Supports:
5m1h1d
- Runtime impact:
- Minimum interval is
1m. - The first run happens after one full interval elapses.
- Minimum interval is
args.jobs[].executors
- Type:
array; Required: yes - Purpose: Ordered list of executors to run for the job.
- Supports:
$tagexplicit executor references- bare
tagreferences - quick-setup expressions such as
debug_print cron refresh
- Runtime impact:
- The array cannot be empty.
- Later executors still run even if an earlier executor returns
Stop, produces a response, or fails.
Behavior
scheduleandintervalare mutually exclusive.- If a job is still running when the next trigger arrives, that trigger is skipped and not replayed later.
- Jobs run with an empty
DnsContext, so this plugin is best suited for side-effect executors or dedicated backgroundsequencechains. cronitself cannot be executed inside a normal requestsequence.
Typical Uses
- Periodic side-effect tasks.
- Scheduling a dedicated background
sequence. - Providing a common trigger surface for future executors such as
reload.
Notes
- A
cronjob cannot reference anothercronexecutor. - Executors that require a real DNS request usually do not make sense in an empty background context.