mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-30 23:17:14 +00:00
fix(xray): reject configs xray-core refuses, and check the fixtures against it
The frontend's golden fixtures are the panel's model of an xray config, but nothing ever asked xray-core whether it would accept them: the snapshots only prove the Zod schemas agree with themselves. Building every fixture through the same config builders the panel hands its config to — conf.InboundDetourConfig for the full-config and AddInbound paths, conf.RouterConfig for ApplyRoutingConfig, conf.DNSConfig for the dns section — found seven the core refuses, three of them reachable from the panel's own UI. A refusal is not scoped to one inbound: the config fails to load and every inbound stays down. Hysteria: xray-core builds version 2 only, in both the protocol settings and the transport settings, but the inbound settings schema accepted any version from 1 up and its comment claimed upstream still supported v1. Both fixtures carried version 1. The schema now pins 2, GenXrayInboundConfig heals stored rows on the way out the way it already heals shadowsocks ciphers and wireguard peers, and the share link drops the dead hysteria:// scheme — the subscription server already emitted hysteria2:// for the same inbound. XHTTP uplinkDataPlacement: both transport forms offered "query", which the core has never accepted for that field (auto and body always, cookie and header in packet-up mode). Replaced with auto, which was missing, and the default label now names auto rather than body. FinalMask items: switching an item to the rand-driven array kind wrote packet:[] next to the rand. xray-core counts an empty array as a packet and every item kind is exclusive, so noise answers "len(item.Packet) > 0 && item.Rand.To > 0" and header-custom "exactly one item kind must be set". The editor now clears the packet, and GetXrayConfig strips the residue from rows already saved with it. The remaining four were stale fixtures: an xmc mask still on the usernames shape v26.7.28 replaced with profiles, a fragment mask with no length, and header-custom and noise items passing an array to the string packet kind — all shapes the panel's own editors cannot produce. golden_fixtures_xray_test.go keeps this from drifting again: every fixture in every category is built through xray-core on each run, with a self-signed pair standing in for the deployment certificate paths, so the next core bump reports which fixture it broke.
This commit is contained in:
@@ -713,6 +713,52 @@ func stripIncompleteXmcMasks(stream map[string]any) int {
|
||||
return dropped
|
||||
}
|
||||
|
||||
// dropEmptyRandPackets removes the leftover empty "packet" from finalmask
|
||||
// items that also carry a rand, and reports how many it cleared.
|
||||
//
|
||||
// xray-core treats even an empty array as a packet, and every item kind is
|
||||
// exclusive: noise refuses "len(item.Packet) > 0 && item.Rand.To > 0" and
|
||||
// header-custom refuses "exactly one item kind must be set". Either error
|
||||
// fails the whole config build, so one such item keeps every inbound offline.
|
||||
// The panel's mask editor wrote that pair whenever an item was switched to the
|
||||
// rand-driven array kind, so stored rows carry it; clearing an empty packet
|
||||
// changes nothing about the mask the admin configured.
|
||||
func dropEmptyRandPackets(node any) int {
|
||||
switch value := node.(type) {
|
||||
case map[string]any:
|
||||
cleared := 0
|
||||
if packet, ok := value["packet"].([]any); ok && len(packet) == 0 && randIsSet(value["rand"]) {
|
||||
delete(value, "packet")
|
||||
cleared++
|
||||
}
|
||||
for _, child := range value {
|
||||
cleared += dropEmptyRandPackets(child)
|
||||
}
|
||||
return cleared
|
||||
case []any:
|
||||
cleared := 0
|
||||
for _, child := range value {
|
||||
cleared += dropEmptyRandPackets(child)
|
||||
}
|
||||
return cleared
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// randIsSet reports whether a finalmask item's rand selects a random packet.
|
||||
// It is a number on header-custom items and a dash-range string on noise ones.
|
||||
func randIsSet(value any) bool {
|
||||
switch rand := value.(type) {
|
||||
case float64:
|
||||
return rand > 0
|
||||
case string:
|
||||
return rand != "" && rand != "0" && rand != "0-0"
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// validateFinalMaskXmcProfiles rejects an xmc finalmask without complete
|
||||
// profiles at save time, so the admin gets a targeted error instead of a core
|
||||
// that refuses to start (or, after GetXrayConfig heals it, an inbound quietly
|
||||
|
||||
Reference in New Issue
Block a user