mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-17 07:37:15 +00:00
feat(xray): update xray-core to v26.9.9 and follow the udpHop move
Bump xtls/xray-core to 52a412d9e2f5 (v26.9.9) and the three binary pins in DockerInit.sh and release.yml in lockstep. Upstream moved UDP port hopping out of finalmask.quicParams.udpHop and into a standalone "udphop" UDP mask with a different shape (mode / interval / remotePorts / remoteIPs). The old key is gone from QuicParams, and since the config loader ignores unknown fields it is now silently dropped rather than rejected — port hopping just stops. The panel adapts where that key was live: - Both link importers rebuilt quicParams.udpHop from the standard mport param, so an imported hysteria2 link produced an outbound that no longer hops. They now emit a udphop mask in intervalremote mode, which is what the old key did. The mode is required: UDPHop.Build() rejects an empty or unknown one. - validFinalMaskUDPTypes and UdpMaskTypeSchema learn "udphop", otherwise the Go link generator strips the mask from every link and sub, and Zod strips it on the next form round trip. - mport generation (Go and frontend) reads the mask first and keeps reading the legacy key, so inbounds stored before the upgrade still advertise their range. On an inbound the old key was always inert — only hysteria's dialer consumed it — so nothing regresses server-side and no migration is needed. udphop stays out of the mask dropdown on purpose: it is client-only in core, which refuses to wrap a server socket, and that form is shared with the inbound editor.
This commit is contained in:
@@ -1081,6 +1081,19 @@ func TestMarshalFinalMask_KeepsXmcTcpMask(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarshalFinalMask_KeepsUdpHopMask(t *testing.T) {
|
||||
fm := map[string]any{
|
||||
"udp": []any{udpHopMask("20000-50000")},
|
||||
}
|
||||
out, ok := marshalFinalMask(fm)
|
||||
if !ok {
|
||||
t.Fatal("expected ok=true for a udphop udp mask")
|
||||
}
|
||||
if !strings.Contains(out, "udphop") || !strings.Contains(out, "20000-50000") {
|
||||
t.Fatalf("marshaled finalmask dropped the udphop mask: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasFinalMaskContent(t *testing.T) {
|
||||
if hasFinalMaskContent(nil) {
|
||||
t.Fatal("nil should not count as content")
|
||||
@@ -1127,6 +1140,13 @@ func TestHysteriaPinHex(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func udpHopMask(ports string) map[string]any {
|
||||
return map[string]any{
|
||||
"type": "udphop",
|
||||
"settings": map[string]any{"mode": "intervalremote", "interval": "5-10", "remotePorts": ports},
|
||||
}
|
||||
}
|
||||
|
||||
func TestHysteriaHopPorts(t *testing.T) {
|
||||
withHop := func(ports any) map[string]any {
|
||||
return map[string]any{
|
||||
@@ -1137,6 +1157,11 @@ func TestHysteriaHopPorts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
}
|
||||
withHopMask := func(ports string) map[string]any {
|
||||
return map[string]any{
|
||||
"finalmask": map[string]any{"udp": []any{udpHopMask(ports)}},
|
||||
}
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
@@ -1144,6 +1169,14 @@ func TestHysteriaHopPorts(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{"range", withHop("20000-50000"), "20000-50000"},
|
||||
{"udphop mask", withHopMask("20000-50000"), "20000-50000"},
|
||||
{"udphop mask wins over legacy key", map[string]any{
|
||||
"finalmask": map[string]any{
|
||||
"udp": []any{udpHopMask("30000-40000")},
|
||||
"quicParams": map[string]any{"udpHop": map[string]any{"ports": "20000-50000"}},
|
||||
},
|
||||
}, "30000-40000"},
|
||||
{"udphop mask without remotePorts", withHopMask(""), ""},
|
||||
{"trimmed", withHop(" 443,20000-50000 "), "443,20000-50000"},
|
||||
{"empty string", withHop(""), ""},
|
||||
{"non-string", withHop(float64(443)), ""},
|
||||
|
||||
Reference in New Issue
Block a user