Skip to content

DNS

Every [dns] field is optional. With no [dns] section at all:

  • names that route DIRECT are resolved by your system's resolvers;
  • other names Core has to resolve itself use udp://8.8.8.8;
  • names sent through a proxy on the local HTTP/SOCKS5 port are passed to the proxy as names and resolved on the proxy side;
  • Enhanced Mode answers with Fake-IP for names that a rule sends through a proxy.

Start without [dns], and add settings only when you have a reason to.

When DNS matters

Local HTTP/SOCKS5 port. Applications send the destination name (CONNECT github.com:443). Domain rules match the name directly and no local lookup is needed.

Enhanced Mode. Applications resolve the name before they connect, so Core must answer their DNS queries to know which name an IP belongs to. On Linux, the eBPF backend redirects queries to Core automatically. [dns.tun] controls how Core answers. Read DNS for transparent routing for the full picture.

Fake-IP

toml
[dns.tun]
mode = "fake-ip"
fake-ip-range = "198.18.0.1/16"
fake-ip-filter-mode = "blacklist"
fake-ip-filter = ["+.lan", "+.local"]

For a name that a rule sends through a proxy, Core answers with an address from the Fake-IP pool. When the application connects to that address, Core maps it back to the name and gives the name to the proxy. Names routed DIRECT get their real address. Names routed to REJECT get REFUSED.

FieldDefaultNotes
mode"fake-ip"fake-ip, or redir-host to always answer with real addresses
fake-ip-range"198.18.0.1/16"IPv4 pool, inside 198.18.0.0/15
fake-ip-range-v6"fd00::1/18"IPv6 pool
fake-ip-filter-mode"blacklist"blacklist: listed names get real addresses; whitelist: only listed names get Fake-IP
fake-ip-filter[]names: example.com, +.example.com (and subdomains), *.example.com, geosite:<code>
fake-ip-ttl300seconds
fake-ip-capacity65535mappings kept; oldest are evicted
store-fake-ipfalsekeep mappings across restarts
fake-ip-store-path""file for stored mappings

Put local names (+.lan, +.local, your company's internal zone if it must resolve locally) in fake-ip-filter.

Upstream resolvers

toml
[dns.upstream]
default = ["https://1.1.1.1/dns-query", "tls://8.8.8.8"]
direct = ["udp://192.168.1.1"]
fallback = ["tls://9.9.9.9"]
FieldDefaultUsed for
default["udp://8.8.8.8"]names Core resolves that do not route direct
directsystem resolversnames that route DIRECT
bootstrapsystem resolversresolving the host names of DoT/DoH resolvers
proxy-serverbootstrapresolving the host names of your [[proxy]] servers
fallback[]used when the primary resolvers fail, or as [dns.fallback-filter] decides
direct-follow-policyfalsealso apply nameserver-policy to direct names

Resolver formats:

FormatProtocolDefault port
192.168.1.1 or udp://192.168.1.1UDP53
tcp://192.168.1.1TCP53
tls://dns.example.com (or dot://)DNS over TLS853
https://dns.example.com/dns-queryDNS over HTTPS443

Add :port to override the port. With the eBPF backend, names that route DIRECT are always resolved by the system resolvers, so direct traffic behaves as it does without Specola.

Per-domain resolvers

toml
[dns.nameserver-policy]
"+.corp.example.com" = "udp://10.0.0.53"
"build.internal.example.org" = ["udp://10.1.0.53", "udp://10.1.0.54"]

Keys are an exact name, +. followed by a suffix (the name and its subdomains), or geosite:<code>. The longest matching key wins. Policies apply to names that do not route direct, and also to direct names when direct-follow-policy = true.

Fallback filter

[dns.fallback-filter] decides when fallback resolvers are used even though the primary ones answered. Fields: geoip (boolean), geoip-code (default "CN"), ipcidr and domain (lists).

Route resolution

Should Core look up a name just to test IP rules? route-resolution decides, separately for the local port ([dns.system-proxy]) and Enhanced Mode ([dns.tun]):

ValueBehavior
domain-only (default)never; names that match no domain rule go to FINAL
unknown-domainonly when no domain rule matched and FINAL is a proxy
alwayslook up every name and compare domain and IP rules in list order

unknown-domain and always need at least two probe-resolver entries, queried in parallel; with fewer, Core logs a warning and uses domain-only.

toml
[dns.system-proxy]
route-resolution = "unknown-domain"
probe-resolver = ["udp://1.1.1.1", "udp://8.8.8.8"]
probe-timeout-ms = 1500

Prefer writing a domain rule over enabling lookups.

General fields

FieldDefaultNotes
ipv6falseanswer AAAA queries; keep false with the eBPF backend, which captures IPv4 only
respect-rulestruesend a name's DNS query through the proxy its rule selects
disable-cachefalse
cache-capacity4096minimum 128
cache-min-ttl10seconds
negative-cache-ttl30seconds
query-timeout-ms5000minimum 100
use-hoststrue
use-system-hoststrueread /etc/hosts

[dns.hosts] enable = true is not implemented and is rejected; use the system hosts file.

Local DNS listener

Core can also serve DNS on a port, for other programs you point at it manually. It is off by default and is not needed for Enhanced Mode.

toml
[dns.listener]
enable = true
address = "127.0.0.1:1053"

Older field names

Flat fields such as [dns] enable, listen, nameserver, default-nameserver, enhanced-mode and fake-ip-range are still read, with a warning. Move them into the tables above: nameserver[dns.upstream] default, default-nameserverbootstrap, listen[dns.listener] address, enhanced-mode[dns.tun] mode.