mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-28 00:24:19 +00:00
c7a76e9626
* fix: enable XTLS vision flow for VLESS+XHTTP+vlessenc in UI and share links (#5157) * fix: enable xtls-rprx-vision flow for VLESS XHTTP with vlessenc encryption (#5157) The flow selector was hidden and the vless:// link omitted flow= because: 1. The backend gate (inboundCanEnableTlsFlow) only accepted tcp+tls/reality. 2. The PR #5185 frontend check used `encryption === 'vlessenc'`, which never matches — the stored value is a generated ML-KEM dotted string, not the CLI subcommand name. Fix: extend inboundCanEnableTlsFlow to also return true for XHTTP when a non-none vlessenc encryption/decryption value is present. Update all three call-sites (inbound.go TlsFlowCapable field, client_crud.go clientWithInboundFlow, inbound_clients.go copy-flow path) and the sub/service.go link generator. Scope is XHTTP-only: TCP without tls/reality is intentionally excluded. Add inbound_protocol_test.go covering the new and existing gate combinations, extend client_flow_isolation_test.go with xhttp+vlessenc cases, and add frontend tests for canEnableTlsFlow with real ML-KEM key values. --------- Co-authored-by: rqzbeh <rqzbeh@users.noreply.github.com> Co-authored-by: Sanaei <ho3ein.sanaei@gmail.com>
91 lines
4.7 KiB
Go
91 lines
4.7 KiB
Go
package service
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/internal/database/model"
|
|
)
|
|
|
|
// A representative vlessenc/ML-KEM encryption value as produced by `xray
|
|
// vlessenc` — a dotted string, never the literal "vlessenc".
|
|
const vlessEncValue = "mlkem768x25519plus.native.0rtt.G3cdPSd1-NnlpTbWNSM5vHsT5VNzWfFzYSKwbUMnV1Y"
|
|
|
|
func TestInboundCanEnableTlsFlow(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
protocol string
|
|
streamSettings string
|
|
settings string
|
|
want bool
|
|
}{
|
|
{"vless tcp tls", string(model.VLESS), `{"network":"tcp","security":"tls"}`, "", true},
|
|
{"vless tcp reality", string(model.VLESS), `{"network":"tcp","security":"reality"}`, "", true},
|
|
{"vless tcp none no enc", string(model.VLESS), `{"network":"tcp","security":"none"}`, "", false},
|
|
{"vless ws tls", string(model.VLESS), `{"network":"ws","security":"tls"}`, "", false},
|
|
{"vless grpc reality", string(model.VLESS), `{"network":"grpc","security":"reality"}`, "", false},
|
|
{"vmess tcp tls", string(model.VMESS), `{"network":"tcp","security":"tls"}`, "", false},
|
|
{"empty stream", string(model.VLESS), "", "", false},
|
|
|
|
// vlessenc is gated to XHTTP only. TCP without tls/reality is NOT
|
|
// Vision-capable even with vlessenc set — the combination only works on
|
|
// XHTTP in practice.
|
|
{"vless tcp vlessenc not capable", string(model.VLESS), `{"network":"tcp","security":"none"}`, `{"decryption":"mlkem768x25519plus.native.600s.mMFxPe7lz5xoq2qBk22cQYefu5fpc_2dGR8lMOKem0E","encryption":"mlkem768x25519plus.native.0rtt.hT4AY_tPWY9NVuKR3BIXxXq6zx9DqN2X86QPYW09XEM"}`, false},
|
|
// ws is a framed transport — vlessenc never enables Vision there.
|
|
{"vless ws vlessenc still off", string(model.VLESS), `{"network":"ws","security":"none"}`, `{"encryption":"` + vlessEncValue + `"}`, false},
|
|
|
|
// XHTTP + VLESS encryption (the #5157 case).
|
|
{"vless xhttp vlessenc", string(model.VLESS), `{"network":"xhttp","security":"none"}`, `{"encryption":"` + vlessEncValue + `"}`, true},
|
|
{"vless xhttp encryption none", string(model.VLESS), `{"network":"xhttp","security":"none"}`, `{"encryption":"none"}`, false},
|
|
{"vless xhttp no settings", string(model.VLESS), `{"network":"xhttp","security":"none"}`, "", false},
|
|
// Regression for PR #5185: the gate is "any non-none encryption", NOT an
|
|
// equality check against the literal "vlessenc" (which the buggy PR used
|
|
// and which never matches a real, generated encryption value). An x25519
|
|
// auth value must enable it just like the ML-KEM value above.
|
|
{"vless xhttp x25519 enc", string(model.VLESS), `{"network":"xhttp","security":"none"}`, `{"encryption":"native.0rtt.121s-180s.xRMUYYjQctqYO1pSyffM-w"}`, true},
|
|
// Server-side configs (API/JSON) may carry only decryption; that alone
|
|
// must also enable the flow gate.
|
|
{"vless xhttp decryption only", string(model.VLESS), `{"network":"xhttp","security":"none"}`, `{"decryption":"` + vlessEncValue + `","encryption":"none"}`, true},
|
|
// XHTTP without encryption stays off even with tls (Vision over XHTTP is
|
|
// gated on vlessenc, not transport security).
|
|
{"vless xhttp tls no encryption", string(model.VLESS), `{"network":"xhttp","security":"tls"}`, `{"encryption":"none"}`, false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := inboundCanEnableTlsFlow(tc.protocol, tc.streamSettings, tc.settings)
|
|
if got != tc.want {
|
|
t.Errorf("inboundCanEnableTlsFlow(%q, %q, %q) = %v, want %v",
|
|
tc.protocol, tc.streamSettings, tc.settings, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Fallbacks must remain raw-TCP-only and must NOT follow the broadened flow gate
|
|
// onto XHTTP+vlessenc.
|
|
func TestInboundCanHostFallbacks_StaysTcpOnly(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
protocol model.Protocol
|
|
streamSettings string
|
|
settings string
|
|
want bool
|
|
}{
|
|
{"vless tcp tls", model.VLESS, `{"network":"tcp","security":"tls"}`, "", true},
|
|
{"trojan tcp reality", model.Trojan, `{"network":"tcp","security":"reality"}`, "", true},
|
|
{"vless xhttp vlessenc not fallback-capable", model.VLESS, `{"network":"xhttp","security":"none"}`, `{"encryption":"` + vlessEncValue + `"}`, false},
|
|
{"vmess tcp tls not fallback-capable", model.VMESS, `{"network":"tcp","security":"tls"}`, "", false},
|
|
{"nil-ish empty stream", model.VLESS, "", "", false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ib := &model.Inbound{Protocol: tc.protocol, StreamSettings: tc.streamSettings, Settings: tc.settings}
|
|
if got := inboundCanHostFallbacks(ib); got != tc.want {
|
|
t.Errorf("inboundCanHostFallbacks = %v, want %v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
if inboundCanHostFallbacks(nil) {
|
|
t.Errorf("inboundCanHostFallbacks(nil) = true, want false")
|
|
}
|
|
}
|