Profile manual
Rules
On this page
[Rule] holds the rules. Each connection is tried against them from the
top, and the first terminal rule that matches decides the policy it goes to.
ROUTE-OPTIONS changes destination fields and continues to the next rule. FINAL,
the last, takes everything no other rule matched.
[Rule]
RULE-SET,LAN,DIRECT
DOMAIN-SUFFIX,doubleclick.net,REJECT,pre-matching
DOMAIN-SUFFIX,github.com,Proxy
DOMAIN-KEYWORD,google,Proxy
IP-CIDR,10.8.0.0/16,Office,no-resolve
RULE-SET,https://example.com/streaming.list,Streaming
AND,((DOMAIN-SUFFIX,example.com),(PROTOCOL,UDP)),REJECT
GEOSITE,category-ads-all,REJECT
GEOIP,REGION,DIRECT
FINAL,Proxy,dns-failed
How rules are tried#
- Rules are tried in the order they are written. The first that matches decides; the rules after it are not tried.
FINALcomes last, once. A profile without it is an error. A rule afterFINALnever applies, and is warned about.- So does a rule that an earlier one covers —
DOMAIN,www.example.comafterDOMAIN-SUFFIX,example.com, a network inside an earlier rule's networks — and the warning on its line names the rule that comes first. An address rule withno-resolvedoes not cover a later one without: that one also sees the addresses names lead to. - Rules marked
pre-matchingare tried before the others, in their own order. - The rules of modules switched on are tried before the profile's own.
- A connection is decided once, when it is made, by the name it was made
to (on the tunnel, the name behind its fake IP), its address, its
ports and what else a rule asks of it. A request the
HTTP engine carries is decided by itself, with its URL and
its
User-Agenttoo. - In Global and Direct modes the rules are not tried — but with an exit node chosen, when they are, in every mode, for what they refuse (How a connection is decided). What a tailnet or a WireGuard network claims never reaches them.
The line#
TYPE,VALUE,POLICY,option,…
DOMAIN-SUFFIX,example.com,Proxy
IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
URL-REGEX,"^https?://ads\.example\.com/(a,b)",REJECT
FINAL,Proxy,dns-failed,notification-text="Going through the proxy"
- The type is read in any case. A type Pipe does not know is an error.
- The value is what the rule matches. A value holding a comma is quoted. A value Pipe cannot read — an address range that is not one, a port that is not a number — is an error.
- The policy is a proxy, a group or a built-in policy. A policy the profile does not have is an error. A proxy Pipe cannot use, and a group in a loop, refuse what the rule matches.
- Options follow the policy: flags such as
no-resolve, andkey=valueoptions.
| Option | On | What it does |
|---|---|---|
no-resolve |
address rules, RULE-SET, DOMAIN-SET |
Match only an address the connection came with: never look a name up for this rule, nor take the address another rule looked up (Address rules on names). |
extended-matching |
name rules, URL-REGEX, RULE-SET, DOMAIN-SET |
Also try the names the connection's first bytes give (extended-matching). |
pre-matching |
rules whose policy is a REJECT one |
Try the rule first, and at DNS time too (pre-matching). |
notification-text, notification-interval |
any rule, FINAL too |
Post a notification when the rule decides a connection (notification-text). |
dns-failed |
FINAL |
Send connections whose lookup failed here too (Address rules on names). |
requires-resolve |
SCRIPT |
Give the script the name's addresses (SCRIPT). |
override-address, override-port |
matching rules with a routing policy | Replace the destination host or port after matching (Destination overrides). |
update-interval, format |
RULE-SET, DOMAIN-SET |
How often a list is fetched again, and what format it is in (Rule sets). |
always-capture is noted: capturing a rule's requests is not supported
yet. Other options are read past.
A rule whose policy is DEVICE:<name> — another device to send traffic
through — is not supported: it refuses what it matches, with a warning.
Destination overrides#
A matching rule can send its connection to another host, another port, or both, through its selected policy:
[Rule]
DOMAIN,service.example,DIRECT,override-address=192.0.2.20,override-port=8443
FINAL,DIRECT
The address is an IP address or a host name of at most 255 bytes; the port is from 1 to 65535. Omit either option to keep that part of the original destination. Both TCP and UDP use the replacement, including a policy group's selected member and its fallbacks. UDP replies retain the original source identity expected by the client. An overridden UDP target that is a host name is resolved through Pipe's DNS before opening the proxy session, so replies can be checked against a fixed numeric peer. Replies from other sources are discarded before restoring the original identity. Ordinary UDP flows without overrides keep their existing DNS behavior.
A terminal override changes the destination only after its own matcher has run. Connection records keep the original destination. HTTP requests keep their URL and Host header, and HTTPS keeps the original TLS server name; only the connection target changes. The effective target also controls DNS lookup and reuse of HTTP connections.
These options belong on a matching rule, including the outer line of a
logical rule. They are errors on FINAL, a reject policy, a logical
sub-rule or a rule-set entry. Invalid or repeated values fail compilation.
Continue with route options#
ROUTE-OPTIONS is a rule action, not an outbound. It applies the specified
destination fields and continues matching. Later rules see that effective
host and port:
[Rule]
DOMAIN,service.example,ROUTE-OPTIONS,override-address=192.0.2.20
IP-CIDR,192.0.2.20/32,ROUTE-OPTIONS,no-resolve,override-port=8443
DEST-PORT,8443,DIRECT
FINAL,REJECT
Address and port accumulate independently. A later supplied field replaces
the earlier value; omitted fields keep their values. A terminal rule's
own override wins for that field. Accumulated values also apply when
FINAL selects the policy. A later reject still refuses the connection.
Changing the address discards its previous lookup. Rule evaluation resumes
at the rule that asks for a new lookup or script answer, so earlier options
are not applied a second time. Address or port changes clear cached script
answers. Sniffed names remain available to explicit extended-matching;
ordinary domain rules match the effective host.
ROUTE-OPTIONS requires at least one destination or runtime control.
The name is reserved
and cannot name a proxy, group or FINAL policy. These actions run only
when the profile rules run: Direct/Global mode retains its existing
precedence.
The following controls also work on terminal routing rules. They follow
selected group members and their fallback members; FINAL inherits earlier
controls but does not accept options itself.
| Option | Behavior |
|---|---|
udp-timeout=30s |
Close an idle UDP session after this much time without a successful send or receive. SOCKS/TUN flows retain their inbound maximum of 60 seconds. Standalone routed DNS/tool sessions use the requested timeout. Zero leaves an earlier timeout in place. |
tls-fragment=true |
Split the first application TLS ClientHello inside SNI, flush the first part, then wait tls-fragment-fallback-delay (default 500ms) before sending the rest. |
tls-record-fragment=true |
Put the two ClientHello parts in separate TLS records. May be combined with tls-fragment. |
network-strategy=default |
Try selected physical interfaces sequentially for direct TCP. hybrid races them. fallback races preferred interfaces, then adds fallback interfaces after fallback-delay (default 300ms) or immediately when any preferred attempt fails. |
network-type=wifi|ethernet |
Select preferred physical interface types: wifi, cellular, ethernet, other. An omitted filter uses the default interface, or all reported interfaces for hybrid. |
fallback-network-type=cellular |
Select the other interfaces used by fallback; omission uses all remaining types. |
Durations accept ns, us, ms, s, m, h and combinations such as
1m30s; a bare native number means seconds. They are nonnegative and capped
at one day. Later positive durations and nonempty interface lists replace
earlier values. TLS flags latch true; false does not undo an earlier true.
A new true tls-fragment also replaces its delay, using the default if
omitted. An explicit network-strategy=default replaces an earlier strategy.
Fragmentation runs after the selected proxy handshake, including CONNECT. Pipe inspects a complete first TLS record up to 16 KiB, assembling partial writes. Non-TLS, malformed, no-SNI and ClientHellos spanning multiple TLS records pass through unchanged. An explicit flush of an incomplete record also passes it through. The stream abstraction has no TCP ACK timing, so packet fragmentation uses the configured fallback delay; multiplexed transports may coalesce writes and cannot promise TCP packet boundaries.
Network strategies apply to direct traffic. They require a current host report of interface names and kinds for filtering or racing, and fail explicitly when that data is absent. They preserve socket protection, interface binding and packet marks. Explicit policy interface, NO-HYBRID and Fast Open conflicts fail instead of silently discarding either setting. All losing TCP attempts are canceled. A fallback win accelerates fallback for 15 seconds; a subsequent preferred win clears that state. UDP tries socket creation/binding serially and cannot detect a blackholed remote path. NAT64 translation is limited to the active interface because the host provides one active-network prefix; alternate interfaces need native IPv4 or IPv6 reachability. Pipe retains its existing 60-second inbound default rather than sing-box's protocol-specific defaults.
Rule types#
| Type | Matches |
|---|---|
DOMAIN |
the name exactly |
DOMAIN-SUFFIX |
the name and every name under it |
DOMAIN-KEYWORD |
names that hold a word |
DOMAIN-WILDCARD |
names by a pattern with * and ? |
DOMAIN-REGEX |
names by a regular expression |
GEOSITE |
names on a domain list, by its name |
IP-CIDR, IP-CIDR6 |
the destination address, in a range |
GEOIP |
the destination address's country |
IP-ASN |
the destination address's autonomous system |
DEST-PORT |
the destination port |
SRC-PORT, IN-PORT |
the source port; the port of the listener that took the connection |
PROTOCOL |
TCP, UDP, HTTP, HTTPS, QUIC, STUN |
HOSTNAME-TYPE |
whether the destination is an address or a name |
SRC-IP |
where the connection comes from |
PROCESS-NAME |
the process that made it |
PROCESS-PATH-REGEX |
a pattern over its executable path |
USER-AGENT |
a request's User-Agent |
URL-REGEX |
a request's URL |
SUBNET, CELLULAR-RADIO, CELLULAR-CARRIER |
the network the device is on |
AND, OR, NOT |
other rules, combined |
SCRIPT |
what a rule script says |
RULE-SET, DOMAIN-SET |
the entries of a list |
FINAL |
everything else |
Names#
| Rule | Matches |
|---|---|
DOMAIN,www.apple.com,DIRECT |
www.apple.com, and no other name. |
DOMAIN-SUFFIX,apple.com,DIRECT |
apple.com and every name under it — www.apple.com, a.b.apple.com — but not myapple.com. A dot in front is ignored. |
DOMAIN-KEYWORD,google,Proxy |
Every name that holds google. |
DOMAIN-WILDCARD,*.example.com,Proxy |
Names by a pattern over the whole name: * is any run of characters, dots included; ? is one character; [ab] is one of those characters. *.example.com matches a.example.com and a.b.example.com, not example.com. |
DOMAIN-REGEX,^api\d+\.example\.com$,Proxy |
Names by a regular expression, found anywhere in the name unless anchored. Names have no case, so neither do the pattern's letters; (?-i) makes case count. |
GEOSITE,google,Proxy |
Names on the domain list of that name: the category of MetaCubeX's meta-rules-dat by the name, in any case — google, apple, category-ads-all, a country's (jp, de), a category's entries of one attribute (apple@cn) — fetched and kept current as RULE-SET keeps a list at a URL: GEOSITE,google is RULE-SET,https://raw.githubusercontent.com/MetaCubeX/meta-rules-dat/meta/geo/geosite/google.yaml. Pipe ships no list of its own. Until the list has been fetched, the rule matches nothing. |
Names are compared in lower case, without a trailing dot. A connection
made to an address rather than a name has no name: these rules do not
match it, unless the rule says
extended-matching and the connection's first
bytes give one. Through the tunnel, a connection made to a fake IP has
its name back before the rules are tried.
Addresses#
| Rule | Matches |
|---|---|
IP-CIDR,192.168.0.0/16,DIRECT |
Addresses in that range. One address alone is a range of one: IP-CIDR,1.1.1.1,Proxy. IPv6 ranges too. |
IP-CIDR6,2001:db8::/32,Proxy |
The same. |
GEOIP,JP,DIRECT |
Addresses in that country, by its two-letter code, in any case. GEOIP,UNKNOWN matches an address the country database places in no country — private and reserved space. GEOIP,REGION matches addresses in the country of the network the device is on (Your region). |
IP-ASN,13335,Proxy |
Addresses in that autonomous system: 13335 or AS13335. IP-ASN,UNKNOWN matches an address the database places in none. |
GEOIP looks addresses up in Pipe's built-in country table — every
country's addresses, from DB-IP's IP to Country Lite,
updated with Pipe — so that a rule for any country works offline, from
the first launch. A profile may name a country database of its own with
geoip-maxmind-url: once it has
arrived it answers instead, and the built-in table until then. IP-ASN
needs the ASN database
ip-asn-maxmind-url names: Pipe
has none built in, and without it IP-ASN rules are left out, with a
warning; until one has arrived, they match nothing. A database that says
it is of the other kind is refused, and said.
Your region
GEOIP,REGION matches addresses in the country of the network the
device is on now — wherever that is, the same rule for everyone:
GEOIP,REGION,DIRECT
- Found out. While the rules name
REGION, in Rule mode, Pipe finds the country out when it starts, each time the device joins another network, and when you switch back to Rule mode: one HTTPS request to Cloudflare's trace page (https://1.1.1.1/cdn-cgi/trace, thenhttps://www.cloudflare.com/cdn-cgi/traceif that fails), made directly from the network the device is on, never through a proxy. In Global and Direct mode it asks nothing. The address it says the request came from is looked up in the built-in table. What it found holds for that network; nothing is asked again until the next one. If the request fails, it is made again after 30 seconds, two minutes and ten minutes, then not until the next network. - Chosen. A region you choose in the app is the region, and nothing is asked.
- The device's. Where it cannot be found out, or you turned finding out off in the app, your device's own region setting stands in — and while a request that failed waits to be made again.
- Not known — and while it is being found out —
GEOIP,REGIONmatches nothing, and a connection goes on to the next rule.
A change applies from the next connection. The app shows the region in use, and where it comes from.
Ports and protocols#
| Rule | Matches |
|---|---|
DEST-PORT,443,Proxy |
The destination port: one port, a range (8000-9000), or a bound (>=1024, >1024, <=1024, <1024). |
SRC-PORT,>=50000,DIRECT |
The source port, written the same way. |
IN-PORT,6152,DIRECT |
The port of the listener that took the connection: the shared proxy, a forwarded port. A connection through the tunnel has none. |
PROTOCOL,UDP,REJECT |
What the connection carries: TCP, UDP, ICMP, HTTP, HTTPS, QUIC, STUN, MTPROTO. |
HOSTNAME-TYPE,SIMPLE,DIRECT |
What the destination is: IPv4 or IPv6 (an address), DOMAIN (a name), SIMPLE (a name without a dot). Written in exactly that case. |
PROTOCOL in detail:
| Value | Matches |
|---|---|
TCP |
TCP, HTTP, HTTPS and MTPROTO included |
UDP |
UDP, QUIC and STUN included |
ICMP |
IPv4 and IPv6 echo requests received by the tunnel; direct policies can forward them, while unsupported proxy policies drop them |
HTTP |
a request the HTTP engine carries in the clear |
HTTPS |
TLS whose ClientHello was read, an HTTP CONNECT tunnel, a decrypted request. A rule that asks for HTTPS has port 443 read ahead. |
QUIC |
a QUIC Initial packet, on any port |
STUN |
a STUN message |
MTPROTO |
what the built-in MTProto server takes |
DOH, DOH3, DOQ, DOT and DNS are left out with a warning: they
match DNS queries Pipe sends itself, which go to their servers without
the rules. Any other value is an error.
Where a connection comes from#
| Rule | Matches |
|---|---|
SRC-IP,192.168.20.0/24,DIRECT |
The connection's source: an address or a range. That is a proxy client's address, or, through the tunnel, the address of the app's on the device. |
PROCESS-NAME,Telegram,Proxy |
The process that made the connection: its executable's name (Telegram, chrome.exe), or its path when the value is written as one (PROCESS-NAME,/usr/bin/curl,DIRECT). Case does not count. On a Mac, Pipe finds the process of what comes through the tunnel, by the source address and port of its packets, and of a connection to its local proxy from the same computer; so does the CLI on macOS and Linux. A process of root's cannot be told from the app, and matches no such rule. The iOS app cannot identify a packet's process, so the rule never matches there. |
PROCESS-PATH-REGEX,^/opt/apps/[^/]+/bin/client$,Proxy searches the executable
path with a regular expression. Case counts; (?i) makes a pattern ignore case.
It has the same platform availability as PROCESS-NAME; a connection with no
executable path does not match. Imports preserve sing-box process_path_regex
in routing rules and in source and binary rule sets.
On a Mac serving the local network as its gateway, a connection can come from a device:
| Rule | Matches |
|---|---|
DEVICE-NAME,Apple-TV,Proxy |
The device it came from, by the name you gave it, or else the one it gave DHCP. * is any run of characters, ? one character, over the whole name. Case does not count. |
MAC-ADDRESS,aa:bb:cc:dd:ee:ff,Proxy |
The device it came from, by its hardware address, in any case, with colons or hyphens. Anything else is an error. |
SRC-IP sees a device's own address. On a phone, which serves no devices,
these two rules are left out with a warning.
Requests#
These rules look inside requests, and only a request the HTTP engine carries has what they look at: plain HTTP, and HTTPS that is decrypted.
| Rule | Matches |
|---|---|
USER-AGENT,Instagram*,Proxy |
The request's User-Agent, by a pattern over the whole of it: * is any run of characters, ? one character. Case counts. An HTTP CONNECT that sent a User-Agent is matched by it too. |
URL-REGEX,^http://example\.com/ads/,REJECT |
The request's URL — http://host/path?query, https://… for a decrypted one — by a regular expression found anywhere in it. Case counts. |
The network the device is on#
| Rule | Matches |
|---|---|
SUBNET,SSID:Home*,DIRECT |
Connections made on a network a subnet expression names: SUBNET,TYPE:CELLULAR,Proxy, SUBNET,ROUTER:192.168.1.1,DIRECT. The expression has its prefix: SUBNET,Home,DIRECT is an error. |
CELLULAR-RADIO,NR,Proxy |
Connections made on cellular, with that radio: LTE, NRNSA, NR, WCDMA, …, in any case. |
CELLULAR-CARRIER,001-01,DIRECT |
Connections made on that carrier's cellular network, by its name or its MCC and MNC. |
They go by what the app last said of the network, connection by connection.
Logical rules#
AND,((DOMAIN-SUFFIX,example.com),(DEST-PORT,443),(PROTOCOL,TCP)),Proxy
OR,((DOMAIN-KEYWORD,ads),(DOMAIN-KEYWORD,tracker)),REJECT
NOT,((DOMAIN-SUFFIX,example.com)),DIRECT
AND,((NOT,((SRC-IP,192.168.1.10))),(DEST-PORT,22)),REJECT
ANDmatches when every rule inside matches,ORwhen any does, andNOTwhen its one rule does not.- The rules inside are written in parentheses, without a policy, each
with its options:
(DOMAIN,a.example,extended-matching). They may be logical rules themselves, ten deep at most. NOTtakes exactly one rule;ANDandORat least one.- A logical rule one of whose rules Pipe cannot evaluate is skipped
whole, with a warning that names that rule.
SCRIPTinside one is such a rule;FINALinside one is an error. - A name is looked up only when an address could change the outcome:
AND,((DOMAIN-SUFFIX,example.com),(GEOIP,JP)),DIRECTnever looks up a name outsideexample.com.
SCRIPT#
[Rule]
SCRIPT,office-hours,Work,requires-resolve
[Script]
office-hours = type=rule, script-path=https://example.com/office-hours.js
A rule script decides whether the rule matches, when
the rules reach it. It is asked once per connection, however many rules
name it. With requires-resolve, the name is looked up first and the
script is given its addresses. A rule naming a script that is not a rule
script of the profile is an error.
FINAL#
FINAL,Policy takes every connection no other rule matched. It comes
once, last. Its options are dns-failed and notification-text.
Address rules on names#
A connection made to a name has no address until the name is looked up.
An address rule — IP-CIDR, IP-CIDR6, GEOIP, IP-ASN, and the
address entries of a RULE-SET — looks it up when the rules reach it,
unless it says no-resolve.
- The lookup goes through the profile's DNS, and is given three seconds.
- It happens only when an address could change the outcome: the rules are walked in order, and a lookup is made at the first rule whose verdict depends on the address — not before, and not at all when a rule that needs no address matches first.
- Once made, the walk continues at the rule that requested the address.
That rule and later address rules that may look a name up see it.
Earlier route options are not replayed. A rule that says
no-resolvedoes not see the address. - It decides the route alone. A connection that goes through a proxy still gives the proxy the name, never the address looked up here.
- When the lookup fails, the connection is refused — unless
FINALsaysdns-failed. Then the rules go on without an address: address rules do not match it, and if no other rule does, it goes toFINAL.
no-resolve on a rule means: match only an address the connection came
with — that of an app that connected to an address, not to a name — and
never one a lookup found, whether this rule's or another's. Such a rule
never takes a name for the addresses it is looked up to: a public name
whose DNS answers with your network's addresses, or your machine's own
(what a DNS rebinding attack makes it answer), is not your network to
RULE-SET,LAN,DIRECT,no-resolve. Your network's own names go there by
name instead (LAN).
IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
GEOIP,REGION,DIRECT
FINAL,Proxy,dns-failed
Here a name that points into 10.0.0.0/8 is looked up for GEOIP and
goes to FINAL: the first rule takes only connections made to such an
address. To send a name of your own network directly, name it
(DOMAIN-SUFFIX,corp.example,DIRECT), or leave no-resolve off the
address rule, which then looks every name that reaches it up.
extended-matching#
DOMAIN-SUFFIX,googlevideo.com,Proxy,extended-matching
RULE-SET,https://example.com/streaming.list,Streaming,extended-matching
With extended-matching, a rule also tries the names the connection's
own first bytes give — the server name a TLS ClientHello asks for, the
Host of an HTTP request — which helps when an app connects to an
address rather than a name. Only the rule that says so does: a rule
without it sees the name the connection was made to.
- It is for
DOMAIN,DOMAIN-SUFFIX,DOMAIN-KEYWORD,DOMAIN-WILDCARD,DOMAIN-REGEX,GEOSITE,RULE-SET,DOMAIN-SETandURL-REGEX. On another rule it does nothing, and a note says so. - On
RULE-SETandDOMAIN-SET, it applies to all of the list's names. A list one of whose lines says it (DOMAIN,cdn.example,extended-matching) tries all of its names on those names too. - On
URL-REGEX, the URL is tried again with each of those names in its host's place. - A name read from the first bytes is never looked up: the address entries of such a rule see the connection's own address.
- Pipe reads a connection's first bytes only when some rule has the option, or another reason to (Sniffing).
pre-matching#
DOMAIN-SUFFIX,ads.example.com,REJECT,pre-matching
RULE-SET,https://example.com/trackers.list,REJECT-DROP,pre-matching
pre-matching on a rule whose policy is a REJECT one moves the rule
before all the others, and has the DNS try it too: a query for a name it
matches is answered with no record — or, for REJECT-DROP, not at all —
before any connection is attempted. REJECT-NO-DROP answers with no
record too.
- A query shows the name alone: what else a rule goes by — an address, a port, a protocol, a process, a list's entries other than names — does not match at DNS time, and waits for the connection. So only a rule's name entries can refuse a query, a list's included.
- Under a
NOT, what a query cannot show would refuse names the rule lets through (NOT,((DEST-PORT,443))would refuse every name). A rule with an address, a port, a protocol, a process, a list at a URL — or any list with more than names in it — under aNOTis not tried at DNS time, nor moved before the others: that is warned about, and the rule is tried in its turn. - A list's own lines are tried at DNS time as a rule's are: a line of a
list that goes by more than the name under a
NOTof its own refuses names there too. - On a rule of any other policy,
pre-matchingis warned about, and the rule keeps its place.
notification-text#
DOMAIN-SUFFIX,bank.example,DIRECT,notification-text="Your bank goes direct"
FINAL,Proxy,notification-text="Something went through the proxy",notification-interval=600
When the rule decides a connection, its text is posted as a
notification, at most once every notification-interval seconds — 300
unless it says. An interval that is not a number is warned about, and 300
is used.
Not supported yet#
Each of these is warned about, and the rule is skipped:
IP-ASNwithoutip-asn-maxmind-url;SCRIPTinside a logical rule or a rule set;PROTOCOLwithDOH,DOH3,DOQ,DOTorDNS;DEVICE-NAMEandMAC-ADDRESS, on a phone;- rule sets nested more than eight deep.
A logical rule one of whose rules is among these is skipped whole.
Sniffing#
Some connections are read before they are decided: their first bytes say what they are for. Pipe reads them on the tunnel and on the shared proxy:
- TCP to port 80, always, since plain HTTP is carried by the HTTP engine;
- TCP to port 443, when a rule looks at what it says — one with
extended-matching, orPROTOCOL,HTTPS; - whatever
[MITM]may decrypt.
From TCP it reads the server name a TLS ClientHello asks for, and the
Host of an HTTP request.
- A connection is given 300 milliseconds to say what it is. One that waits for the server to speak first is held that long, then carried as it is.
- What was read goes on to the destination ahead of the rest, byte for byte, as the client sent it.
- A SOCKS5 or HTTP
CONNECTclient says nothing until it is told its tunnel is up, so it is told first; a rule that then refuses it closes the connection, rather than answering with a refusal. always-raw-tcp-hostsandalways-raw-tcp-keywordsname hosts never to read, by the name or address the connection was made to.
QUIC — UDP to port 443, versions 1 and 2 — says its server name too, in the ClientHello its first Initial packets carry, which Pipe decrypts: their keys are public by design. A ClientHello too large for one packet is put back together from the datagrams that follow, in any order. The flow is held for them 200 milliseconds at most, and opened without the name after that. Google's older gQUIC is not read.
Rule sets#
A rule set is a list of rules, used by one rule line: the line matches when any of the list's entries does.
[Rule]
RULE-SET,LAN,DIRECT
RULE-SET,Streaming,Proxy
RULE-SET,https://example.com/telegram.list,Proxy,no-resolve
RULE-SET,https://example.com/apple.yaml,DIRECT,format=clash-classical
DOMAIN-SET,https://example.com/ads.txt,REJECT,update-interval=43200
RULE-SET,corp.list,DIRECT
[Ruleset Streaming]
DOMAIN-SUFFIX,netflix.com
DOMAIN-SUFFIX,nflxvideo.net
DOMAIN-KEYWORD,youtube
RULE-SET and DOMAIN-SET#
RULE-SET |
DOMAIN-SET |
|
|---|---|---|
| Holds | rules of any type, without a policy | names alone, one per line, or a binary list |
| Takes | LAN, SYSTEM, a [Ruleset <name>], a file, a URL |
a file, a URL |
| Read as | told by its content, unless format= says |
domain-set, with srs and mrs recognized by their first bytes, unless format= says |
- The value is read in this order:
LANandSYSTEM, written in capitals; forRULE-SET, a[Ruleset <name>]section of that name; then, when it holds://,/or., a file or a URL. Anything else is an error. - A URL must be
http://orhttps://; another kind is an error. - One list named by several lines is one list, read as the first line
that names it says. A later line with other options gets a note.
Naming one list both as a
RULE-SETand as aDOMAIN-SETis an error. no-resolveon the line applies to the list's address entries: they match only an address the connection came with. An entry the list itself marksno-resolvenever looks a name up, nor takes an address one found.extended-matchingon the line applies to all of the list's names (extended-matching).- A list in a file or at a URL cannot name another list: a
RULE-SETorDOMAIN-SETline in it is passed over.
[Ruleset <name>] sections#
A list written in the profile itself, one rule line per line, without a
policy, used as RULE-SET,<name>,Policy.
- It is read as a list in a file is — once, however many rules use it — and goes into the compiled configuration as one: what a list may hold, and how much (What a list may hold), a section may, and a line past that is passed over as a list's is.
- Beside what a list may hold, its lines may name another section, a
list in a file or at a URL, or a
GEOSITElist, and may be logical rules that do. Those lines are kept beside the list, and tried with it wherever the section is used. Sections may name each other eight deep at most. What sections naming sections come to is counted: one compile takes at most 65,536 steps of it, and a profile that asks for more — a few lines can, when each names several sections that each name several more — does not compile. - A line Pipe cannot use matches nothing, and is warned about once, with
its line:
in [Ruleset Streaming]: …; this line matches nothing. - A section that names itself, directly or through others, matches nothing through that line, with a warning.
- One of its lines that says
extended-matchingmakes all of its names try the names a connection's first bytes give, as in a list. #!includeunder[Ruleset *]brings in every[Ruleset …]section of a file; a module's lines for a section join it.
LAN and SYSTEM#
Two lists are built in.
LAN is the local network: names under local, localhost,
home.arpa, internal, lan, localdomain, home and corp —
suffixes no public name can have — names of a single label (nas,
printer), and these ranges. Its names match as they are, never looked
up. no-resolve on its line applies to the ranges, which then see only
an address a connection came with.
| Ranges | |
|---|---|
| IPv4 | 0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24, 192.168.0.0/16, 224.0.0.0/4 |
| IPv6 | ::1/128, fc00::/7, fe80::/10 |
SYSTEM is what Apple's own system services talk to: api.smoot.apple.com,
captive.apple.com, xp.apple.com, configuration.apple.com,
guzzoni.apple.com, smp-device-content.apple.com,
aod.itunes.apple.com, mesu.apple.com, api.smoot.apple.cn,
gs-loc.apple.com, mvod.itunes.apple.com and
streamingaudio.itunes.apple.com, and the names under ess.apple.com,
push-apple.com.akadns.net, push.apple.com, lcdn-locator.apple.com,
lcdn-registration.apple.com and ls.apple.com.
Lists at URLs#
- A list at a URL is downloaded by the engine — through whatever the rules choose for the list's host, at every redirect again — and kept.
- The tunnel never waits for a list to start. A list downloaded before is in effect from its copy. One that never was is downloaded in the background, and the rules use it once it arrives; until then it matches nothing.
- It is fetched again every
update-intervalseconds — 86400, a day, unless the line says.update-interval=0, or a negative number, fetches it only when there is no copy. - It is asked for with what the server said last time (
ETag,Last-Modified), so that an unchanged list costs one short request. - A failed fetch keeps the copy in use, and is tried again after 30 seconds, then a minute, two minutes, and so on, up to an hour.
- Downloads are capped at 16 MiB on iOS (64 MiB elsewhere), with gzip decompressed as it arrives.
- A page that is no list — a captive portal's, an error page — never takes the place of a copy in use: it is kept as a fetch error, and said. What is kept on disk is a list's text and what it compiled to, never anything the engine would take on trust.
What a set is shown by — its status, the log, what a connection matched — keeps a URL's query out of sight, so a token in it is not spread around.
Lists in files#
A list in a file is read as bytes when the profile is compiled, and its entries are
checked then. Binary lists (srs and mrs) are embedded in the compiled
configuration as base64; the tunnel runs exactly the bytes that were checked.
A binary list has numbered rules instead of text lines; diagnostics identify
the rule number, for example ads.srs:12: ...; this rule matches nothing. The lines Pipe passes over are named, with their line and
why: corp.list:3: …; this line matches nothing. When there are many,
the first few are named, and the rest counted. A file that is not there
is warned about, and the rule is skipped.
List formats#
A list may be written in any of these formats. format= on the line
names it; without it, a RULE-SET list's format is told from its
content.
format= |
What the list is |
|---|---|
surge |
One rule per line, without a policy: DOMAIN-SUFFIX,example.com, IP-CIDR,10.0.0.0/8,no-resolve. |
domain-set |
One name per line. example.com is that name; .example.com and +.example.com the name and everything under it; *.example.com only what is under it. |
clash-domain |
A Clash domain provider: a YAML payload: list, or one entry per line. +.example.com is the name and everything under it; .example.com only what is under it; *.example.com one label under it; anything else the name exactly. |
clash-ipcidr |
A Clash address provider: address ranges, in a payload: list or one per line. |
clash-classical |
A Clash classical provider: rule lines, in a payload: list or one per line. |
sing-box |
sing-box's rule-set source, in JSON: {"version": …, "rules": […]}. |
srs |
sing-box's binary rule set (.srs), versions 1 to 5. |
mrs |
mihomo's binary rule set (.mrs), of names or address ranges. |
pipe |
Pipe's own JSON table: {"domainExact": […], "domainSuffix": […], "domainKeyword": […], "domainRegex": […]}. |
auto |
Told from the content (the default). |
A format= Pipe does not know is an error. What a list compiles to is
the engine's own cache, kept beside the list's text — never a format a
line can name, and never read from anywhere but that cache.
Told from the content: srs by SRS and zlib, mrs by zstd and its MRS header, and JSON by its first bytes (a JSON object with a
rules list is sing-box's, any other Pipe's); a Clash provider by its
payload: — classical when an entry has a comma, addresses when every
entry is a range, names otherwise; and anything else line by line: a
line with a comma is a rule, an address range is a range, and anything
else is a name, read as domain-set reads it.
In sing-box's source and binary formats, a rule's destination fields — domain,
domain_suffix (.example.com is only what is under it),
domain_keyword, domain_regex, adguard_domain, ip_cidr — match when any of them
does, and with them every other field the rule has: port,
port_range, source_ip_cidr, source_port, source_port_range,
network, process_name, process_path, process_path_regex. Each field
takes one value or a list.
invert turns a rule round, and logical rules ("type": "logical",
"mode": "and" or "or") combine rules. A rule with any other field is
passed over, with why.
What a list may hold#
- Comments — lines starting with
#,//or;— and blank lines. A comment after a space ends a line. - In a rule list, any rule a
[Rule]line can be — names, keywords, patterns, address ranges, countries (GEOIP,UNKNOWNandGEOIP,REGIONtoo), ASNs, ports,USER-AGENT, logical rules — butFINAL,SCRIPT, aRULE-SETorDOMAIN-SETnaming another list or a built-in one (LAN,SYSTEM), andGEOSITE, which names another list too. - Names made of letters (of any script), digits,
-,_and dots. A line that is not one is passed over. - A line Pipe cannot use — an empty value included — is passed over, as an invalid line is: it matches nothing, and is counted.
- A
SUBNETline whose expression has no prefix (SUBNET,Home), in a logical rule too, is such a line: a list is read as it is, not converted as an imported Surge profile is. - At most a million entries — every value counted, those of logical
rules too; a list with more is no list at all. Of them, at most 4,096
keywords, 1,024 patterns over names, 1,024
PROCESS-PATH-REGEXpatterns, 2,048URL-REGEXpatterns, 2,048USER-AGENTpatterns, and 1,024 logical rules (and rules with a port or a process): one past those is passed over, and counted — a line alone, or a value alone of a sing-box rule. - A text list is UTF-8. A binary list expands to at most 16 MiB on iOS and 64 MiB on CLI hosts; a sing-box binary list is of version 1 to 5. Unknown binary items reject the list with a diagnostic.