Profile manual
DNS
On this page
How Pipe answers for names: the servers [General] names, the [Host]
section, and the fake IPs the tunnel hands out.
[General]
dns-server = system, 9.9.9.9, 1.1.1.1
encrypted-dns-server = https://dns.google/dns-query, quic://dns.adguard-dns.com
always-real-ip = *.srv.nintendo.net, *.stun.playstation.net
[Host]
router.home = 192.168.1.1
*.corp.example = server:10.0.0.53, 10.0.0.54
cdn.example = cdn.example.net
Where an answer comes from#
A name is answered by the first of these that has an answer:
- rules marked
pre-matching, which refuse the names they match; [Host]entries, in the order they are written — addresses, another name, servers of their own, or a script;localhostand the names under it (proxy.localhost), which are this device: 127.0.0.1 and ::1, so a proxy listening on the device can be named so;[DNS Rule]lines, in their written order;- the servers selected by
dns-final, or the ordinary server race.
For the tunnel's A and AAAA queries, DNS reject and predefined actions are applied before allocating a fake address. A rule that routes or evaluates a query asks for a real DNS answer. Names no DNS rule handles keep the usual fake-IP behavior below.
Named servers and ordered DNS rules#
[DNS Server] gives resolvers names. [DNS Rule] chooses what to do with
each query, using the same quoted values and logical-rule grammar as
traffic rules. These DNS rules are separate from [Rule], which routes
connections. They do not become [Host] entries.
[General]
dns-final = secure
dns-strategy = prefer_ipv4
[DNS Server]
bootstrap = 9.9.9.9
secure = https://dns.example/dns-query, domain-resolver=bootstrap
backup = 1.1.1.1
[DNS Rule]
DOMAIN-SUFFIX,ads.example,REJECT
AND,((DOMAIN-SUFFIX,legacy.example),(QUERY-TYPE,AAAA)),PREDEFINED,rcode=NOERROR
DOMAIN,missing.example,PREDEFINED,rcode=NXDOMAIN
DOMAIN,service.example,PREDEFINED,answer="service.example. 60 IN A 192.0.2.8"
FINAL,ROUTE-OPTIONS,rewrite-ttl=60
FINAL,EVALUATE,server=secure,tag=checked
RESPONSE-RCODE,SERVFAIL,backup,response-tag=checked
FINAL,RESPOND,response-tag=checked
[Rule]
FINAL,DIRECT
Server definitions#
A definition is name = address-or-URL, options. It accepts the existing
DNS URL forms, udp://name:port, and system (also local). A system
definition keeps the whole set of resolvers captured from the network,
including updates when the network changes. Defining a server does not
make it the default: use dns-final = name to select it. Without
dns-final, the existing dns-server/encrypted-dns-server race remains.
| Server option | Meaning |
|---|---|
domain-resolver=name |
Another named resolver looks up this server's hostname. Undefined names and bootstrap cycles are errors. |
skip-cert-verification=true |
Disables certificate verification for this TLS, HTTPS, QUIC or HTTP/3 server. Modules cannot enable this. |
URL fragments retain their routing meaning: tls://dns.example#Proxy
uses that proxy or group; tls://dns.example#RULES follows the live
traffic rules. An undefined policy is an error. Names and references
are case-sensitive; quote names or URLs containing commas or comment
characters. dns-strategy explicitly selects as_is, prefer_ipv4,
prefer_ipv6, ipv4_only or ipv6_only, independently of the default
chosen by ipv6.
Conditions and order#
| Condition | Value |
|---|---|
DOMAIN |
One exact name. |
DOMAIN-SUFFIX |
A name and its descendants. A leading dot matches descendants only. |
DOMAIN-KEYWORD |
Literal text in the normalized question name. |
DOMAIN-REGEX |
A regular expression over the normalized question name. |
QUERY-TYPE |
A DNS type name or number, such as A, AAAA, HTTPS or 65. |
RESPONSE-RCODE |
A response code name or number. Requires a response binding. |
RESPONSE-ANSWER, RESPONSE-NS, RESPONSE-EXTRA |
A quoted DNS record to find in that response section. Record TTL does not affect equality. |
IP-CIDR |
An address range matched against an evaluated response. |
IP-IS-PRIVATE, IP-ACCEPT-ANY |
true; use NOT to negate an address condition. Requires a response binding. |
AND, OR, NOT |
Nested conditions, as in traffic rules. NOT takes one child. |
FINAL |
An unconditional condition. Unlike traffic FINAL, a nonterminal DNS action can be followed by more rules. |
Question names are normalized to lowercase without the final dot.
Configured domain, suffix and keyword literals retain their spelling:
use lowercase without a trailing dot, following sing-box's DNS matching.
Regex patterns retain case; (?i) opts into case-insensitive matching.
invert=true negates a condition, while NOT expresses inversion in a
logical tree. An empty default condition remains unconditional even
with invert, following sing-box's behavior.
DNS conditions can use RULE-SET, DOMAIN-SET and GEOSITE with the
same named sections, local resources and remote lists as traffic rules.
The referenced list must contain only domain, suffix, keyword or regex
entries. Mixed address, process or logical entries, undefined names and
partially parsed lists are rejected as whole lists. Combine a list with
other conditions in the DNS rule itself:
[DNS Server]
primary = 192.0.2.53
backup = 9.9.9.9
[Ruleset Regional]
DOMAIN-SUFFIX,corp.test
DOMAIN,portal.test
[DNS Rule]
AND,((RULE-SET,Regional),(QUERY-TYPE,A)),primary
FINAL,backup
[Rule]
FINAL,DIRECT
Here primary is an example corporate resolver address. DNS and traffic
share the same compiled list storage. Reloads and remote refreshes update
both; a rejected refresh keeps the previous active and cached list. A remote
list with no cached copy starts empty until its first successful download,
following the traffic rule-set loader's startup behavior.
The first matching terminal action ends the program. ROUTE-OPTIONS
and EVALUATE continue to the next rule. An unhandled query uses
dns-final or the ordinary server race, with the accumulated options.
Actions#
| Action | Result |
|---|---|
| A server name | Routes to that named resolver. ROUTE,server=name is the explicit form, required when the name is an action word such as REJECT. |
REJECT |
Refuses the query. method=drop drops it; no-drop=true keeps a refusal from escalating to a drop. |
REJECT-DROP |
Leaves the query unanswered. |
PREDEFINED |
Builds a response from rcode, repeated answer, ns and extra record options. rcode=NOERROR with no records is NODATA; rcode=NXDOMAIN marks the name absent. |
ROUTE-OPTIONS |
Accumulates DNS exchange options for later actions. |
EVALUATE |
Queries server=name, saves the response, and continues. tag=name saves a named response; without a tag it becomes the current anonymous response. |
RESPOND |
Returns the response selected by the rule's response binding. |
Use match-response=true for the most recent anonymous evaluation, or
response-tag=name for a named one. The latter always means a name,
even for a tag literally called true or false. match-response=name
also accepts ordinary non-boolean names. Bindings can be attached to a
logical condition or its children; the outer binding also chooses what
RESPOND returns. Without an outer binding, RESPOND uses the latest
anonymous evaluation; a child's binding does not change that selection.
Every referenced evaluation must occur earlier in the
program. Undefined, repeated or forward-referenced tags are errors.
Rules are visited in written order. An evaluation is awaited when a
rule needs its result; evaluations no longer needed after a terminal
action are canceled. In the example above, secure is asked first; a
SERVFAIL response selects backup, and every other response is returned
by RESPOND.
race=true lets a terminal response rule compete with other racing rules
while its evaluated answer is pending. The first matching racing rule wins;
a later ordinary fallback waits until preceding candidates have failed.
speculative=true starts a ROUTE or EVALUATE query early while a preceding
racing response rule is pending, but keeps its rule's precedence. It does
nothing without a preceding race; ordinary response rules still block later
rules. A successful earlier rule still wins. Losing queries are canceled, and each deferred rule keeps
the evaluated-response binding it had at its position in the program.
Records use DNS master-file syntax with fully qualified owner names and
names inside record data: write the final unescaped dot. Relative names
and @ have no implicit origin and are rejected.
An omitted TTL is 3,600 seconds. Text records keep quoted strings;
unsupported presentation types can use RFC 3597 data or a base64 wire
record. The parser checks record counts, token counts and encoded size
before accepting a predefined response.
Exchange options#
ROUTE, EVALUATE and ROUTE-OPTIONS accept disable-cache,
disable-optimistic-cache, rewrite-ttl, timeout, client-subnet and
remove-client-subnet. Boolean options accept true/false; a bare
flag means true. rewrite-ttl is an unsigned 32-bit count of seconds,
including zero. timeout uses a duration such as 500ms or 3s.
Cache and in-flight lookup identities include the effective exchange
options, so changing one rule's cache or TTL behavior does not change
another rule's answers. Explicit client-subnet controls also participate
in that identity. Ordinary cached lookups share an in-flight exchange;
evaluations own their cache-miss exchanges so that an unused evaluation
can be canceled independently. Identical simultaneous evaluations may
therefore ask the upstream separately.
Modules, editing and limits#
Named servers and evaluated responses belong to the profile or to the module that defines them. A module's own name is resolved first, then the profile's names; another module's private definitions are not visible. A module cannot replace a profile-owned server by reusing its name or weaken a server's certificate checks. Invalid module DNS lines roll back that module's servers and rules together. The module summary reports its DNS reach.
The profile editing API exposes named resolvers and ordered DNS rules — a logical one as its conditions, nested ones too — checks stale edits, and updates server references when a resolver is renamed. In the app, each DNS rule is a form whose options are fields.
Native DNS sections allow at most 256 named servers, 1,024 top-level rules and 256 KiB of text, with logical nesting limited to ten levels. Parsing and validation draw from the shared compile allowance across module retries. Exceeding these native section limits or the shared compile allowance fails the whole compilation; it is not downgraded to omitting a module and changing the remaining policy. The engine applies its additional rule, record and regex limits and reserves runtime rule storage from its memory budget.
Servers#
dns-server#
The servers names are looked up at. They are asked at once, and the
first good answer wins. Without the key, system.
| Written | Is |
|---|---|
system |
The network's own resolvers (below). |
9.9.9.9 |
A server over UDP, on port 53. |
1.1.1.1:5353, [2606:4700:4700::1111]:53 |
With a port. |
tcp://9.9.9.9, tcp://dns.example:5353 |
Over TCP, on port 53 unless it says. |
https://…, h3://…, quic://…, tls://…, sdns://… |
An encrypted server: read as if it were written under encrypted-dns-server. |
- With
ipv6 = false, the default, IPv6 server addresses are left out, with a note. - A server Pipe cannot read is left out, with a warning, and the rest stand.
tcp://servers count with the encrypted ones: when a profile has any, they answer.
encrypted-dns-server#
| Written | Is | Port |
|---|---|---|
https://dns.google/dns-query |
DNS over HTTPS. The path is /dns-query unless the URL gives one. |
443 |
h3://dns.example/dns-query |
DNS over HTTP/3. | 443 |
quic://dns.adguard-dns.com |
DNS over QUIC. | 853 |
tls://1.1.1.1 |
DNS over TLS. | 853 |
sdns://… |
A DNS stamp: a DNSCrypt server, or any of the above. | the stamp's; 443 for DNSCrypt |
encrypted-dns-server = sdns://AQMAAAAAAAAAETk0LjE0MC4xNC4xNDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20
A DNSCrypt stamp (version 2) gives the server's address and its provider's name and key. Pipe asks the server for its certificates in plain DNS, takes the newest one its provider signed that is valid now (so the device's clock must be right), and seals every query with it — X25519 with XSalsa20-Poly1305 or XChaCha20-Poly1305, as the certificate says — over UDP, and over TCP for an answer too large. It asks again every four hours, when the certificate expires, and when a query goes unanswered: a server rotates its keys.
A stamp for DNS over HTTPS, TLS or QUIC is the server its URL would be. Its certificate hashes and bootstrap address are not used: the server's certificate is checked against its name, as any encrypted server's is. A plain DNS stamp belongs under
dns-server; relay and Oblivious DoH stamps are refused with a warning.With any encrypted server, the encrypted servers answer for every name, and the plain servers of
dns-serveronly look up the encrypted servers' own names.offin the list is skipped (it is for[SSID Setting]); a plain address is warned about and left out — it belongs underdns-server.encrypted-dns-skip-cert-verification = true: the encrypted servers' certificates are not checked.encrypted-dns-follow-outbound-mode = true: DNS over TCP, TLS, HTTPS, QUIC and HTTP/3, and DNSCrypt, follows the current mode, rules and group selection. Each query checks the route before reusing a connection; changing mode or selecting another group member moves subsequent queries to that path. The resolver's actual port and bootstrapped address participate in matching. Rejected resolver traffic fails instead of falling back direct. Rule scripts are not run for the engine's own DNS connections.
Sending DNS through a proxy#
Append #Policy to a UDP address, a TCP, TLS, HTTPS, QUIC or HTTP/3 URL, or a DNS stamp to pin that
resolver to a proxy or group. #RULES asks the current routing policy instead.
These fragments work in [Host] server assignments too. An explicit policy
wins over encrypted-dns-follow-outbound-mode.
[General]
dns-server = system
encrypted-dns-server = tls://1.1.1.1#Proxy
[Proxy]
Proxy = socks5, proxy.example, 1080
[Host]
*.corp.example = server:10.0.0.53#Proxy
[Rule]
FINAL,Proxy
dns-bootstrap-server adds resolvers used only for proxy and DNS server names;
they do not race ordinary destination queries. For example, pair
dns-server = 10.0.0.53#Proxy with dns-bootstrap-server = system.
Keep a directly reached resolver for bootstrapping proxy and DNS server names. The compiler rejects a fragment naming an unavailable policy. A failed proxy path never retries the query directly; a truncated UDP response retries over TCP through the same policy. The selected proxy must support the transport it is asked to carry. QUIC and HTTP/3 require UDP support and keep the resolver's TLS identity even when a routing rule replaces its network destination. Each connection has bounded QUIC windows and admitted datagram queues; a failed UDP association closes the connection without opening a direct socket.
When ROUTE-OPTIONS replaces a resolver with another hostname, resolving
rules later in that resolver's route use bootstrap DNS too. This prevents
the resolver from waiting on itself. Ordinary client traffic and engine
downloads keep their normal DNS resolution scope.
DNS presets#
The default profile asks system, the network's own resolvers. In
their place the app can ask a public resolver, chosen from these presets
— each in plain DNS, over HTTPS or over TLS, and with DNSCrypt where the
provider publishes stamps of its own:
| Preset | Plain DNS | Over HTTPS | Over TLS | DNSCrypt |
|---|---|---|---|---|
| System (the default) | the network's own resolvers | — | — | — |
| Cloudflare | 1.1.1.1, 1.0.0.1 |
https://1.1.1.1/dns-query, https://1.0.0.1/dns-query |
tls://1.1.1.1, tls://1.0.0.1 |
— |
8.8.8.8, 8.8.4.4 |
https://8.8.8.8/dns-query, https://8.8.4.4/dns-query |
tls://8.8.8.8, tls://8.8.4.4 |
— | |
| Quad9 — blocks domains known to spread malware | 9.9.9.9, 149.112.112.112 |
https://9.9.9.9/dns-query, https://149.112.112.112/dns-query |
tls://9.9.9.9, tls://149.112.112.112 |
Quad9's stamps for 9.9.9.9:8443 and 149.112.112.112:8443 |
| AdGuard — blocks ads and trackers | 94.140.14.14, 94.140.15.15 |
https://94.140.14.14/dns-query, https://94.140.15.15/dns-query |
tls://94.140.14.14, tls://94.140.15.15 |
AdGuard's stamp for 94.140.14.14:5443 |
| AliDNS | 223.5.5.5, 223.6.6.6 |
https://223.5.5.5/dns-query, https://223.6.6.6/dns-query |
tls://223.5.5.5, tls://223.6.6.6 |
— |
- A plain preset is its servers under
dns-server. An encrypted one is its servers underencrypted-dns-server, withdns-server = systemkept for the network's search domains alone. - The encrypted servers are given by address — every provider here names its addresses in its certificate, and a DNSCrypt stamp holds its server's address and key — so nothing is looked up before the first query, at the network's resolvers or anywhere else.
- A preset is the app's setting, not a line of the profile — the default
DNS: every profile that leaves its DNS to the network asks it, the
default profile among them, edited or not. A profile that says DNS
servers of its own (
dns-serverother thansystem,encrypted-dns-server,dns-finalor a[DNS Server]section) keeps them, and the preset does not apply to it.
Racing and caching#
- The servers that answer for a name are asked at once; the first good answer is used.
- Answers are cached for their TTL, capped at one day. An answer past its time is still given for up to ten minutes while it is fetched again in the background, so that a lookup rarely waits.
- With
ipv6 = false, names are looked up for IPv4 addresses alone. Withipv6 = true, for both, IPv4 preferred.
[Host]#
[Host] gives names answers of their own: name = value, one per line.
[Host]
router.home = 192.168.1.1
nas.home = 192.168.1.20, fd00::20
*.dev.example = 10.0.0.8
cdn.example = cdn.example.net
*.corp.example = server:10.0.0.53, 10.0.0.54
*.lan = server:system
printer.local = server:force-syslib
geo.example = script:geo-answer
DOMAIN-SET:https://example.com/blocked.txt = 0.0.0.0
| Value | Answer |
|---|---|
addresses: 192.168.1.1, 192.168.1.20, fd00::20 |
Those addresses, of either family. |
one name: cdn.example.net |
The lookup starts again with that name, and the answer says so with a CNAME. |
server:<servers> |
The name is asked of these servers alone: plain or encrypted servers, written as dns-server and encrypted-dns-server take them, several separated by commas and raced. server:system is the network's own resolvers; server:force-syslib the system's resolver itself. |
script:<name> |
A DNS script answers. |
- Keys are names, exactly, or patterns with
*(any run of characters) and?(one character), in any case. - Entries are tried in the order they are written; the first whose key matches decides.
- A list can stand for the key:
DOMAIN-SET:<file or URL>orRULE-SET:<file or URL>, the same lists rules take, kept current the same way. Only the list's name entries can match, since nothing has been looked up yet. Such a key ends at=, with spaces, since a URL may hold=of its own. A[Ruleset <name>]section cannot be a key. - A value that is neither addresses nor one name is warned about, and the
line is left out. So is
server:naming no server Pipe can use; a server it cannot read is left out of the list, with a warning. - A line naming a script the profile does not have as a DNS script is an error.
- Servers that only
[Host]entries name are asked about those entries' names alone. - Answers from
[Host]addresses are given for ten minutes.
Through the tunnel, a name [Host] gives addresses to still gets a
fake IP, like any other: the mapping applies when the
connection is made. A connection that goes out directly goes to the
[Host] address. A connection through a proxy gives the proxy the name,
unless use-local-host-item-for-proxy is on: then a
TCP connection goes to one of the [Host] addresses, picked at random.
/etc/hosts#
On a Mac, read-etc-hosts = true answers for the names in /etc/hosts
too, after the profile's [Host] entries:
[General]
read-etc-hosts = true
Its names are answered as [Host] addresses are. The file is looked at
again as names are asked for, every few seconds at most, so a change is
there for the next lookup, and if the file goes, so do its names. Up to
16,384 names are read from a file of up to 1 MiB, with eight addresses
each; a larger file leaves things as they were, with a warning. A
module cannot turn this on.
Names of one label#
A name without a dot — nas — is looked up with the network's first
search domain added (nas.home.example), at the network's own
resolvers. On iPhone the network's search domains are read with its
resolvers, from the system's DNS configuration (the local domain of
multicast DNS aside): by the app when it compiles the profile, and by
the tunnel each time it starts, before its own DNS takes over.
system on iPhone#
In the tunnel, system is asked of iOS's own resolver, about the network
the phone is on, rather than at the addresses read when the tunnel
started: it stays the network's own resolvers when the phone moves from
Wi-Fi to cellular and back. The search domains stay the ones read when
the tunnel started.
Fake IPs#
Through the tunnel, names are answered with fake IPs: addresses from
198.18.0.0/15 that stand for the names. An app then connects to a fake
IP, Pipe puts the name back, and the rules decide by the name. The name
is really looked up only when a rule needs its address, or when the
connection goes out directly. The table of fake IPs is kept across
restarts, so that an app holding one still reaches its name.
- On a Mac whose tunnel carries IPv6 (
ipv6-vif), AAAA queries are answered the same way, fromfd00:6152::/64, and DNS is reached atfd00:6152::2. Elsewhere an AAAA query that would be answered with a fake address gets no record. always-real-iplists the names that get real answers instead — from[Host]first, then the servers. It is a host list: patterns with*and?, tried in order, the first that matches deciding;-in front of one gives what it matches a fake IP after all. An item that cannot be read is left out, with a warning.- The profile's items are tried first, then Pipe's own, which always get
real answers unless an item of the profile says otherwise:
*.lan,*.local,*.localdomain,*.home.arpa,localhost,captive.apple.com,*.msftconnecttest.com,*.msftncsi.com,connectivitycheck.gstatic.com,time.*.com,time.*.apple.com,ntp.*.com,*.ntp.org,stun.*and*.stun.*— local names, captive-portal and connectivity checks, time servers, and STUN, which needs real addresses to work. - A reverse lookup of a fake IP is answered with its name.
- SVCB and HTTPS records are answered with no record of them, since the
addresses they carry would go past the fake IPs, unless
allow-dns-svcb = true: then they are asked of the servers. use-application-dns.netis answered as a name that does not exist, which tells a browser that would send its lookups over DNS of its own — Firefox — to use the system's.
[General]
always-real-ip = *.srv.nintendo.net, -fake.example.lan, *.example.lan
DNS sent elsewhere#
Through the tunnel, every DNS query sent to port 53 is answered by
Pipe, over UDP and TCP both, whatever address it was sent to: an app
that asks 8.8.8.8 itself gets Pipe's answer. Other apps name such
addresses with hijack-dns (8.8.8.8:53, *:53), which is no setting
of Pipe's: a conversion leaves it out.
DNS scripts#
[Host]
*.dynamic.example = script:geo-answer
[Script]
geo-answer = type=dns, script-path=https://example.com/geo-answer.js
name = script:<name> asks the DNS script of that name for the name's
addresses (DNS scripts).
- Its answer is kept for the
ttlit gives, in seconds; one that gives none is asked again each time. - An answer that names servers has those servers asked.
- An answer that says nothing, or a script that fails, leaves the name
to be looked up as if
[Host]did not have it.
Per-network servers#
[SSID Setting] lines give some networks DNS
servers of their own, asked in place of the profile's.