Files
3x-ui/internal/util/maskcompat/xdns.go
T
MHSanaei 62423cacd1 feat(xray): update xray-core to v26.9.30 and adapt panel
Bump xtls/xray-core to b26a91de4f (v26.9.30) and the three binary pins
(DockerInit.sh, release.yml Linux + Windows) in lockstep. No deleted
symbols; the sing and sing-shadowsocks indirect deps drop out with the
SS2022 rewrite.

XDNS finalmask (#6718) replaced its string lists with objects: domains
are {name, types, edns0, lenLimit, labelLimit} and client resolvers
{type, settings.addr}. The loader no longer parses the old lists, so a
single stored xdns mask keeps the whole core from starting. The new leaf
package internal/util/maskcompat converts them: "name[:type]" becomes a
domain and "name[:type]+udp://addr" a domain plus a udp resolver. A bare
name maps to TXT, the type legacy clients queried by default, and each
converted domain keeps the 1232-byte EDNS0 the old code always used
(without it the server caps answers at 512). It runs from:
- the XdnsFinalmaskObjectsFix seeder, over inbound streams, hosts, the
  xray template, the global sub-JSON mask and cached subscription
  outbounds;
- inbound save (normalizeStreamSettings) and GetXrayConfig, for rows
  that never went through the seeder;
- both link importers, since fm= from an older panel carries the lists.
The finalmask form edits the object shape (every key needs a registered
field, or the finalmask watch drops it on save) and lifts legacy masks
on open. The udp-mask golden fixture moves to the object shape, which
TestGoldenStreamFixturesBuildInXray now builds through the core. The
wire format changed as well, so pre-upgrade clients need the new core.

WireGuard outbound (#6771) dropped settings.domainStrategy and the
remoteDNS "local" mode. The endpoint lookup now follows
sockopt.domainStrategy and in-tunnel targets the outbound's
targetStrategy. The old key is silently ignored, which undid the
IPv4-first endpoint lookup the WARP outbound depends on (#5205), and
"local" now panics the core at startup because remoteDNS goes through
netip.MustParseAddr. The WireguardDomainStrategyFix seeder moves a stored
family preference to both keys (a value already set wins) and turns
"local" into targetStrategy; the outbound form lifts legacy rows the same
way and drops its select, the WARP modal writes the new placement, and
the inbound form loses a field the server never read.
ValidateOutboundConfig now refuses a non-IP remoteDNS entry, which
conf.Build() lets through, on template save and for outbound
subscriptions.

Noise finalmask items accept type "exp" (#6862), a tag expression. The
form offers it for noise items only: header-custom items go through the
core's PraseByteSlice, which refuses it.

TUN gained autoSystemDnsToGateway (Linux) and autoSystemWfpBlockLeak
(Windows). Both pass through the settings schema so a value set in JSON
survives the next form save.

MASQUE (inbound, outbound, transport) and the XDRIVE transport are new
protocols the panel does not offer yet; their new loader refusals only
cover configs the panel never generates. The FakeDNS IPv6 pool default,
the SS2022 rewrite (same gRPC account; emails are now deduped
case-insensitively, as the panel already does), the restored udphop
interval default and the rest change no panel-facing config.
2026-10-02 13:54:39 +02:00

108 lines
3.1 KiB
Go

package maskcompat
import "strings"
// legacyXdnsEDNS0 is the EDNS0 payload the pre-26.9.30 xdns always negotiated;
// the object shape makes it opt-in and caps every answer at 512 bytes without it.
const legacyXdnsEDNS0 = 1232
var legacyXdnsRecordTypes = map[string]int{"": 16, "txt": 16, "a": 1, "aaaa": 28}
// UpgradeLegacyXdns rewrites xdns masks from the string lists xray-core 26.9.30
// (#6718) no longer parses into its object lists; one legacy mask fails the whole config.
func UpgradeLegacyXdns(finalmask any) bool {
fm, _ := finalmask.(map[string]any)
masks, _ := fm["udp"].([]any)
changed := false
for _, entry := range masks {
mask, _ := entry.(map[string]any)
if maskType, _ := mask["type"].(string); !strings.EqualFold(maskType, "xdns") {
continue
}
if settings, ok := mask["settings"].(map[string]any); ok && upgradeLegacyXdnsSettings(settings) {
changed = true
}
}
return changed
}
// upgradeLegacyXdnsSettings maps a bare name to TXT, the type legacy clients queried by
// default, and drops entries the old core refused instead of keeping them to fail again.
func upgradeLegacyXdnsSettings(settings map[string]any) bool {
rawDomains, _ := settings["domains"].([]any)
rawResolvers, _ := settings["resolvers"].([]any)
if !hasLegacyXdnsEntry(rawDomains) && !hasLegacyXdnsEntry(rawResolvers) {
return false
}
domains := make([]any, 0, len(rawDomains)+len(rawResolvers))
listed := map[string]bool{}
addDomain := func(domain map[string]any) {
key, _ := domain["name"].(string)
key = strings.ToLower(key)
if key != "" && listed[key] {
return
}
listed[key] = true
domains = append(domains, domain)
}
for _, entry := range rawDomains {
switch value := entry.(type) {
case map[string]any:
addDomain(value)
case string:
if domain, ok := legacyXdnsDomain(value); ok {
addDomain(domain)
}
}
}
resolvers := make([]any, 0, len(rawResolvers))
for _, entry := range rawResolvers {
spec, ok := entry.(string)
if !ok {
resolvers = append(resolvers, entry)
continue
}
head, addr, found := strings.Cut(spec, "+udp://")
addr = strings.TrimSpace(addr)
domain, valid := legacyXdnsDomain(head)
if !found || addr == "" || !valid {
continue
}
addDomain(domain)
resolvers = append(resolvers, map[string]any{
"type": "udp",
"settings": map[string]any{"addr": addr},
})
}
settings["domains"] = domains
if len(resolvers) > 0 {
settings["resolvers"] = resolvers
} else {
delete(settings, "resolvers")
}
return true
}
// legacyXdnsDomain parses the "name[:txt|a|aaaa]" spec both legacy lists used.
func legacyXdnsDomain(spec string) (map[string]any, bool) {
name, method := strings.TrimSpace(spec), ""
if i := strings.LastIndex(name, ":"); i >= 0 {
name, method = name[:i], strings.ToLower(name[i+1:])
}
name = strings.Trim(name, ".")
recordType, known := legacyXdnsRecordTypes[method]
if name == "" || !known {
return nil, false
}
return map[string]any{"name": name, "types": []any{recordType}, "edns0": legacyXdnsEDNS0}, true
}
func hasLegacyXdnsEntry(values []any) bool {
for _, value := range values {
if _, ok := value.(string); ok {
return true
}
}
return false
}