Pipe Manual

Profile manual

Rules

On this page
  1. How rules are tried
  2. The line
  3. Destination overrides
  4. Rule types
  5. Address rules on names
  6. extended-matching
  7. pre-matching
  8. notification-text
  9. Not supported yet
  10. Sniffing
  11. Rule sets

[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.
  • FINAL comes last, once. A profile without it is an error. A rule after FINAL never applies, and is warned about.
  • So does a rule that an earlier one covers — DOMAIN,www.example.com after DOMAIN-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 with no-resolve does not cover a later one without: that one also sees the addresses names lead to.
  • Rules marked pre-matching are 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-Agent too.
  • 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, and key=value options.
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, then https://www.cloudflare.com/cdn-cgi/trace if 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,REGION matches 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
  • AND matches when every rule inside matches, OR when any does, and NOT when 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.
  • NOT takes exactly one rule; AND and OR at least one.
  • A logical rule one of whose rules Pipe cannot evaluate is skipped whole, with a warning that names that rule. SCRIPT inside one is such a rule; FINAL inside one is an error.
  • A name is looked up only when an address could change the outcome: AND,((DOMAIN-SUFFIX,example.com),(GEOIP,JP)),DIRECT never looks up a name outside example.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-resolve does 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 FINAL says dns-failed. Then the rules go on without an address: address rules do not match it, and if no other rule does, it goes to FINAL.

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-SET and URL-REGEX. On another rule it does nothing, and a note says so.
  • On RULE-SET and DOMAIN-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 a NOT is 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 NOT of its own refuses names there too.
  • On a rule of any other policy, pre-matching is 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-ASN without ip-asn-maxmind-url;
  • SCRIPT inside a logical rule or a rule set;
  • PROTOCOL with DOH, DOH3, DOQ, DOT or DNS;
  • DEVICE-NAME and MAC-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, or PROTOCOL,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 CONNECT client 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-hosts and always-raw-tcp-keywords name 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: LAN and SYSTEM, written in capitals; for RULE-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:// or https://; 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-SET and as a DOMAIN-SET is an error.
  • no-resolve on the line applies to the list's address entries: they match only an address the connection came with. An entry the list itself marks no-resolve never looks a name up, nor takes an address one found.
  • extended-matching on 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-SET or DOMAIN-SET line 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 GEOSITE list, 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-matching makes all of its names try the names a connection's first bytes give, as in a list.
  • #!include under [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-interval seconds — 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,UNKNOWN and GEOIP,REGION too), ASNs, ports, USER-AGENT, logical rules — but FINAL, SCRIPT, a RULE-SET or DOMAIN-SET naming another list or a built-in one (LAN, SYSTEM), and GEOSITE, 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 SUBNET line 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-REGEX patterns, 2,048 URL-REGEX patterns, 2,048 USER-AGENT patterns, 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.