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
+8 -2
View File
@@ -107,6 +107,7 @@ type subControllerConfig struct {
subJsonMux string
subJsonRules string
subJsonRoutingRules string
subJsonDns string
subJsonFinalMask string
subJsonObservatory string
subClashEnableRouting bool
@@ -191,6 +192,10 @@ func WithSUBJsonRoutingRules(value string) SUBControllerOption {
return func(config *subControllerConfig) { config.subJsonRoutingRules = value }
}
func WithSUBJsonDns(value string) SUBControllerOption {
return func(config *subControllerConfig) { config.subJsonDns = value }
}
func WithSUBJsonFinalMask(value string) SUBControllerOption {
return func(config *subControllerConfig) { config.subJsonFinalMask = value }
}
@@ -268,6 +273,7 @@ func NewSUBController(g *gin.RouterGroup, options ...SUBControllerOption) *SUBCo
sub := NewSubService(config.remarkTemplate)
subJsonSvc := NewSubJsonService(config.subJsonMux, config.subJsonRules, config.subJsonFinalMask, config.subJsonRoutingRules, sub)
subJsonSvc.SetObservatoryConfig(config.subJsonObservatory)
subJsonSvc.SetDnsConfig(config.subJsonDns)
a := &SUBController{
subTitle: config.subTitle,
subSupportUrl: config.subSupportURL,
@@ -932,8 +938,8 @@ func (a *SUBController) ApplyCommonHeaders(
rules, remote, routingErr := resolveRoutingSource(remoteRoutingHapp, profileRoutingRules)
if strings.TrimSpace(profileRoutingRules) == "" {
// Happ/INCY fetch the geo files the baked JSON rules reference through
// this header, so a blank Happ setting falls back to the JSON profile.
// Happ/INCY fetch the geo files the baked rules reference through this
// header; unlike the documents, it keeps the profile's own DNS servers.
rules, remote, routingErr = jsonRoutingHeaderSource(a.subJsonRoutingRules), false, nil
}
// The off values undo a previously pushed setting, so they ride the same