Skip to content

feat(route): add optional "name" field to route and DNS rules#4061

Open
Pushkinmazila2 wants to merge 101 commits intoSagerNet:testingfrom
Pushkinmazila2:testing
Open

feat(route): add optional "name" field to route and DNS rules#4061
Pushkinmazila2 wants to merge 101 commits intoSagerNet:testingfrom
Pushkinmazila2:testing

Conversation

@Pushkinmazila2
Copy link
Copy Markdown

Summary

Adds an optional name field to DefaultRule and LogicalRule (both route and DNS) that allows users to label individual rules with human-readable names. The field is purely cosmetic — it has no effect on matching logic — and is fully backward-compatible.

Motivation

Large sing-box configurations can contain dozens of rules. Without labels, log output and debugging rely entirely on auto-generated descriptions like rule_set(geosite-category-ads) domain_keyword(...), which makes it hard to understand at a glance which rule fired and why.

A name field solves this with zero risk: old configs without it continue to work unchanged, and new configs with it work fine on old builds (unknown fields are ignored by the JSON parser).

Changes

option/rule.go

  • Added Name string \json:"name,omitempty"`toRawDefaultRule`
  • Added Name string \json:"name,omitempty"`toRawLogicalRule`

option/dns_rule.go

  • Added Name string \json:"name,omitempty"`toRawDefaultDNSRule`
  • Added Name string \json:"name,omitempty"`toLogicalDNSRule`

route/rule/rule_abstract.go

  • abstractDefaultRule.String() now prefixes output with [name] when tag is set
  • abstractLogicalRule.String() now prefixes output with [name] when tag is set

route/rule/rule_default.go

  • NewDefaultRule: passes options.NameabstractDefaultRule.tag
  • NewLogicalRule: passes options.NameabstractLogicalRule.tag

Behavior

When name is set, log lines change from:

rule_set(geosite-category-ads)

to:

[Block ads] rule_set(geosite-category-ads)

When name is omitted, output is identical to the current behavior.

Example

{
  "route": {
    "rules": [
      {
        "name": "Block ads",
        "rule_set": ["geosite-category-ads"],
        "action": "reject"
      },
      {
        "name": "Force direct for CN",
        "rule_set": ["geoip-cn", "geosite-cn"],
        "action": "route",
        "outbound": "direct"
      },
      {
        "name": "Proxy everything else",
        "type": "logical",
        "mode": "or",
        "rules": [
          { "network": "tcp" },
          { "network": "udp" }
        ],
        "action": "route",
        "outbound": "proxy"
      }
    ]
  }
}

Backward Compatibility

Scenario Result
Old config (no name) → new build ✅ Works, field defaults to ""
New config (with name) → old build ✅ Works, unknown field is silently ignored
New config (with name) → new build ✅ Works, name appears in logs

Testing

Built and verified using Docker:

docker build -t sing-box-dev .
docker run --rm \
  -v $(pwd)/test-config.json:/etc/sing-box/config.json \
  sing-box-dev check -c /etc/sing-box/config.json

Log output confirms [name] prefix appears correctly on rule match.

image

DNS rules referencing rule-sets that contain only ip_cidr predicates
silently stopped matching when legacy DNS mode was disabled, because the
IP-CIDR branch cannot match against an in-flight DNS query. The existing
validation intentionally let every rule_set through on the premise that
mixed sets still work via their non-IP branches, which is only true when
such a branch exists. Track whether a rule-set carries any non-IP-CIDR
predicate and reject pure-IP references the same way bare ip_cidr fields
are already rejected.
Serialize probe rounds in startProber to eliminate unbounded fan-out of
fire-and-forget probe goroutines (up to 100/sec per direction), and close
HTTP/3 transports via transport.Close() in addition to CloseIdleConnections.
Signed-off-by: Pushkinmazila2 <zavalny199@yandex.com>
@Pushkinmazila2
Copy link
Copy Markdown
Author

@nekohasekai ive tested in new build.I tested the new version with your changes, everything works. Whether you have a "name" or not, everything works too.

@hdrover
Copy link
Copy Markdown
Contributor

hdrover commented Apr 22, 2026

Was name chosen intentionally instead of tag?

I’m wondering because if rules ever become referenceable in the future, having name now and tag later could create two almost overlapping fields. So is the idea that name should stay purely cosmetic / log-only and never be used semantically?

@Pushkinmazila2
Copy link
Copy Markdown
Author

The trick here is that if there are tags in the future, I'm only in favor,
Right now this value is logical (cosmetic), to navigate among dozens of rules.

@nekohasekai nekohasekai force-pushed the testing branch 6 times, most recently from 1b0e6c5 to abedea4 Compare April 28, 2026 07:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants