Pipe Manual

Profile manual

DNS

On this page
  1. Where an answer comes from
  2. Named servers and ordered DNS rules
  3. Servers
  4. [Host]
  5. Names of one label
  6. system on iPhone
  7. Fake IPs
  8. DNS sent elsewhere
  9. DNS scripts
  10. Per-network servers

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:

  1. rules marked pre-matching, which refuse the names they match;
  2. [Host] entries, in the order they are written — addresses, another name, servers of their own, or a script;
  3. localhost and 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;
  4. [DNS Rule] lines, in their written order;
  5. 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-server only look up the encrypted servers' own names.

  • off in the list is skipped (it is for [SSID Setting]); a plain address is warned about and left out — it belongs under dns-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 —
Google 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 under encrypted-dns-server, with dns-server = system kept 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-server other than system, encrypted-dns-server, dns-final or 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. With ipv6 = 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> or RULE-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, from fd00:6152::/64, and DNS is reached at fd00:6152::2. Elsewhere an AAAA query that would be answered with a fake address gets no record.
  • always-real-ip lists 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.net is 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 ttl it 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.