Rules
In route-mode = "rule", every connection is checked against [rule].list from top to bottom. The first rule that matches decides where the connection goes. The list must end with FINAL.
[rule]
list = [
"DOMAIN-SUFFIX,corp.example.com,office",
"PROCESS-NAME,cargo,work",
"IP-CIDR,10.0.0.0/8,DIRECT,no-resolve",
"FINAL,DIRECT",
]Syntax
TYPE,payload,target
TYPE,payload,target,no-resolve
FINAL,targetTYPEis case-insensitive.MATCHis accepted as another name forFINAL.payloadcannot contain a comma.targetisDIRECT,REJECT, thenameof a[[proxy]], or thenameof a[[proxy-group]]. Names are case-sensitive.FINALmust be the last entry, and exactly oneFINALmust exist.- Rules containing a backslash, such as regular expressions, must use TOML single quotes:
'DOMAIN-REGEX,^ci-[0-9]+\.example\.com$,work'. In double quotes\.is an invalid escape and the whole file fails to load. no-resolveis the only option. On IP rules it means: do not resolve a domain just to test this rule; only match when the destination is already an IP address.
Rule types
Domain
| Type | Matches | Example |
|---|---|---|
DOMAIN | exactly this name | DOMAIN,api.github.com,work |
DOMAIN-SUFFIX | this name and all subdomains | DOMAIN-SUFFIX,github.com,work |
DOMAIN-KEYWORD | names containing the text | DOMAIN-KEYWORD,tracker,REJECT |
DOMAIN-WILDCARD | * and ? patterns | DOMAIN-WILDCARD,*.cdn.example.com,DIRECT |
DOMAIN-REGEX | a regular expression | DOMAIN-REGEX,^ci-[0-9]+\.example\.com$,work |
GEOSITE | a category from a GeoSite file | GEOSITE,github,work |
IP
| Type | Matches | Example |
|---|---|---|
IP-CIDR | an IPv4 network | IP-CIDR,10.0.0.0/8,DIRECT,no-resolve |
IP-CIDR6 | an IPv6 network | IP-CIDR6,fd00::/8,DIRECT,no-resolve |
GEOIP | a country or category from a GeoIP file | GEOIP,private,DIRECT,no-resolve |
IP-ASN | an autonomous system number | IP-ASN,13335,DIRECT |
Connection
| Type | Matches | Example |
|---|---|---|
DST-PORT | a destination port or range a-b | DST-PORT,22,DIRECT |
NETWORK | TCP or UDP | NETWORK,UDP,DIRECT |
Process
Process rules need to know which program opened the connection. On Linux that comes from eBPF Enhanced Mode; connections that arrive on the local HTTP/SOCKS5 port carry process information only when the platform can provide it.
| Type | Matches | Example |
|---|---|---|
PROCESS-NAME | process name; * and ? allowed | PROCESS-NAME,cargo,work |
PROCESS-PATH | absolute executable path; * and ? allowed | PROCESS-PATH,/usr/bin/git,work |
PROCESS-NAME-REGEX | process name, regular expression | PROCESS-NAME-REGEX,^python3(\.[0-9]+)?$,work |
PROCESS-PATH-REGEX | executable path, regular expression | PROCESS-PATH-REGEX,^/opt/tools/,work |
On Linux the process name is the kernel comm, at most 15 characters: git-remote-https is seen as git-remote-http. Name matching is case-sensitive. See Rule recipes for developer tools for real examples.
Rule set
| Type | Matches | Example |
|---|---|---|
RULE-SET | any entry of a named rule set | RULE-SET,ads,REJECT |
See Rule sets below.
AND, OR, NOT and SUB-RULE are recognized names, but their payload needs commas, which the rule syntax does not allow, so they cannot be used in [rule].list.
Ordering
First match wins, so put narrow rules above broad ones:
[rule]
list = [
"DOMAIN,status.corp.example.com,DIRECT", # exception first
"DOMAIN-SUFFIX,corp.example.com,office", # then the general case
"FINAL,DIRECT",
]A readable layout for larger lists: blocks (REJECT), process names, domains, networks, rule sets, then FINAL.
GeoIP and GeoSite
GEOIP and GEOSITE read V2Ray-format data files (geoip.dat, geosite.dat, as published by v2fly and Loyalsoldier). Tell Specola where they are with [rule.cache]:
[rule]
list = [
"GEOIP,private,DIRECT,no-resolve",
"GEOSITE,github,work",
"FINAL,DIRECT",
]
[rule.cache]
geoip = "/home/me/.local/share/specola/geoip.dat"
geosite = "/home/me/.local/share/specola/geosite.dat"Without the file, validation fails with GEOIP rule requires a geoip resource. Category names are case-insensitive. Use absolute paths.
IP-ASN needs a text catalog in [rule.cache] asn = "<path>", one AS<number>,<cidr> pair per line.
Rule sets
A rule set is a named list of rules, stored in a file or downloaded, that you use with RULE-SET. Declare it under [rule.cache]:
[rule]
list = [
"RULE-SET,ads,REJECT",
"RULE-SET,internal,office",
"FINAL,DIRECT",
]
[[rule.cache.rule-set]]
name = "ads"
behavior = "domain"
url = "https://lists.example.com/ads.txt"
[[rule.cache.rule-set]]
name = "internal"
behavior = "classical"
path = "/home/me/.config/specola/internal-rules.txt"| Field | Notes |
|---|---|
name | required; used as the RULE-SET payload |
path | local file; with url, where the download is stored |
url | download location; fetched every time the profile loads |
behavior | classical (default), domain or ipcidr |
proxy | download through this [[proxy]] |
File formats, one entry per line; empty lines and lines starting with # or // are skipped:
behavior | Line format |
|---|---|
classical | TYPE,payload without a target, for example DOMAIN-SUFFIX,example.com |
domain | example.com (suffix), or prefixed full:, domain:, keyword:, regexp: |
ipcidr | 10.0.0.0/8 or fd00::/8 |
The target always comes from the RULE-SET rule that uses the set.
Troubleshooting
| Message | Cause |
|---|---|
Rule must use TYPE,payload,target or FINAL,target | wrong number of commas, often a comma inside the payload |
Unsupported rule type | misspelled type |
Rule references unknown outbound: X | X is not DIRECT, REJECT, a proxy or a group; check capitalization |
FINAL must be the last rule | move FINAL to the end |
Rule list requires an explicit FINAL target | add "FINAL,<target>" |
RULE-SET references unknown cache: X | no [[rule.cache.rule-set]] with name = "X" |
If a domain rule does not match traffic captured by Enhanced Mode, the application probably resolved the name on its own; see DNS.