mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-12 17:05:01 +03:00
ec0a57fc4f
xray-core 26.9.9 dropped finalmask.quicParams.udpHop for the client-only
"udphop" UDP mask {mode, interval, remoteIPs, remotePorts}. The panel kept
writing the old key, so hysteria/xhttp outbounds and JSON-subscription
clients silently stopped hopping ports.
An inbound keeps quicParams.udpHop: it is panel metadata advertised as the
link's mport, and a udphop mask on a listener fails with "udphop: client
only". The conversion (maskcompat.UpgradeLegacyUDPHop, intervalRemote mode)
runs only where a client config is produced:
- UDPHopClientMaskFix seeder over hosts, sub-JSON finalmask, cached
outbound-subscription outbounds and template outbounds (never inbounds);
- GetXrayConfig heals template outbounds at build time;
- the JSON subscription upgrades each generated client stream;
- the Go and frontend link importers upgrade a legacy fm= param.
The finalmask editor gains a side prop: the client side lifts the dead key
on mount and offers a udphop mask editor (also for xhttp's HTTP/3 dial);
the inbound side keeps the UDP Hop switch, labelled as advertised.
55 lines
1.4 KiB
Go
55 lines
1.4 KiB
Go
package maskcompat
|
|
|
|
import "strings"
|
|
|
|
// UpgradeLegacyUDPHop moves the quicParams.udpHop range xray-core 26.9.9 stopped reading
|
|
// into the client-only "udphop" UDP mask, whose intervalRemote mode is what the key did.
|
|
func UpgradeLegacyUDPHop(finalmask any) bool {
|
|
fm, _ := finalmask.(map[string]any)
|
|
quicParams, _ := fm["quicParams"].(map[string]any)
|
|
legacy, found := quicParams["udpHop"]
|
|
if !found {
|
|
return false
|
|
}
|
|
delete(quicParams, "udpHop")
|
|
if len(quicParams) == 0 {
|
|
delete(fm, "quicParams")
|
|
}
|
|
|
|
hop, _ := legacy.(map[string]any)
|
|
ports := hop["ports"]
|
|
if isEmptyHopValue(ports) || hasUDPHopMask(fm) {
|
|
return true
|
|
}
|
|
settings := map[string]any{"mode": "intervalRemote", "remotePorts": ports}
|
|
if interval := hop["interval"]; !isEmptyHopValue(interval) {
|
|
settings["interval"] = interval
|
|
}
|
|
masks, _ := fm["udp"].([]any)
|
|
fm["udp"] = append(masks, map[string]any{"type": "udphop", "settings": settings})
|
|
return true
|
|
}
|
|
|
|
func hasUDPHopMask(fm map[string]any) bool {
|
|
masks, _ := fm["udp"].([]any)
|
|
for _, entry := range masks {
|
|
mask, _ := entry.(map[string]any)
|
|
if maskType, _ := mask["type"].(string); strings.EqualFold(maskType, "udphop") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isEmptyHopValue(value any) bool {
|
|
switch v := value.(type) {
|
|
case nil:
|
|
return true
|
|
case string:
|
|
return strings.TrimSpace(v) == ""
|
|
case float64:
|
|
return v == 0
|
|
}
|
|
return false
|
|
}
|