mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-11-04 12:15:02 +03:00
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.
This commit is contained in:
@@ -23,6 +23,7 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/mtproto"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/tuic"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/common"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/maskcompat"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/netsafe"
|
||||
wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
|
||||
|
||||
@@ -654,6 +655,27 @@ func (s *InboundService) normalizeStreamSettings(inbound *model.Inbound) {
|
||||
return
|
||||
}
|
||||
inbound.StreamSettings = canonicalizeStreamNetworkKey(inbound.StreamSettings)
|
||||
inbound.StreamSettings = canonicalizeLegacyXdnsMasks(inbound.StreamSettings)
|
||||
}
|
||||
|
||||
// canonicalizeLegacyXdnsMasks stores an xdns mask posted in the pre-26.9.30 string
|
||||
// lists in the object shape the core parses, as GetXrayConfig would heal it anyway.
|
||||
func canonicalizeLegacyXdnsMasks(streamSettings string) string {
|
||||
if streamSettings == "" {
|
||||
return streamSettings
|
||||
}
|
||||
var stream map[string]any
|
||||
if err := json.Unmarshal([]byte(streamSettings), &stream); err != nil {
|
||||
return streamSettings
|
||||
}
|
||||
if !maskcompat.UpgradeLegacyXdns(stream["finalmask"]) {
|
||||
return streamSettings
|
||||
}
|
||||
out, err := json.MarshalIndent(stream, "", " ")
|
||||
if err != nil {
|
||||
return streamSettings
|
||||
}
|
||||
return string(out)
|
||||
}
|
||||
|
||||
// canonicalizeStreamNetworkKey rewrites a streamSettings JSON that names its
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
)
|
||||
|
||||
const legacyXdnsStream = `{"network":"kcp","security":"none","kcpSettings":{"mtu":900},"finalmask":{"udp":[{"type":"xdns","settings":{"domains":["t.example.com"]}}]}}`
|
||||
|
||||
func firstXdnsDomain(t *testing.T, stream map[string]any) any {
|
||||
t.Helper()
|
||||
finalmask, _ := stream["finalmask"].(map[string]any)
|
||||
udp, _ := finalmask["udp"].([]any)
|
||||
if len(udp) != 1 {
|
||||
t.Fatalf("finalmask.udp = %v, want one mask", finalmask["udp"])
|
||||
}
|
||||
mask, _ := udp[0].(map[string]any)
|
||||
settings, _ := mask["settings"].(map[string]any)
|
||||
domains, _ := settings["domains"].([]any)
|
||||
if len(domains) != 1 {
|
||||
t.Fatalf("xdns domains = %v, want one", settings["domains"])
|
||||
}
|
||||
return domains[0]
|
||||
}
|
||||
|
||||
// An API client can still post the pre-26.9.30 string lists; stored as sent they would
|
||||
// reach the sub links and the form in a shape the core no longer parses.
|
||||
func TestAddInbound_StoresXdnsMaskInObjectShape(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
in := &model.Inbound{
|
||||
Tag: "in-45300-kcp", Enable: true, Listen: "0.0.0.0", Port: 45300, Protocol: model.VLESS,
|
||||
Settings: `{"clients":[],"decryption":"none"}`, StreamSettings: legacyXdnsStream,
|
||||
}
|
||||
if _, _, err := (&InboundService{}).AddInbound(in); err != nil {
|
||||
t.Fatalf("AddInbound: %v", err)
|
||||
}
|
||||
|
||||
var stored model.Inbound
|
||||
if err := database.GetDB().Where("tag = ?", "in-45300-kcp").First(&stored).Error; err != nil {
|
||||
t.Fatalf("reload: %v", err)
|
||||
}
|
||||
var stream map[string]any
|
||||
if err := json.Unmarshal([]byte(stored.StreamSettings), &stream); err != nil {
|
||||
t.Fatalf("stored stream is not JSON: %v", err)
|
||||
}
|
||||
domain, ok := firstXdnsDomain(t, stream).(map[string]any)
|
||||
if !ok || domain["name"] != "t.example.com" {
|
||||
t.Fatalf("stored xdns domain = %#v, want an object named t.example.com", firstXdnsDomain(t, stream))
|
||||
}
|
||||
}
|
||||
|
||||
// A row that never went through the save path (restored backup, node sync, direct DB
|
||||
// edit) must still reach the core in a shape it builds, or it keeps every inbound down.
|
||||
func TestGetXrayConfig_UpgradesLegacyXdnsMask(t *testing.T) {
|
||||
setupConflictDB(t)
|
||||
seedInboundConflict(t, "in-45301-kcp", "127.0.0.1", 45301, model.VLESS,
|
||||
legacyXdnsStream, `{"clients":[],"decryption":"none"}`)
|
||||
|
||||
var legacy map[string]any
|
||||
if err := json.Unmarshal([]byte(`{"tag":"in-45301-kcp","listen":"127.0.0.1","port":45301,"protocol":"vless",
|
||||
"settings":{"clients":[],"decryption":"none"},"streamSettings":`+legacyXdnsStream+`}`), &legacy); err != nil {
|
||||
t.Fatalf("decode legacy inbound: %v", err)
|
||||
}
|
||||
if err := buildGoldenInbound(t, legacy); err == nil {
|
||||
t.Fatal("xray-core accepted the legacy xdns lists; the heal is no longer needed")
|
||||
}
|
||||
|
||||
cfg, err := (&XrayService{}).GetXrayConfig()
|
||||
if err != nil {
|
||||
t.Fatalf("GetXrayConfig: %v", err)
|
||||
}
|
||||
for i := range cfg.InboundConfigs {
|
||||
if cfg.InboundConfigs[i].Tag != "in-45301-kcp" {
|
||||
continue
|
||||
}
|
||||
raw, err := json.Marshal(cfg.InboundConfigs[i])
|
||||
if err != nil {
|
||||
t.Fatalf("marshal emitted inbound: %v", err)
|
||||
}
|
||||
var emitted map[string]any
|
||||
if err := json.Unmarshal(raw, &emitted); err != nil {
|
||||
t.Fatalf("decode emitted inbound: %v", err)
|
||||
}
|
||||
assertXrayAccepts(t, "the healed xdns inbound", buildGoldenInbound(t, emitted))
|
||||
return
|
||||
}
|
||||
t.Fatal("inbound in-45301-kcp not found in the generated config")
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/logger"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/json_util"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/util/maskcompat"
|
||||
"github.com/mhsanaei/3x-ui/v3/internal/xray"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
@@ -366,6 +367,9 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) {
|
||||
logger.Warningf("Inbound %q: dropping %d XMC finalmask mask(s) without complete Minecraft profiles — reconfigure them to restore the obfuscation (see XTLS/Xray-core#6487)", inbound.Tag, dropped)
|
||||
}
|
||||
|
||||
// A row that skipped the save path can still carry the pre-26.9.30 xdns lists.
|
||||
maskcompat.UpgradeLegacyXdns(stream["finalmask"])
|
||||
|
||||
// xray-core v26.6.22 (#6258) renamed the XHTTP session keys and
|
||||
// kept no fallback. Lift legacy sessionPlacement/sessionKey onto the
|
||||
// new names here so inbounds stored before the rename keep working
|
||||
|
||||
Reference in New Issue
Block a user