mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-16 15:17:14 +00:00
fix(sub): bind JSON local inbounds to 127.0.0.1 and keep mux.cool off Vision outbounds (#6418)
* fix(sub): bind JSON local inbounds to 127.0.0.1 and keep mux.cool off Vision outbounds The JSON subscription's local SOCKS/HTTP inbounds had no listen address, so every client that runs the profile verbatim bound an unauthenticated proxy on 0.0.0.0, and iOS packet-tunnel clients could not reach it at all (Happ iOS: CONNECTED with zero traffic, same symptom as #6379 — on the same device the mixed inbound also worked once bound to 127.0.0.1). Bind both to loopback, which is what every client's own generated config does. The global subJsonMux was also applied to VLESS outbounds carrying xtls-rprx-vision. XTLS flows do not support mux.cool: Xray answers the mux handshake with "common/mux: unexpected network TCP" and the tunnel passes nothing, on every platform (verified with Happ iOS/Android/macOS, V2Box iOS and desktop Xray 26.6.27 against a 3x-ui 3.7.0 box with per-client traffic counters). Skip the mux block whenever the outbound carries a flow. Refs #6379 * fix(sub): keep XUDP settings when disabling TCP mux on Vision outbounds Clearing the whole mux object also dropped xudpConcurrency, xudpProxyUDP443 and any per-host muxParams override. Xray reads those only under mux.enabled, so set concurrency to -1 instead: TCP mux.cool (which XTLS flows reject) is off, XUDP and the UDP/443 policy stay. The test now decodes each outbound into a fresh map. --------- Co-authored-by: Farhan Zare <farhan.zare@openscreen.com>
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 10808,
|
||||
"protocol": "socks",
|
||||
"settings": {
|
||||
@@ -31,6 +32,7 @@
|
||||
"tag": "mixed"
|
||||
},
|
||||
{
|
||||
"listen": "127.0.0.1",
|
||||
"port": 10809,
|
||||
"protocol": "http",
|
||||
"settings": {
|
||||
|
||||
@@ -788,12 +788,31 @@ func (s *SubJsonService) genVless(subReq *SubService, inbound *model.Inbound, st
|
||||
}
|
||||
if client.Flow != "" && !inbound.DisableFlow {
|
||||
settings["flow"] = client.Flow
|
||||
outbound.Mux = muxWithoutTCP(mux)
|
||||
}
|
||||
outbound.Settings = settings
|
||||
result, _ := json.MarshalIndent(outbound, "", " ")
|
||||
return result
|
||||
}
|
||||
|
||||
// XTLS flows reject TCP mux.cool ("unexpected network TCP"); concurrency -1
|
||||
// turns only that off and keeps the XUDP keys (Xray reads them under enabled).
|
||||
func muxWithoutTCP(mux string) json_util.RawMessage {
|
||||
if mux == "" {
|
||||
return nil
|
||||
}
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal([]byte(mux), &m); err != nil || m == nil {
|
||||
return nil
|
||||
}
|
||||
m["concurrency"] = -1
|
||||
out, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return json_util.RawMessage(out)
|
||||
}
|
||||
|
||||
func (s *SubJsonService) genServer(subReq *SubService, inbound *model.Inbound, streamSettings json_util.RawMessage, client model.Client, mux string) json_util.RawMessage {
|
||||
outbound := Outbound{}
|
||||
|
||||
|
||||
@@ -66,11 +66,55 @@ func TestDefaultJSONUsesCompatibleLocalInbounds(t *testing.T) {
|
||||
if settings == nil || settings["udp"] != true {
|
||||
t.Fatalf("port 10808 settings = %#v, want udp enabled", socks["settings"])
|
||||
}
|
||||
if socks["listen"] != "127.0.0.1" {
|
||||
t.Fatalf("port 10808 listen = %#v, want 127.0.0.1 (an unbound local inbound is exposed to the LAN and is not reachable by iOS packet tunnels)", socks["listen"])
|
||||
}
|
||||
|
||||
http := byPort[10809]
|
||||
if http == nil || http["protocol"] != "http" {
|
||||
t.Fatalf("port 10809 inbound = %#v, want http protocol", http)
|
||||
}
|
||||
if http["listen"] != "127.0.0.1" {
|
||||
t.Fatalf("port 10809 listen = %#v, want 127.0.0.1", http["listen"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubJsonServiceVisionFlowDisablesTCPMuxOnly(t *testing.T) {
|
||||
globalMux := `{"enabled":true,"concurrency":8,"xudpConcurrency":16,"xudpProxyUDP443":"reject"}`
|
||||
svc := NewSubJsonService(globalMux, "", "", nil)
|
||||
inbound := &model.Inbound{Listen: "1.2.3.4", Port: 443, Protocol: model.VLESS, Settings: `{"encryption":"none"}`}
|
||||
|
||||
decode := func(raw []byte) map[string]any {
|
||||
t.Helper()
|
||||
var ob map[string]any
|
||||
if err := json.Unmarshal(raw, &ob); err != nil {
|
||||
t.Fatalf("unmarshal outbound: %v", err)
|
||||
}
|
||||
return ob
|
||||
}
|
||||
|
||||
vision := decode([]byte(svc.genVless(&SubService{}, inbound, nil, model.Client{ID: "uuid-1", Flow: "xtls-rprx-vision"}, globalMux)))
|
||||
mux, _ := vision["mux"].(map[string]any)
|
||||
if mux == nil {
|
||||
t.Fatalf("vision outbound must keep its mux object for the XUDP keys, got %#v", vision["mux"])
|
||||
}
|
||||
if mux["concurrency"] != float64(-1) {
|
||||
t.Fatalf("vision outbound mux.concurrency = %v, want -1 (TCP mux.cool is rejected by XTLS flows)", mux["concurrency"])
|
||||
}
|
||||
if mux["enabled"] != true || mux["xudpConcurrency"] != float64(16) || mux["xudpProxyUDP443"] != "reject" {
|
||||
t.Fatalf("vision outbound lost its XUDP settings: %#v", mux)
|
||||
}
|
||||
|
||||
plain := decode([]byte(svc.genVless(&SubService{}, inbound, nil, model.Client{ID: "uuid-1"}, globalMux)))
|
||||
mux, _ = plain["mux"].(map[string]any)
|
||||
if mux == nil || mux["concurrency"] != float64(8) {
|
||||
t.Fatalf("flow-less outbound must keep the global mux unchanged, got %#v", plain["mux"])
|
||||
}
|
||||
|
||||
noMux := decode([]byte(svc.genVless(&SubService{}, inbound, nil, model.Client{ID: "uuid-1", Flow: "xtls-rprx-vision"}, "")))
|
||||
if _, has := noMux["mux"]; has {
|
||||
t.Fatalf("no global mux must still mean no mux key, got %#v", noMux["mux"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubJsonServiceInjectsGlobalFinalMask(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user