Operating system
Linux
System version
Arch
Installation type
Original sing-box Command Line
If you are using a graphical client, please provide the version of the client.
No response
Version
sing-box version 1.14.0-beta.1
Environment: go1.26.2 linux/amd64
Tags: with_gvisor,with_quic,with_dhcp,with_wireguard,with_utls,with_acme,with_clash_api,with_tailscale,with_ccm,with_ocm,with_cloudflared,with_usbip,with_openvpn,with_openconnect,badlinkname,tfogo_checklinkname0
Revision: 8bc6787c7ff785e5f6343241affdadd5ca239bd7
CGO: enabled
Description
A process-based routing rule (process_name, process_path, process_path_regex, package_name, user, user_id) introduced by a live rule-set update never takes effect until sing-box is restarted.
Whether a process searcher exists is decided once, in Router.Start during StartStateStart (route/router.go): needFindProcess is computed from the static route/DNS rules plus a one-time scan of each rule-set's ContainsProcessRule metadata, and r.processSearcher is assigned only inside that block. The connection path then gates on r.processSearcher == nil (route/process_cache.go).
A local rule-set is hot-reloaded on file change (route/rule/rule_set_local.go): reloadFile -> reloadRules recomputes ContainsProcessRule and fires the rule-set's update callbacks. But nothing in the router reacts to that for process-searcher purposes, so if no process rule existed at startup (processSearcher == nil), a process rule added by the update is silently never matched until the next full restart.
This is the live-update analog of #2469 (fixed by #2470), which handled a process rule already present in a rule-set at startup. The same class of bug remains for a rule-set that gains a process rule after startup.
Note: successful local rule-set reloads are silent (no success log; only errors are logged, unlike remote rule-sets which log updated rule-set). Reload can be confirmed by saving the file with a syntax error, which logs reload rule-set <tag>: ....
Reproduction
config.json:
{
"log": { "level": "debug" },
"inbounds": [
{ "type": "mixed", "tag": "in", "listen": "127.0.0.1", "listen_port": 12345 }
],
"outbounds": [
{ "type": "direct", "tag": "direct" }
],
"route": {
"rule_set": [
{ "type": "local", "tag": "proc", "format": "source", "path": "ruleset.json" }
],
"rules": [
{ "rule_set": "proc", "action": "reject" }
],
"final": "direct"
}
}
ruleset.json — initial (no process rule, so needFindProcess == false at startup):
{ "version": 5, "rules": [ { "domain": "google.com" } ] }
Steps:
- Start a local target:
python3 -m http.server 8080
- Run:
sing-box run -c config.json
curl -x http://127.0.0.1:12345 http://127.0.0.1:8080/ -> succeeds (rule-set doesn't match; no process searcher created).
curl -x http://127.0.0.1:12345 google.com -> rejected (prove rule-set was loaded)
- Edit
ruleset.json in place and save:
{ "version": 5, "rules": [ { "process_name": ["curl"] } ] }
curl -x http://127.0.0.1:12345 http://127.0.0.1:8080/ again -> still succeeds (bug: the reloaded process_name rule is silently ignored, so reject never fires). curl -x http://127.0.0.1:12345 google.com also succeeds.
- Restart sing-box with the process-rule
ruleset.json already in place -> the same curl is now rejected, confirming the rule itself is correct and the only difference is that the searcher is created solely at startup.
Logs
Step 6 (process rule live-added; no process lookup performed):
INFO[0000] inbound/mixed[in]: tcp server started at 127.0.0.1:12345
INFO[0000] sing-box started (0.00s)
INFO[0008] [1135326760 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:43372
INFO[0008] [1135326760 0ms] inbound/mixed[in]: inbound connection to 127.0.0.1:8080
INFO[0008] [1135326760 0ms] outbound/direct[direct]: outbound connection to 127.0.0.1:8080
DEBUG[0008] [1135326760 2ms] connection: connection download finished
DEBUG[0008] [1135326760 2ms] connection: connection upload finished
DEBUG[0008] [1135326760 2ms] inbound/mixed[in]: connection closed: read http request: EOF
INFO[0014] [2161371828 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:54184
INFO[0014] [2161371828 0ms] inbound/mixed[in]: inbound connection to google.com:80
DEBUG[0014] [2161371828 0ms] router: match[0] rule_set=proc => reject
DEBUG[0014] [2161371828 0ms] router: connection closed: rejected
DEBUG[0014] [2161371828 0ms] inbound/mixed[in]: connection closed: (rejected | Head "http://google.com/": io: read/write on closed pipe)
INFO[0035] [3399146383 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:33658
INFO[0035] [3399146383 0ms] inbound/mixed[in]: inbound connection to 127.0.0.1:8080
INFO[0035] [3399146383 0ms] outbound/direct[direct]: outbound connection to 127.0.0.1:8080
DEBUG[0035] [3399146383 1ms] connection: connection download finished
DEBUG[0035] [3399146383 1ms] connection: connection upload finished
DEBUG[0035] [3399146383 2ms] inbound/mixed[in]: connection closed: read http request: EOF
INFO[0040] [2707212090 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:35404
INFO[0040] [2707212090 0ms] inbound/mixed[in]: inbound connection to google.com:80
INFO[0040] [2707212090 0ms] outbound/direct[direct]: outbound connection to google.com:80
DEBUG[0040] [2707212090 0ms] dns: lookup domain google.com
DEBUG[0040] [2707212090 16ms] dns: exchanged google.com NOERROR 266
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::66
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::71
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::8b
DEBUG[0040] [2707212090 16ms] dns: exchanged AAAA google.com. 266 IN AAAA 2607:f8b0:400e:c1b::64
DEBUG[0040] [2707212090 36ms] dns: exchanged google.com NOERROR 279
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.113
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.101
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.102
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.139
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.138
DEBUG[0040] [2707212090 36ms] dns: exchanged A google.com. 279 IN A 172.253.117.100
DEBUG[0040] [2707212090 36ms] dns: lookup succeed for google.com: 172.253.117.113 172.253.117.101 172.253.117.102 172.253.117.139 172.253.117.138 172.253.117.100 2607:f8b0:400e:c1b::66 2607:f8b0:400e:c1b::71 2607:f8b0:400e:c1b::8b 2607:f8b0:400e:c1b::64
TRACE[0040] [2707212090 83ms] connection: connection download closed
DEBUG[0040] [2707212090 83ms] connection: connection upload finished
DEBUG[0040] [2707212090 83ms] inbound/mixed[in]: connection closed: read http request: EOF
Step 7 (same process-rule ruleset present at startup; now matched and rejected):
INFO[0000] inbound/mixed[in]: tcp server started at 127.0.0.1:12345
INFO[0000] sing-box started (0.00s)
INFO[0003] [2963730613 0ms] inbound/mixed[in]: inbound connection from 127.0.0.1:41722
INFO[0003] [2963730613 0ms] inbound/mixed[in]: inbound connection to 127.0.0.1:8080
INFO[0003] [2963730613 13ms] router: found process path: /usr/bin/curl, user: garmr
DEBUG[0003] [2963730613 13ms] router: match[0] rule_set=proc => reject
DEBUG[0003] [2963730613 13ms] router: connection closed: rejected
DEBUG[0003] [2963730613 13ms] inbound/mixed[in]: connection closed: (rejected | Head "http://127.0.0.1:8080/": EOF)
Supporter
Integrity requirements
Operating system
Linux
System version
Arch
Installation type
Original sing-box Command Line
If you are using a graphical client, please provide the version of the client.
No response
Version
Description
A process-based routing rule (
process_name,process_path,process_path_regex,package_name,user,user_id) introduced by a live rule-set update never takes effect until sing-box is restarted.Whether a process searcher exists is decided once, in
Router.StartduringStartStateStart(route/router.go):needFindProcessis computed from the static route/DNS rules plus a one-time scan of each rule-set'sContainsProcessRulemetadata, andr.processSearcheris assigned only inside that block. The connection path then gates onr.processSearcher == nil(route/process_cache.go).A
localrule-set is hot-reloaded on file change (route/rule/rule_set_local.go):reloadFile->reloadRulesrecomputesContainsProcessRuleand fires the rule-set's update callbacks. But nothing in the router reacts to that for process-searcher purposes, so if no process rule existed at startup (processSearcher == nil), a process rule added by the update is silently never matched until the next full restart.This is the live-update analog of #2469 (fixed by #2470), which handled a process rule already present in a rule-set at startup. The same class of bug remains for a rule-set that gains a process rule after startup.
Note: successful
localrule-set reloads are silent (no success log; only errors are logged, unlikeremoterule-sets which logupdated rule-set). Reload can be confirmed by saving the file with a syntax error, which logsreload rule-set <tag>: ....Reproduction
config.json:{ "log": { "level": "debug" }, "inbounds": [ { "type": "mixed", "tag": "in", "listen": "127.0.0.1", "listen_port": 12345 } ], "outbounds": [ { "type": "direct", "tag": "direct" } ], "route": { "rule_set": [ { "type": "local", "tag": "proc", "format": "source", "path": "ruleset.json" } ], "rules": [ { "rule_set": "proc", "action": "reject" } ], "final": "direct" } }ruleset.json— initial (no process rule, soneedFindProcess == falseat startup):{ "version": 5, "rules": [ { "domain": "google.com" } ] }Steps:
python3 -m http.server 8080sing-box run -c config.jsoncurl -x http://127.0.0.1:12345 http://127.0.0.1:8080/-> succeeds (rule-set doesn't match; no process searcher created).curl -x http://127.0.0.1:12345 google.com-> rejected (prove rule-set was loaded)ruleset.jsonin place and save:{ "version": 5, "rules": [ { "process_name": ["curl"] } ] }curl -x http://127.0.0.1:12345 http://127.0.0.1:8080/again -> still succeeds (bug: the reloadedprocess_namerule is silently ignored, sorejectnever fires).curl -x http://127.0.0.1:12345 google.comalso succeeds.ruleset.jsonalready in place -> the same curl is now rejected, confirming the rule itself is correct and the only difference is that the searcher is created solely at startup.Logs
Supporter
Integrity requirements