소스 검색

fix(inbound): strip XHTTP client-only fields from xray config, keep for subscriptions (#5349)

Inbound XMUX and other client-side xHTTP knobs were written into
bin/config.json even though xray-core's server listener ignores them.
Strip them in GenXrayInboundConfig while leaving the DB row intact so
buildXhttpExtra still pushes defaults to clients via share links.
nima1024m 14 시간 전
부모
커밋
cdaf5f80db
3개의 변경된 파일131개의 추가작업 그리고 2개의 파일을 삭제
  1. 1 1
      frontend/src/test/stream-wire-normalize.test.ts
  2. 48 1
      internal/database/model/model.go
  3. 82 0
      internal/database/model/model_test.go

+ 1 - 1
frontend/src/test/stream-wire-normalize.test.ts

@@ -66,7 +66,7 @@ describe('normalizeXhttpForWire stream-one', () => {
     expect(out).not.toHaveProperty('scMaxEachPostBytes');
   });
 
-  it('keeps inbound xmux when enableXmux is on (for the share-link extra)', () => {
+  it('keeps inbound xmux when enableXmux is on (stored for subscription extra; stripped from xray config on Go side)', () => {
     const out = normalizeXhttpForWire({
       path: '/app',
       mode: 'auto',

+ 48 - 1
internal/database/model/model.go

@@ -225,6 +225,49 @@ func jsonStringFieldFromRaw(r json.RawMessage) string {
 	return string(trimmed)
 }
 
+// StripInboundXhttpClientFields removes xHTTP knobs that belong on the
+// client dialer and subscription share-link extras only. xray-core's XHTTP
+// inbound listener does not consume them; the panel still stores them on
+// the inbound row so buildXhttpExtra can push defaults to clients.
+func StripInboundXhttpClientFields(streamSettings string) (string, bool) {
+	if streamSettings == "" {
+		return streamSettings, false
+	}
+	var stream map[string]any
+	if err := json.Unmarshal([]byte(streamSettings), &stream); err != nil {
+		return streamSettings, false
+	}
+	if stream["network"] != "xhttp" {
+		return streamSettings, false
+	}
+	xhttp, ok := stream["xhttpSettings"].(map[string]any)
+	if !ok || len(xhttp) == 0 {
+		return streamSettings, false
+	}
+	clientOnly := []string{
+		"xmux",
+		"downloadSettings",
+		"scMinPostsIntervalMs",
+		"uplinkChunkSize",
+		"noGRPCHeader",
+	}
+	changed := false
+	for _, key := range clientOnly {
+		if _, has := xhttp[key]; has {
+			delete(xhttp, key)
+			changed = true
+		}
+	}
+	if !changed {
+		return streamSettings, false
+	}
+	out, err := json.MarshalIndent(stream, "", "  ")
+	if err != nil {
+		return streamSettings, false
+	}
+	return string(out), true
+}
+
 // GenXrayInboundConfig generates an Xray inbound configuration from the Inbound model.
 func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
 	listen := i.Listen
@@ -248,12 +291,16 @@ func (i *Inbound) GenXrayInboundConfig() *xray.InboundConfig {
 			settings = stripped
 		}
 	}
+	streamSettings := i.StreamSettings
+	if stripped, ok := StripInboundXhttpClientFields(streamSettings); ok {
+		streamSettings = stripped
+	}
 	return &xray.InboundConfig{
 		Listen:         json_util.RawMessage(listen),
 		Port:           i.Port,
 		Protocol:       protocol,
 		Settings:       json_util.RawMessage(settings),
-		StreamSettings: json_util.RawMessage(i.StreamSettings),
+		StreamSettings: json_util.RawMessage(streamSettings),
 		Tag:            i.Tag,
 		Sniffing:       json_util.RawMessage(i.Sniffing),
 	}

+ 82 - 0
internal/database/model/model_test.go

@@ -188,3 +188,85 @@ func TestInboundClientIpsUnmarshalJSONAcceptsBothShapes(t *testing.T) {
 		})
 	}
 }
+
+func TestStripInboundXhttpClientFields_RemovesClientOnlyKnobs(t *testing.T) {
+	stream := `{
+		"network": "xhttp",
+		"security": "reality",
+		"xhttpSettings": {
+			"path": "/app",
+			"host": "example.com",
+			"mode": "stream-one",
+			"xmux": { "maxConcurrency": "16-32" },
+			"downloadSettings": { "network": "xhttp" },
+			"scMinPostsIntervalMs": "20-40",
+			"uplinkChunkSize": 4096,
+			"noGRPCHeader": true
+		}
+	}`
+	out, changed := StripInboundXhttpClientFields(stream)
+	if !changed {
+		t.Fatal("expected client-only xhttp fields to be stripped")
+	}
+	if strings.Contains(out, `"xmux"`) {
+		t.Fatalf("xmux should be removed from xray config stream: %s", out)
+	}
+	for _, key := range []string{"downloadSettings", "scMinPostsIntervalMs", "uplinkChunkSize", "noGRPCHeader"} {
+		if strings.Contains(out, `"`+key+`"`) {
+			t.Fatalf("%s should be removed from xray config stream: %s", key, out)
+		}
+	}
+	var parsed map[string]any
+	if err := json.Unmarshal([]byte(out), &parsed); err != nil {
+		t.Fatalf("invalid JSON: %v", err)
+	}
+	xhttp := parsed["xhttpSettings"].(map[string]any)
+	if xhttp["path"] != "/app" || xhttp["host"] != "example.com" {
+		t.Fatalf("server fields must survive: %#v", xhttp)
+	}
+}
+
+func TestStripInboundXhttpClientFields_UnchangedWithoutClientFields(t *testing.T) {
+	stream := `{"network":"xhttp","xhttpSettings":{"path":"/app","mode":"stream-one"}}`
+	out, changed := StripInboundXhttpClientFields(stream)
+	if changed {
+		t.Fatalf("expected no change, got: %s", out)
+	}
+	if out != stream {
+		t.Fatalf("unchanged stream must be returned verbatim")
+	}
+}
+
+func TestStripInboundXhttpClientFields_NonXhttpPassthrough(t *testing.T) {
+	stream := `{"network":"ws","wsSettings":{"path":"/"}}`
+	out, changed := StripInboundXhttpClientFields(stream)
+	if changed || out != stream {
+		t.Fatalf("non-xhttp stream must pass through unchanged, got changed=%v out=%s", changed, out)
+	}
+}
+
+func TestGenXrayInboundConfig_OmitsInboundXmuxButDbRowUnchanged(t *testing.T) {
+	stream := `{
+		"network": "xhttp",
+		"xhttpSettings": {
+			"path": "/app",
+			"mode": "stream-one",
+			"xmux": { "maxConcurrency": "16-32", "hMaxRequestTimes": "600-900" }
+		}
+	}`
+	in := Inbound{
+		Protocol:       VLESS,
+		Port:           443,
+		Listen:         "0.0.0.0",
+		Tag:            "in-xhttp",
+		Settings:       `{"clients":[],"decryption":"none"}`,
+		StreamSettings: stream,
+	}
+	cfg := in.GenXrayInboundConfig()
+	if strings.Contains(string(cfg.StreamSettings), `"xmux"`) {
+		t.Fatalf("GenXrayInboundConfig must not emit xmux: %s", cfg.StreamSettings)
+	}
+	if strings.Contains(in.StreamSettings, `"xmux"`) == false {
+		t.Fatal("inbound row streamSettings must still carry xmux for subscriptions")
+	}
+}