The upstream_router resolve plugin routes queries to different upstream DNS
servers based on the queried domain name. It does not answer queries itself;
instead, it sets per-request upstream candidates on the PluginContext, which
the core resolver then uses to forward the query with failover.
Typical uses:
- Send all
*.corpqueries to on-prem resolvers while everything else goes to public resolvers. - Route certain SaaS domains through special filtering or logging resolvers.
plugins:
- id: corp-router
type: upstream_router
hooks:
pre_resolve: 235
config:
targets:
ips: [192.168.0.0/16 ]
routes:
- suffix: corp
upstreams:
- host: 10.0.0.2
port: 53
- host: 10.0.0.3
port: 53plugins:
- id: router-advanced
type: upstream_router
hooks:
pre_resolve: 230
config:
# BasePlugin targeting + logging
targets:
ips:
- 192.168.0.0/16
ignore_ips:
- 192.168.1.100/32
listeners: any
domains:
- example.com
domains_mode: suffix
qtypes: [ 'A', 'AAAA' ]
logging:
level: info
stderr: true
# Route definitions
routes:
# Exact match: only 'internal.example.com'
- domain: internal.example.com
upstreams:
- host: 10.1.0.10
port: 53
# Suffix match: any name ending in '.corp' or equal to 'corp'
- suffix: corp
upstreams:
- host: 10.2.0.20
port: 53
- host: 10.2.0.21
port: 53
# Another suffix using a FQDN-style suffix
- suffix: .svc.example
upstreams:
- host: 172.16.0.10
port: 53The configuration is described by UpstreamRouterConfig and UpstreamRoute:
-
routes: list[UpstreamRoute]-
Each route is a mapping with keys:
domain: str | null- Exact domain name to match. Compared case-insensitively with the trailing dot removed.
suffix: str | null- Domain suffix to match. Stored without a leading dot internally.
- A query
qnamematches whenqname == suffixorqnameends with"." + suffix.
upstreams: list[{host, port}](required)- The upstream candidates to use when the route matches.
- Each entry has:
host: str– IP or hostname of the upstream resolver.port: int– port number (1–65535).
-
-
Routes without at least one of
domainorsuffixor without any validupstreamsare ignored during normalization.
- During
pre_resolve, UpstreamRouter:- Checks BasePlugin targeting (
targets*, listener/domain filters). - Normalizes
qname(lowercase, no trailing dot). - Walks routes in order and picks the first match.
- When a match is found, sets
ctx.upstream_candidatesto the route's[{"host", "port"}, ...]and returnsNoneso normal processing continues.
- Checks BasePlugin targeting (
- The core resolver is responsible for actually forwarding to the chosen
upstreams and handling failover. A helper
_forward_with_failoverexists in the plugin code but is not used directly by configuration.
UpstreamRouter supports all BasePlugin options for client, listener and qtype/domain targeting, as well as per-plugin logging. The full configuration example above demonstrates typical usage.