feat(sub): let the panel set the JSON subscription DNS servers (#6485)

* feat(sub): let the panel set the JSON subscription DNS servers

A baked routing profile (#6402) carries only the DNS its preset defines, so an
operator who wants their own resolvers has to override the whole profile or
patch the subscription behind a proxy.

Add the subJsonDns setting: either a full xray dns block or a bare array of
servers. It wins over the profile's DNS while leaving the profile's routing
rules intact, and reaches per-inbound, balancer and info-node documents alike.

The value is validated with xray's own schema (internal/xray/dnsconf): a block
the client could not load is rejected when the settings are saved and ignored
with a warning at request time, instead of being baked into every document.
Both the sub server and the settings API share that validator, so a stored
value can never be silently dropped.

xray's Build() is deliberately not used for validation: it resolves geosite
tokens from the geodata files and would reject valid configs whenever those
are absent from the panel's working directory.

* style(dnsconf): drop the ineffectual initial map assignment

golangci's ineffassign flagged the zero-value map whose value both paths
overwrite: the object branch now assigns the decoded map directly.

* docs(sub): scope the DNS setting to the documents it rewrites

The Routing header mirrored to Happ/INCY keeps the routing profile's own
resolvers, so the setting description and the header-source comment now say
so instead of claiming the profile's DNS is replaced everywhere.

Also trims two comments in the new dnsconf package to the repo's two-line cap.
This commit is contained in:
DIMFLIX
2026-09-13 12:51:56 +03:00
committed by GitHub
parent aaa5e61cad
commit 2730e4d071
33 changed files with 709 additions and 5 deletions
+16 -3
View File
@@ -37,6 +37,9 @@ type SubJsonService struct {
bakedRoutingMu sync.Mutex
bakedRouting *bakedRoutingState
// dnsBlock is the panel DNS override, fixed for the service's lifetime.
dnsBlock map[string]any
SubService *SubService
}
@@ -83,7 +86,7 @@ func NewSubJsonService(mux string, rules string, finalMask string, routingRules
// Re-resolved per call so an upstream edit reaches the documents without a
// restart; a failed resolve keeps the last good template.
func (s *SubJsonService) bakedTemplate() map[string]any {
if s.routingRules == "" {
if s.routingRules == "" && s.dnsBlock == nil {
return s.configJson
}
spec := resolveJsonRoutingSpec(s.routingRules)
@@ -93,12 +96,19 @@ func (s *SubJsonService) bakedTemplate() map[string]any {
if spec.empty() || spec.equal(s.bakedRouting.spec) {
return s.bakedRouting.configJson
}
} else if spec.empty() {
} else if spec.empty() && s.dnsBlock == nil {
return s.configJson
}
template := make(map[string]any, len(s.configJson)+2)
maps.Copy(template, s.configJson)
applyJsonRouting(template, spec)
if !spec.empty() {
applyJsonRouting(template, spec)
}
// The panel-level DNS block is an explicit choice, so it also replaces the
// dns subtree a routing profile would otherwise bake in.
if s.dnsBlock != nil {
template["dns"] = s.dnsBlock
}
s.bakedRouting = &bakedRoutingState{spec: spec, configJson: template}
return template
}
@@ -1041,6 +1051,9 @@ func (s *SubJsonService) genDummySocksConfig(remark string) json_util.RawMessage
newConfigJson := make(map[string]any)
maps.Copy(newConfigJson, s.configJson)
if s.dnsBlock != nil {
newConfigJson["dns"] = s.dnsBlock
}
newConfigJson["outbounds"] = newOutbounds
newConfigJson["remarks"] = remark